Compare commits

...

328 Commits

Author SHA1 Message Date
0e4e72429d
refactor(webapp): vue 2.7.16 (#9160) 2026-02-04 10:32:18 +00:00
a78c25a258
refactor(backend): test roles (#9157) 2026-02-03 16:00:37 +00:00
753a300c3f
refactor(backend): middleware before/after (#9128) 2026-02-03 14:20:19 +01:00
dependabot[bot]
b28cdada6d
build(deps): bump node from 25.4.0-alpine to 25.5.0-alpine in /webapp (#9147)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-31 19:36:31 +00:00
dependabot[bot]
8a04b09fd7
build(deps): bump node from 25.4.0-alpine to 25.5.0-alpine in /backend (#9148)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-31 19:10:07 +00:00
dependabot[bot]
b26a06f0ef
build(deps): bump actions/cache from 5.0.2 to 5.0.3 (#9149)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-31 18:50:29 +00:00
dependabot[bot]
e5231acd4f
build(deps): bump docker/login-action from 3.6.0 to 3.7.0 (#9150)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-31 18:24:21 +00:00
dependabot[bot]
8e8bab6f9d
build(deps): bump @aws-sdk/client-s3 and @aws-sdk/lib-storage in /backend (#9151)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-31 18:04:36 +00:00
dependabot[bot]
71228260e5
build(deps-dev): bump @types/node from 25.0.10 to 25.1.0 in /backend (#9152)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-31 17:41:59 +00:00
d96cb32f11
refactor(backend): properly model group-membership (#9124) 2026-01-30 04:56:03 +01:00
bea7c275e8
fix(webapp): allow internal path for custom button (#9129) 2026-01-29 18:51:53 +01:00
07ff0a6b5e
feat(backend): db script disable notifications (#9131) 2026-01-28 22:14:35 +01:00
6fc3c03860 feat(backend): group pins (#9034)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2026-01-28 16:53:29 +01:00
524c4caf5e
refactor(backend): lint graphql (#8473) 2026-01-28 14:42:53 +01:00
8136ec1aba
fix(backend): fix bug in notifications settings for currentUser (#9130) 2026-01-28 00:36:51 +01:00
0ee476cfff
fix(backend): fix email url encoding (#9127) 2026-01-27 22:39:40 +00:00
Wolfgang Huß
b39d1c737b
refactor(other): consolidate Node.js versions and fix e2e workflow (#9126)
Co-authored-by: mahula <lenzmath@posteo.de>
2026-01-27 18:23:26 +01:00
dependabot[bot]
dcff378727
build(deps): bump cheerio from 1.1.2 to 1.2.0 in /backend (#9141)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 17:12:12 +00:00
dependabot[bot]
6c34da94f4
build(deps-dev): bump @types/node from 25.0.9 to 25.0.10 in /backend (#9142)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 15:26:12 +00:00
dependabot[bot]
f5f6ceb2c5
build(deps): bump @aws-sdk/client-s3 and @aws-sdk/lib-storage in /backend (#9144)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 15:04:44 +00:00
dependabot[bot]
e109ac29b7
build(deps): bump peter-evans/repository-dispatch from 09094272a794c6105029af051e3831908c649b6c to cf70392543065ca62813db6712a06df1c4f4ae9f (#9145)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 14:17:06 +00:00
dependabot[bot]
05aeb1c20d
build(deps): bump the metascraper group in /backend with 12 updates (#9136)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-25 14:47:47 +01:00
dependabot[bot]
003ec2bda0
build(deps-dev): bump sass-embedded from 1.97.2 to 1.97.3 (#9135)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 22:24:46 +00:00
dependabot[bot]
208a6dca01
build(deps): bump preview-email from 3.1.0 to 3.1.1 in /backend (#9138)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 20:30:49 +00:00
af497deb77
fix(webapp): allow running frontend tests locally (#9125) 2026-01-24 20:09:36 +00:00
dependabot[bot]
ba481547f1
build(deps): bump node from 25.3.0-alpine to 25.4.0-alpine in /webapp (#9133)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 19:41:50 +00:00
dependabot[bot]
9f4c105335
build(deps-dev): bump @cucumber/cucumber from 12.5.0 to 12.6.0 in the cypress group (#9134)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 18:38:52 +00:00
dependabot[bot]
8012d56dc8
build(deps): bump lodash from 4.17.21 to 4.17.23 in /backend (#9140)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 15:02:14 +00:00
dependabot[bot]
9d994a7554
build(deps-dev): bump prettier from 3.8.0 to 3.8.1 in /webapp (#9139)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 14:41:41 +00:00
dependabot[bot]
322d2aeb97
build(deps-dev): bump prettier from 3.8.0 to 3.8.1 in /backend (#9143)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 14:15:01 +00:00
dependabot[bot]
afee1033af
build(deps): bump actions/checkout from 6.0.1 to 6.0.2 (#9146)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 13:48:54 +00:00
dependabot[bot]
d358fdf6b4
build(deps): bump node from 25.3.0-alpine to 25.4.0-alpine in /backend (#9132)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-24 11:55:14 +00:00
150b318aab
feat(backend): admin creation command for production (#9057)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2026-01-19 18:36:20 +00:00
f0f9b7faec
fix(backend): fix permissions for GroupInviteCodes (#9121)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2026-01-19 18:14:17 +00:00
b22974031c
fix(backend): fix group-myRole field query (#9102)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2026-01-19 17:48:25 +00:00
mahula
0ca45dd06e
refactor(e2e): optimize step definitions loading with filepart pairing (#9122) 2026-01-19 15:00:40 +00:00
6a42d12fda
fix(webapp): fix cta-join-group, can crash when group is not defined (#9103) 2026-01-19 12:05:09 +00:00
b7604e9af5
fix(backend): fix active categories when inproperly configured (#9123)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2026-01-19 11:29:26 +00:00
3d00ae4e25
fix(webapp): fix local webapp tests (#9104)
Co-authored-by: mahula <lenzmath@posteo.de>
2026-01-18 11:55:40 +00:00
dependabot[bot]
fa71b0e189
build(deps-dev): bump the cypress group across 1 directory with 3 updates (#9058)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-18 12:29:03 +01:00
dependabot[bot]
855a049f90
build(deps): bump node from 25.2.1-alpine to 25.3.0-alpine in /webapp (#9105)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-18 00:12:19 +00:00
dependabot[bot]
679e4876bc
build(deps): bump actions/setup-node from 6.1.0 to 6.2.0 (#9107)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 23:45:25 +00:00
dependabot[bot]
e84a81bd2f
build(deps): bump node from 25.2.1-alpine to 25.3.0-alpine in /backend (#9106)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 23:25:20 +00:00
dependabot[bot]
34e547553e
build(deps): bump actions/cache from 5.0.1 to 5.0.2 (#9108)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-18 00:04:22 +01:00
dependabot[bot]
5aa298b3a2
build(deps-dev): bump eslint-plugin-n from 17.23.1 to 17.23.2 in /backend (#9110)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 22:29:23 +00:00
dependabot[bot]
626372a741
build(deps-dev): bump @types/lodash from 4.17.21 to 4.17.23 in /backend (#9111)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 22:04:58 +00:00
dependabot[bot]
a0ac5157a1
build(deps): bump @aws-sdk/lib-storage from 3.958.0 to 3.967.0 in /backend (#9113)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 21:43:30 +00:00
dependabot[bot]
083d81be89
build(deps-dev): bump eslint-plugin-prettier from 5.5.4 to 5.5.5 in /backend (#9114)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 21:22:21 +00:00
dependabot[bot]
721fd75288
build(deps-dev): bump @eslint-community/eslint-plugin-eslint-comments from 4.5.0 to 4.6.0 in /backend (#9120)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 20:57:08 +00:00
dependabot[bot]
7e6d79f1dc
build(deps-dev): bump prettier from 3.7.4 to 3.8.0 in /webapp (#9117)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 20:35:57 +00:00
dependabot[bot]
bc7e750e83
build(deps): bump ioredis from 5.9.1 to 5.9.2 in /backend (#9119)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 20:07:42 +00:00
dependabot[bot]
fbe98aa2b4
build(deps): bump @aws-sdk/client-s3 from 3.967.0 to 3.971.0 in /backend (#9118)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 19:46:46 +00:00
dependabot[bot]
7ae516cf85
build(deps-dev): bump eslint-plugin-prettier from 5.5.4 to 5.5.5 in /webapp (#9115)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 19:22:19 +00:00
dependabot[bot]
02bf7f0ab8
build(deps-dev): bump prettier from 3.7.4 to 3.8.0 in /backend (#9116)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 19:54:52 +01:00
dependabot[bot]
cc8ab95eaf
build(deps-dev): bump @types/node from 25.0.7 to 25.0.9 in /backend (#9112)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-17 17:59:37 +01:00
dependabot[bot]
017bfbc820
build(deps): bump the metascraper group in /backend with 12 updates (#9063)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-14 10:59:59 +00:00
dependabot[bot]
25eeb8d485
build(deps-dev): bump eslint-plugin-jest from 29.12.0 to 29.12.1 in /backend (#9090)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-14 10:32:48 +01:00
dependabot[bot]
eca7f5096e
build(deps): bump ioredis from 5.8.2 to 5.9.1 in /backend (#9095)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-14 09:02:33 +00:00
dependabot[bot]
9f581f4773
build(deps): bump @aws-sdk/client-s3 from 3.958.0 to 3.966.0 in /backend (#9100)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 10:43:05 +00:00
dependabot[bot]
b767e02263
build(deps): bump @aws-sdk/lib-storage from 3.933.0 to 3.958.0 in /backend (#9093)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 11:20:48 +01:00
dependabot[bot]
b01d5e5a27
build(deps-dev): bump @types/node from 25.0.3 to 25.0.5 in /backend (#9096)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 10:54:01 +01:00
dependabot[bot]
06a79225f3
build(deps): bump peter-evans/repository-dispatch from 46fabd2783425293d3f24bc1080da28d046e2dd3 to 09094272a794c6105029af051e3831908c649b6c (#9089)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-13 09:34:38 +01:00
dependabot[bot]
eaa9b34d58
build(deps): bump vue-advanced-chat from 2.0.11 to 2.1.2 in /webapp (#9084)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-01 15:54:02 +00:00
dependabot[bot]
c4fcd558e3
build(deps): bump nginx from 1.29.3-alpine to 1.29.4-alpine in /webapp (#9070)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-01 15:09:57 +00:00
dependabot[bot]
0fef81464f
build(deps): bump @aws-sdk/client-s3 from 3.933.0 to 3.958.0 in /backend (#9086)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-01 12:52:30 +00:00
dependabot[bot]
9c3d3e2fcd
build(deps-dev): bump @types/node from 24.10.1 to 25.0.3 in /backend (#9078)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-01 10:40:44 +00:00
dependabot[bot]
cbb57622f7
build(deps-dev): bump eslint-plugin-jest from 29.1.0 to 29.11.0 in /backend (#9087)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-01 11:19:47 +01:00
dependabot[bot]
861275aeda
build(deps): bump express from 5.1.0 to 5.2.1 in /backend (#9065)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-27 12:08:44 +01:00
dependabot[bot]
c0c396653f
build(deps-dev): bump @types/lodash from 4.17.20 to 4.17.21 in /backend (#9051)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-27 10:42:27 +00:00
dependabot[bot]
5642e0db2c
build(deps-dev): bump ts-jest from 29.4.5 to 29.4.6 in /backend (#9069)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-27 10:20:32 +00:00
dependabot[bot]
49f7118468
build(deps): bump validator from 13.15.23 to 13.15.26 in /webapp (#9083)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-27 09:37:50 +00:00
dependabot[bot]
9a0c97e6ce
build(deps): bump validator from 13.15.23 to 13.15.26 in /backend (#9080)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-27 09:10:01 +00:00
dependabot[bot]
1dd7fc3d75
build(deps): bump nodemailer from 7.0.10 to 7.0.12 in /backend (#9088)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-27 08:48:33 +00:00
dependabot[bot]
0e2d90c634
build(deps): bump actions/upload-artifact from 5.0.0 to 6.0.0 (#9072)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-27 09:26:57 +01:00
dependabot[bot]
56338422a2
build(deps): bump peter-evans/repository-dispatch from d2c43ab06ec1cddd2c2a0aae659681b8465ce87a to 46fabd2783425293d3f24bc1080da28d046e2dd3 (#9060)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 15:30:08 +00:00
dependabot[bot]
817ac7226e
build(deps-dev): bump prettier from 3.6.2 to 3.7.4 in /webapp (#9059)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 14:05:00 +00:00
dependabot[bot]
eb81c0b7e4
build(deps): bump docker/metadata-action from 5.9.0 to 5.10.0 (#9049)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 13:38:02 +00:00
dependabot[bot]
34aa894068
build(deps): bump actions/setup-node from 6.0.0 to 6.1.0 (#9061)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 14:17:19 +01:00
dependabot[bot]
d751e7090f
build(deps): bump gaurav-nelson/github-action-markdown-link-check from 1.0.16 to 1.0.17 (#8329)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 10:15:30 +00:00
dependabot[bot]
a9949e1147
build(deps): bump actions/cache from 4.3.0 to 5.0.1 (#9071)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 09:48:11 +00:00
dependabot[bot]
c78f8deee9
build(deps): bump actions/checkout from 5.0.0 to 6.0.1 (#9062)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 10:04:02 +00:00
dependabot[bot]
2cabe0f4d2
build(deps-dev): bump prettier from 3.6.2 to 3.7.4 in /backend (#9067)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-10 10:09:34 +01:00
dependabot[bot]
0ac7bf908c
build(deps): bump mime-types from 3.0.1 to 3.0.2 in /backend (#9044)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-22 14:54:04 +01:00
dependabot[bot]
0368676b26
build(deps): bump cross-env from 10.0.0 to 10.1.0 in /backend (#8943)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-20 16:52:07 +00:00
dependabot[bot]
cd7931b77b
build(deps): bump validator from 13.15.20 to 13.15.23 in /webapp (#9029)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-20 16:09:36 +00:00
dependabot[bot]
801131e351
build(deps-dev): bump nodemon from 3.1.10 to 3.1.11 in /backend (#9028)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-20 15:20:23 +01:00
dependabot[bot]
30d30a2f2d
build(deps): bump node from 25.1.0-alpine to 25.2.0-alpine in /backend (#9024)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-20 14:57:02 +01:00
dependabot[bot]
9dfd5e31ef
build(deps): bump node from 25.1.0-alpine to 25.2.0-alpine in /webapp (#9023)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-20 11:13:03 +00:00
dependabot[bot]
c86d816e56
build(deps): bump docker/metadata-action from 5.8.0 to 5.9.0 (#9014)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-19 17:51:36 +00:00
dependabot[bot]
de89810eae
build(deps-dev): bump cypress from 15.5.0 to 15.6.0 in the cypress group (#9016)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-19 17:13:53 +00:00
dependabot[bot]
9df61a752a
build(deps): bump bcryptjs from 3.0.2 to 3.0.3 in /backend (#9019)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-19 17:04:20 +01:00
dependabot[bot]
2410fa8527
build(deps): bump @aws-sdk/lib-storage from 3.917.0 to 3.922.0 in /backend (#9022)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 18:38:28 +00:00
dependabot[bot]
b5b7b5d78c
build(deps): bump peter-evans/repository-dispatch from 2c856c63feddee6147cab2f38801935b6a59a765 to d2c43ab06ec1cddd2c2a0aae659681b8465ce87a (#9025)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 18:16:35 +00:00
dependabot[bot]
00d8fd960d
build(deps): bump amannn/action-semantic-pull-request from e49f57ce06c1747542fce2243c7a98682384bc0e to 069817c298f23fab00a8f29a2e556a5eac0f6390 (#9026)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 18:56:58 +01:00
dependabot[bot]
caf95664b8
build(deps-dev): bump eslint-plugin-jest from 29.0.1 to 29.1.0 in /backend (#9027)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 03:50:46 +00:00
dependabot[bot]
66f35ca51d
build(deps): bump @aws-sdk/client-s3 from 3.922.0 to 3.932.0 in /backend (#9030)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 03:52:03 +01:00
dependabot[bot]
c6878f19f9
build(deps-dev): bump @types/node from 24.9.2 to 24.10.1 in /backend (#9031)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 01:57:53 +00:00
dependabot[bot]
421bfe6755
build(deps): bump validator from 13.15.20 to 13.15.23 in /backend (#9033)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-18 02:36:41 +01:00
592a8af42c
chore(release): v3.13.1 (#9003) 2025-11-01 15:37:40 +00:00
dependabot[bot]
a48510f349
build(deps): bump nginx from 1.29.2-alpine to 1.29.3-alpine in /webapp (#9005)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 15:11:44 +00:00
dependabot[bot]
a03e5d888b
build(deps): bump minimatch from 10.0.3 to 10.1.1 in /backend (#9009)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 14:31:33 +00:00
dependabot[bot]
7f7c56cec2
build(deps-dev): bump @types/node from 24.9.1 to 24.9.2 in /backend (#9010)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 14:05:19 +00:00
dependabot[bot]
77d8a5092b
build(deps): bump @aws-sdk/client-s3 from 3.917.0 to 3.922.0 in /backend (#9012)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 13:44:25 +00:00
60bda5a949
fix(webapp): fix map (#9004) 2025-11-01 13:09:44 +00:00
dependabot[bot]
ff2b6465db
build(deps): bump validator from 13.15.15 to 13.15.20 in /backend (#9007)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 09:25:26 +00:00
dependabot[bot]
72b4af395f
build(deps): bump validator from 13.15.15 to 13.15.20 in /webapp (#9011)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 10:04:11 +01:00
2130aa0d68
fix(webapp): always link compiled styleguide (#8998) 2025-10-31 16:36:51 +01:00
dependabot[bot]
1c7f2f27b5
build(deps-dev): bump the babel group with 2 updates (#8982)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-31 00:54:02 +00:00
dependabot[bot]
e2ef636cf8
build(deps): bump node from 24.10.0-alpine to 25.0.0-alpine in /backend (#8972)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-31 00:24:33 +00:00
3f69b70820
fix(workflow): cleanup when running e2e jobs aswell (#9001) 2025-10-31 00:02:39 +00:00
dependabot[bot]
e6244c848c
build(deps): bump node from 20.12.1-alpine to 25.0.0-alpine in /webapp (#8974)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-31 00:21:54 +01:00
0280ac7201
fix(workflow): clean up space on workflow runner machines (#9000) 2025-10-30 23:53:11 +01:00
09327ddc01
fix(docker): fix on build copy command failing when branding (#8996) 2025-10-30 21:16:14 +00:00
49e6f0b7e8
fix(backend): fix node25 compatibility (#8995) 2025-10-30 20:47:39 +00:00
c9b5c02862
fix(webapp): update nuxt to v2.15.8 (#8997) 2025-10-30 20:34:48 +01:00
52459b23f1
chore(release): v3.13.0 (#8991) 2025-10-28 12:06:32 +01:00
dependabot[bot]
737f548f38
build(deps-dev): bump cypress from 15.4.0 to 15.5.0 in the cypress group (#8983)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-26 00:47:36 +00:00
dependabot[bot]
1a29167f08
build(deps): bump actions/upload-artifact from 4.6.2 to 5.0.0 (#8981)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-26 02:20:47 +02:00
dependabot[bot]
06bd4f1ea4
build(deps): bump the metascraper group in /backend with 12 updates (#8985)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-26 00:59:20 +02:00
dependabot[bot]
bbb2a189db
build(deps): bump @aws-sdk/lib-storage from 3.908.0 to 3.913.0 in /backend (#8986)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-25 19:23:48 +00:00
dependabot[bot]
a5f720dba1
build(deps-dev): bump @types/node from 24.8.1 to 24.9.1 in /backend (#8987)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-25 19:01:48 +00:00
dependabot[bot]
3b93d255a6
build(deps): bump nodemailer from 7.0.9 to 7.0.10 in /backend (#8988)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-25 18:41:14 +00:00
dependabot[bot]
5b84b93a16
build(deps): bump @aws-sdk/client-s3 from 3.913.0 to 3.917.0 in /backend (#8989)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-25 18:19:49 +00:00
dependabot[bot]
1622c31010
build(deps): bump ioredis from 5.8.1 to 5.8.2 in /backend (#8990)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-25 19:59:53 +02:00
dependabot[bot]
09536ed0e8
build(deps): bump actions/setup-node from 5.0.0 to 6.0.0 (#8973)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-19 13:37:41 +02:00
dependabot[bot]
005e2569a1
build(deps-dev): bump @types/node from 24.7.2 to 24.8.1 in /backend (#8976)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 15:28:48 +02:00
dependabot[bot]
ed328e70d8
build(deps): bump @aws-sdk/client-s3 from 3.908.0 to 3.913.0 in /backend (#8978)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 14:56:48 +02:00
dependabot[bot]
d96f145e2b
build(deps): bump @aws-sdk/lib-storage from 3.896.0 to 3.905.0 in /backend (#8970)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-12 16:40:02 +00:00
dependabot[bot]
f3d36bb779
build(deps): bump ioredis from 5.8.0 to 5.8.1 in /backend (#8969)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-12 16:19:48 +00:00
dependabot[bot]
5e1d9e280a
build(deps): bump @aws-sdk/client-s3 from 3.901.0 to 3.908.0 in /backend (#8968)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-12 15:37:22 +02:00
dependabot[bot]
5bb8508df9
build(deps-dev): bump @types/node from 24.6.2 to 24.7.1 in /backend (#8967)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-12 12:36:59 +02:00
dependabot[bot]
1b0ef1f81b
build(deps): bump nodemailer from 7.0.6 to 7.0.9 in /backend (#8964)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-11 23:02:08 +00:00
dependabot[bot]
0042914231
build(deps-dev): bump ts-jest from 29.4.4 to 29.4.5 in /backend (#8963)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-11 22:41:30 +00:00
dependabot[bot]
3f216d84d7
build(deps-dev): bump eslint-plugin-jsonc from 2.20.1 to 2.21.0 in /backend (#8962)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-11 22:20:44 +00:00
dependabot[bot]
88ec6fdef0
build(deps-dev): bump the cypress group with 2 updates (#8961)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-11 15:20:31 +00:00
dependabot[bot]
d22479cfa2
build(deps): bump nginx from 1.29.1-alpine to 1.29.2-alpine in /webapp (#8960)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-11 12:45:14 +00:00
dependabot[bot]
ad5108996f
build(deps): bump node from 24.9.0-alpine to 24.10.0-alpine in /backend (#8959)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-11 02:19:54 +00:00
Wolfgang Huß
558e964c83
feat(webapp): add reason and call to action on post view page if commenting is disabled (#8958)
Co-authored-by: Ulf Gebhardt <ulf.gebhardt@webcraft-media.de>
2025-10-10 19:21:07 +02:00
dependabot[bot]
2a7d2f10ed
build(deps-dev): bump dotenv from 17.2.2 to 17.2.3 (#8939)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-07 23:44:45 +00:00
b5895afe3e
refactor(e2e): remove packages not used on root folder (#8954) 2025-10-07 21:16:22 +02:00
1964ff3eb1
refactor(workflow): lint pr title scope e2e (#8955) 2025-10-07 16:40:04 +00:00
9fc2379090
fix(webapp): fix line-height in styleguide when generated (#8952)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2025-10-07 09:41:09 +00:00
1e19bd1be7
refactor(workflow): pr titles - allow scope styleguide (#8953)
Co-authored-by: mahula <lenzmath@posteo.de>
2025-10-07 08:13:53 +00:00
dependabot[bot]
98af683277
build(deps): bump peter-evans/repository-dispatch from de78ac1a711fc6f29e77338f843065faf5335227 to 66739071c2122a05106fc2a2c306fdaf33bb9cda (#8936)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 19:52:48 +00:00
dependabot[bot]
b40015d408
build(deps): bump @aws-sdk/client-s3 from 3.896.0 to 3.901.0 in /backend (#8942)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-06 19:19:41 +00:00
dependabot[bot]
1512167197
build(deps): bump cross-env from 10.0.0 to 10.1.0 in /webapp (#8944)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-04 09:09:36 +00:00
dependabot[bot]
263f35d2e0
build(deps): bump docker/login-action from 3.5.0 to 3.6.0 (#8937)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-04 10:30:57 +02:00
dependabot[bot]
05ab27e868
build(deps): bump amannn/action-semantic-pull-request from e7d011b07ef37e089bea6539210f6a0d360d8af9 to e49f57ce06c1747542fce2243c7a98682384bc0e (#8938)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-04 08:01:56 +00:00
dependabot[bot]
f49022e94d
build(deps-dev): bump @testing-library/jest-dom from 6.8.0 to 6.9.1 in /webapp (#8945)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-04 07:41:47 +00:00
dependabot[bot]
719457b896
build(deps-dev): bump jest from 30.1.3 to 30.2.0 in /backend (#8946)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-04 09:13:20 +02:00
dependabot[bot]
6d4fd54c30
build(deps-dev): bump @types/node from 24.5.2 to 24.6.2 in /backend (#8951)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-04 06:51:42 +00:00
dependabot[bot]
54aeca0375
build(deps): bump tslog from 4.9.3 to 4.10.2 in /backend (#8949)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-04 08:31:00 +02:00
ffffd9e15f
feat(other): deploy styleguide scripts and description (#8935) 2025-10-02 17:57:40 +02:00
b06b29b858
feat(webapp): bind local styleguide & fix maintenance page (#8933) 2025-09-30 14:54:43 +02:00
ace5e9a89e
refactor(other): include styleguide repo including all commits (#8932) 2025-09-29 19:29:41 +02:00
c6fe18f1f7
Merge branch 'master' into include-styleguide-repo 2025-09-28 19:33:28 +02:00
f9af98da97
included whole styleguide repository including all commits instead of a submodule 2025-09-28 19:28:22 +02:00
d9ed8a42b5
remove styleguide
remove .gitmodule
2025-09-28 18:58:59 +02:00
52da131ee4
Merge pull request #2 from Ocelot-Social-Community/remove-gitattributes
fix(other): remove gitattributes
2025-09-28 18:56:12 +02:00
6d9529a021
remove git attributes 2025-09-28 18:51:53 +02:00
32eca68520
Merge pull request #1 from Ocelot-Social-Community/node24-support
Node24 support
2025-09-27 14:46:06 +02:00
e4717e0d89
fix(backend): fix potential leak in updateOnlineStatus (#8923)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2025-09-27 14:37:12 +02:00
33ca59343a
docs(docu): describe current and desired architecture of services (#8850)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2025-09-27 12:09:47 +00:00
dependabot[bot]
1044231e4e
build(deps): bump node from 24.8.0-alpine to 24.9.0-alpine in /backend (#8926)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-27 10:10:03 +00:00
dependabot[bot]
2bf39f5ad0
build(deps): bump actions/cache from 4.2.4 to 4.3.0 (#8927)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-27 09:52:41 +00:00
dependabot[bot]
68030dc5ec
build(deps): bump @aws-sdk/lib-storage from 3.888.0 to 3.896.0 in /backend (#8929)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-27 11:30:46 +02:00
dependabot[bot]
ced52e30db
build(deps): bump ioredis from 5.7.0 to 5.8.0 in /backend (#8931)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-27 10:46:45 +02:00
dependabot[bot]
35c3dd3bbc
build(deps-dev): bump the cypress group across 1 directory with 2 updates (#8925)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-26 20:23:30 +00:00
dependabot[bot]
768d80f2a1
build(deps): bump peter-evans/repository-dispatch from 7279ea08e172078316f128ed1118df40d2904f0f to de78ac1a711fc6f29e77338f843065faf5335227 (#8909)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-26 19:01:29 +00:00
dependabot[bot]
f7dc901c2a
build(deps): bump metascraper from 5.49.2 to 5.49.4 in /backend in the metascraper group (#8911)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-26 15:18:44 +00:00
dependabot[bot]
fadc37a49c
build(deps-dev): bump ts-jest from 29.4.1 to 29.4.4 in /backend (#8912)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-26 17:00:40 +02:00
21c253b690
remove docs folder 2025-09-26 15:36:41 +02:00
08c8cfe42c
fix build 2025-09-26 15:31:56 +02:00
59dd435138
update node-sass 2025-09-26 15:01:10 +02:00
dependabot[bot]
158e1ee4e0
build(deps-dev): bump @types/node from 24.4.0 to 24.5.2 in /backend (#8913)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-26 12:54:08 +02:00
dependabot[bot]
2350e594d4
build(deps): bump graphql-middleware from 4.0.2 to 4.0.3 in /backend (#8915)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-25 20:38:57 +00:00
30560bff69
fix(webapp): fix user avatar & post image urls (#8921)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2025-09-25 22:19:27 +02:00
dependabot[bot]
4fc71fc495
build(deps): bump @sentry/node from 5.15.4 to 5.30.0 in /backend (#8916)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-25 11:12:44 +00:00
dependabot[bot]
3ca00c83c0
build(deps-dev): bump eslint-plugin-n from 17.21.3 to 17.23.1 in /backend (#8918)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-25 10:52:37 +00:00
dependabot[bot]
9f91ff1124
build(deps): bump @aws-sdk/client-s3 from 3.888.0 to 3.893.0 in /backend (#8919)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-25 10:00:42 +00:00
Moriz Wahl
a15351aa42
feat(webapp): dynamic static pages (#8920)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2025-09-25 11:16:07 +02:00
Moriz Wahl
9a4f7326c1
fix(webapp): no distance to me on own profile (#8907)
Co-authored-by: Wolfgang Huß <wolle.huss@pjannto.com>
2025-09-22 23:59:18 +02:00
Wolfgang Huß
2ed92c0a78
feat(webapp): post page group link improvement (#8883) 2025-09-20 10:15:04 +00:00
de65a380ab
fix(backend): fix naming of PRIVATEKEY (#8905) 2025-09-20 11:47:29 +02:00
45d2283138
refactor(backend): externalize all remaining queries in spec files (#8902)
* refactor: externalize all remaining queries in spec files

* User with different queries due to permissions

* fix notification:groups

* fix hashtagsmiddleware

* fix blockedUsers

* fix softDeleteMiddleware

* fix shouts.spec

* fix userInteractions spec

* fix mutedUsers spec

* seocialMedia spec

* fix notificationMiddleware.spec

* fix user.spce & fix undefined activeCategories

* fix notifications.spec

* fix userInteractions.spec

* fix blockedUsers & mutedUsers spec

* remove unused comment

* fix locations spec

* fix orderByMiddleware & spec

* fix lint

* fix shout spec
2025-09-18 17:43:15 +02:00
Robert Schäfer
bdb3c204aa
Merge pull request #194 from Human-Connection/update-version
Update version to 0.5.22
2019-12-09 14:56:04 +01:00
Alina Beck
8be9662987
Update version to 0.5.22 2019-12-09 15:49:33 +03:00
Robert Schäfer
7ef8340500
Merge pull request #190 from Human-Connection/load-svgs-with-babel
Use babel-loader to transpile svgs
2019-12-06 12:33:07 +01:00
Alina Beck
5d6391f505 use babel loader to transpile svgs
IE and Edge had trouble loading the Human Connection network
because object spread operators were left in the minified code
using babel-loader for svgs (as suggested in the vue-svg-loader docs)
hopefully solves this issue
2019-12-05 13:07:45 +03:00
Robert Schäfer
d46fc1570c
Merge pull request #176 from hwilson2563/master
add a xxx-small box to space
2019-10-08 01:48:11 +02:00
Hilary Matusiak
c752e25221 add a xxx-small box to space 2019-10-04 11:46:06 -04:00
Alina Beck
808b3c5a95
Merge pull request #155 from Human-Connection/update_version
Update version to 0.5.21
2019-09-09 18:04:32 +01:00
roschaefer
b05583a8b0 Implement a custom deployment condition via bash 2019-09-09 16:16:29 +02:00
roschaefer
90f747a2a9 Don't deploy if you're not on master
Thank you:
https://github.com/travis-ci/travis-ci/issues/5419#issuecomment-222815942
2019-09-09 15:59:52 +02:00
roschaefer
3d85451aff Update version to 0.5.21 2019-09-09 15:59:52 +02:00
Robert Schäfer
8dafc50286
Merge pull request #154 from Human-Connection/deploy_github_pages_through_travis
Deploy Github pages through Travis CI
2019-09-09 13:02:11 +02:00
roschaefer
0b6360bc0b Deploy Github pages through Travis CI
This solves two problems
* It automatically deploys the documentation
* It deploys the documentation only if the npm package is deployed (ie.
if there is a git tag) and keeps both in sync
2019-09-09 12:59:11 +02:00
Robert Schäfer
2ad4602de3
Merge pull request #153 from Human-Connection/update_all_dependencies
Update all dependencies except `sass-loader`
2019-09-09 12:28:02 +02:00
roschaefer
80eb430460 Update all dependencies except sass-loader
We want to make another release and had a look which dependabot PR was
green. So I just updated the green dependencies manually and had a look
if anything breaks.

Again it would be great to have a build server and a reliable test suite.
2019-09-09 12:22:32 +02:00
Robert Schäfer
aa772ca19f
Merge pull request #149 from Human-Connection/dependabot/npm_and_yarn/sass-loader-8.0.0
Bump sass-loader from 7.2.0 to 8.0.0
2019-09-09 12:20:18 +02:00
Alina Beck
94c0ed83d9 use renamed sass-loader prependData option 2019-09-09 11:16:14 +01:00
Robert Schäfer
ac960d8ecd
Merge pull request #137 from Human-Connection/dependabot/npm_and_yarn/babel-jest-24.9.0
Bump babel-jest from 24.8.0 to 24.9.0
2019-09-09 12:04:12 +02:00
Robert Schäfer
0889e137d9
Merge pull request #144 from Human-Connection/dependabot/npm_and_yarn/vue/cli-plugin-unit-jest-3.11.0
Bump @vue/cli-plugin-unit-jest from 3.10.0 to 3.11.0
2019-09-09 12:03:31 +02:00
Robert Schäfer
66c049544e
Merge pull request #152 from Human-Connection/fix-grid-component
Fix grid component
2019-09-09 11:59:05 +02:00
Alina Beck
dc4ce74b21 add missing )) 2019-09-04 17:45:20 +01:00
dependabot-preview[bot]
e0d3717ca0
Bump sass-loader from 7.2.0 to 8.0.0
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 7.2.0 to 8.0.0.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v7.2.0...v8.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-30 04:20:00 +00:00
Grzegorz Leoniec
73195606fb Bump version 2019-08-25 12:48:11 +02:00
Grzegorz Leoniec
e6fe9ba4b7 Commit phantom changes 2019-08-25 12:47:49 +02:00
Grzegorz Leoniec
3c65a2426c Updated docs 2019-08-25 12:44:49 +02:00
Grzegorz Leoniec
c9d969afc6 Fixed font issues 2019-08-25 12:20:16 +02:00
dependabot-preview[bot]
573890102f
Bump @vue/cli-plugin-unit-jest from 3.10.0 to 3.11.0
Bumps [@vue/cli-plugin-unit-jest](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-unit-jest) from 3.10.0 to 3.11.0.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v3.11.0/packages/@vue/cli-plugin-unit-jest)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-22 04:23:15 +00:00
Robert Schäfer
64389852c4
Merge pull request #140 from Human-Connection/deploy_github_page
Deploy GitHub page
2019-08-21 12:00:15 +02:00
roschaefer
2536542aa9 Rebuild the docs/
Update the deployed docs at https://styleguide.human-connection.org/ via
Github pages.
2019-08-21 00:32:28 +02:00
roschaefer
bcad113682 Add missing http-server to dev dependencies
We cannot assume it to be installed globally.
2019-08-21 00:16:23 +02:00
dependabot-preview[bot]
208bfc1550
Bump babel-jest from 24.8.0 to 24.9.0
Bumps [babel-jest](https://github.com/facebook/jest/tree/HEAD/packages/babel-jest) from 24.8.0 to 24.9.0.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/commits/v24.9.0/packages/babel-jest)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-19 04:28:01 +00:00
Alina Beck
be4d526505 bump version to 0.5.19 2019-08-15 17:12:48 +01:00
Alina Beck
9e49afaac3
Merge pull request #131 from Human-Connection/add-grid-layout
Add grid component
2019-08-15 17:08:24 +01:00
roschaefer
79cbcf2901 Update to 0.5.18
dependency updates
2019-08-15 17:29:48 +02:00
roschaefer
f83a13c7f2 Update transitive dependencies
Should fix our security vulnerabilities
2019-08-15 17:29:04 +02:00
Robert Schäfer
f87d8dacfd
Merge pull request #122 from Human-Connection/dependabot/npm_and_yarn/remarkable-1.7.4
[Security] Bump remarkable from 1.7.1 to 1.7.4
2019-08-15 17:18:59 +02:00
dependabot-preview[bot]
461a762205
[Security] Bump remarkable from 1.7.1 to 1.7.4
Bumps [remarkable](https://github.com/jonschlinkert/remarkable) from 1.7.1 to 1.7.4. **This update includes security fixes.**
- [Release notes](https://github.com/jonschlinkert/remarkable/releases)
- [Changelog](https://github.com/jonschlinkert/remarkable/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jonschlinkert/remarkable/compare/1.7.1...v1.7.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-15 15:11:41 +00:00
Robert Schäfer
9d5cb51ed5
Merge pull request #134 from Human-Connection/massive_update
Update all packages with `ncu -u`
2019-08-15 17:10:10 +02:00
roschaefer
e6352cc6fb Update all packages with ncu -u
And fix another bug: require(...).default returns the string content
of the file.
2019-08-15 16:07:16 +02:00
Robert Schäfer
57fe905a96
Merge pull request #133 from Human-Connection/fix_build_errors
Fix build errors
2019-08-15 15:51:39 +02:00
roschaefer
79a8fc5f5f Fix javascript runtime errors
I could see a warning about duplicate keys on the `Chip` page, also
there seems to be a missing check if a param is null (I used a default).
2019-08-15 15:46:29 +02:00
roschaefer
3d41d811d2 Update the minimum of package to get a build
I was not able to build the styleguide on my machine. This commit allows
me to get at least to http://127.0.0.1:8080/ to see some components.

Here are my specs:
```sh
robert@e480 ~> node --version
v12.7.0
robert@e480 ~> yarn --version
1.17.3
robert@e480 ~> cat /proc/version
Linux version 5.2.6-arch1-1-ARCH (builduser@heftig-3961) (gcc version 9.1.0 (GCC)) #1 SMP PREEMPT Sun Aug 4 14:58:49 UTC 2019
```
2019-08-15 15:43:46 +02:00
Alina Beck
df7b736f5d add validator function for grid gap 2019-08-14 15:29:48 +01:00
Alina Beck
3f4a8a543e pass numbers as numbers 2019-08-14 15:22:08 +01:00
Alina Beck
3fa3d6f5c4 use design tokens for grid gap 2019-08-14 12:28:17 +01:00
Alina Beck
4edb8cd541 add grid item to grid docs 2019-08-13 19:08:27 +01:00
Alina Beck
f51890a54f adjust default values, add docs 2019-08-13 18:58:28 +01:00
Alina Beck
b730b91e2c set placeholder height to 100% 2019-08-13 17:54:17 +01:00
Alina Beck
2ccaeb6166 add grid item component 2019-08-13 17:34:35 +01:00
Alina Beck
3a93d9abb3 add grid component 2019-08-13 16:23:44 +01:00
6f1d94610e
Merge pull request #84 from Human-Connection/2019/kw19/fix_form_user_input_event
2019/kw19/fix_form_user_input_event
2019-05-07 20:04:25 +02:00
d70d331e6f
implemented input-valid ad input events to enable notification of every userinput, field match example, fixed console output handling, added ulfgebhardt to the authors, version bump to 0.5.17 2019-05-07 19:59:08 +02:00
396e08127a
Merge pull request #83 from Human-Connection/2019/kw19/fix_form_user_input_event
2019/kw19/fix_form_user_input_event
2019-05-07 14:31:57 +02:00
059072982f
bump version from 0.5.15 to 0.5.16 2019-05-07 13:36:27 +02:00
8da1d99e33
Merge pull request #82 from Human-Connection/2019/kw19/windows_lineendings
2019/kw19/windows_lineendings
2019-05-07 13:21:07 +02:00
bbbf051026
fixed user input event to be fired correctly, added reset event 2019-05-07 13:20:50 +02:00
5e9e55f00e
gitattributes lf 2019-05-06 16:50:22 +02:00
Grzegorz Leoniec
3f7a00b8b9
Use online instead of is-online on avatar 2019-03-15 17:48:16 +01:00
Grzegorz Leoniec
e76fed663c
Fixed some avatar styling 2019-03-15 13:36:35 +01:00
Grzegorz Leoniec
c8002a2b9a
Fixed avatar styling in conjunction with the size values 2019-03-15 13:29:30 +01:00
Grzegorz Leoniec
b3bde1aa2a
Added fixed size to avatar component 2019-03-15 11:24:37 +01:00
Grzegorz Leoniec
9b8ac8f1b3
Fixed avatar and added online status 2019-03-14 21:03:28 +01:00
Grzegorz Leoniec
baf3134f9d
Push 0.5.15 2019-03-12 15:25:32 +01:00
Grzegorz Leoniec
19a6badd55
Upgrade docs 2019-03-12 15:25:07 +01:00
Grzegorz Leoniec
bc3e3875e8
Update browser list 2019-03-12 15:24:53 +01:00
Grzegorz Leoniec
fe536b626f
0.5.14 2019-03-12 10:47:33 +01:00
Grzegorz Leoniec
4e43be4ff6
Merge branch 'master' of github.com:Human-Connection/Nitro-Styleguide 2019-03-12 10:47:02 +01:00
Grzegorz Leoniec
da22c5869a
Try to fix build issue 2019-03-12 10:46:49 +01:00
Grzegorz Leoniec
e77754cb78
Fixed npm badge 2019-03-11 20:48:12 +01:00
Grzegorz Leoniec
319f6a90f7
Try to fix some issues 0.5.12 2019-03-11 20:44:51 +01:00
Grzegorz Leoniec
562fddb1b4
Fixed docs 2019-03-11 11:21:36 +01:00
Grzegorz Leoniec
e7c25e15ed
Updated docs 2019-03-11 11:13:42 +01:00
Grzegorz Leoniec
ce20d5b58e
Update README.md 2019-03-09 08:59:51 +01:00
Grzegorz Leoniec
de26ce29e6
Added npm badge 2019-03-09 08:57:11 +01:00
Grzegorz Leoniec
fd83399e70
Merge pull request #34 from Human-Connection/remove_portal
Remove vue-portal
2019-03-09 08:48:59 +01:00
Grzegorz Leoniec
a100c09ac5
Updated to 0.5.11 2019-03-09 08:47:20 +01:00
Robert Schäfer
f6fbe058f6 Remove occurences of vue-portal
I don't quite get the reason for having `vue-portal`. Maybe to ensure,
the modal get's rendered only once? If yes, then I would suggest to give
the user the responsibility to render only one modal at a time.
2019-03-09 01:41:33 +01:00
Robert Schäfer
02f34f0fcf Run git clean -dfx
deletes ignored but indexed files
2019-03-09 01:39:26 +01:00
Grzegorz Leoniec
ce69fe5cb7
Style fixes 2019-03-07 11:02:29 +01:00
Grzegorz Leoniec
b941bb24d8
Fixed HC Documentation link to point to nitro 2019-03-07 10:41:30 +01:00
Grzegorz Leoniec
58cb1ee096
Try to fix build 0.5.10 2019-03-07 10:35:59 +01:00
Grzegorz Leoniec
c64d6fbb83
Update to 0.5.9 2019-03-07 10:16:22 +01:00
Grzegorz Leoniec
702cd07171
Remove dist folder 2019-03-07 10:15:45 +01:00
Grzegorz Leoniec
ba27f81977
Fixing build and styleguide CNAME 2019-03-07 10:15:22 +01:00
Grzegorz Leoniec
900dad94ae
Updated docs 2019-03-07 10:15:00 +01:00
Grzegorz Leoniec
a98e22dba1
Bumped version to 0.5.8 2019-03-07 09:46:47 +01:00
Grzegorz Leoniec
e9eaafd067
Extend build by also using yarn build beside build:lib 2019-03-07 09:46:33 +01:00
Grzegorz Leoniec
21dd977bec
Fixed some linting issues 2019-03-07 09:46:08 +01:00
Grzegorz Leoniec
23cbd42d3f
Bumped version to 0.5.7 2019-03-06 20:13:19 +01:00
Grzegorz Leoniec
201de94230
Improved select input 2019-03-06 20:13:01 +01:00
Grzegorz Leoniec
273c0ca1a6
Bump version to 0.5.6 2019-03-06 19:05:37 +01:00
Grzegorz Leoniec
4f87a3feec
Bumped version to 0.5.5 2019-03-04 16:43:58 +01:00
Grzegorz Leoniec
dca1ea6c42
Fixed select behavior 2019-03-04 14:24:27 +01:00
Grzegorz Leoniec
01ca0a6d8b
Fixed search input behavior 2019-03-04 11:25:19 +01:00
Grzegorz Leoniec
4a574ee430
Added vue and vue-portal as peed dependencies 0.5.4 2019-03-01 10:35:05 +01:00
Grzegorz Leoniec
83da96f8c8
Bumped version to 0.5.3 2019-03-01 10:11:41 +01:00
Grzegorz Leoniec
b7371582dc
Fixed issue with select filtering 2019-03-01 10:10:56 +01:00
Grzegorz Leoniec
fbc3ee5658
Added .npmignore 2019-02-26 11:08:28 +01:00
Grzegorz Leoniec
4edea10b18
ignore icons _all icons in npm package 2019-02-26 10:47:44 +01:00
Grzegorz Leoniec
0bbaba189f
Missed to bumb version 2019-02-25 11:32:26 +01:00
Grzegorz Leoniec
a0189658a8
Making the package public 2019-02-25 11:31:40 +01:00
Grzegorz Leoniec
f4f16962d9
Merge branch 'master' of github.com:Human-Connection/Nitro-Styleguide 2019-02-25 11:28:23 +01:00
Grzegorz Leoniec
095b1539ad
Added deyplo config for npm 2019-02-25 11:27:38 +01:00
Grzegorz Leoniec
6a7256f0c1
Merge pull request #14 from Human-Connection/dependabot/npm_and_yarn/vue-and-vue-template-compiler-2.6.7
Bump vue and vue-template-compiler
2019-02-25 10:49:56 +01:00
dependabot[bot]
1a82e6edda
Bump vue and vue-template-compiler
Bumps [vue](https://github.com/vuejs/vue) and [vue-template-compiler](https://github.com/vuejs/vue). These dependencies needed to be updated together.

Updates `vue` from 2.6.6 to 2.6.7
- [Release notes](https://github.com/vuejs/vue/releases)
- [Commits](https://github.com/vuejs/vue/compare/v2.6.6...v2.6.7)

Updates `vue-template-compiler` from 2.6.6 to 2.6.7
- [Release notes](https://github.com/vuejs/vue/releases)
- [Commits](https://github.com/vuejs/vue/compare/v2.6.6...v2.6.7)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-25 09:46:00 +00:00
Grzegorz Leoniec
ffe1ceabf1
Fixing build 2019-02-25 10:43:47 +01:00
Grzegorz Leoniec
81bcdc87e3
Disable testing for now 2019-02-25 10:35:57 +01:00
Grzegorz Leoniec
85114b78d6
Added missing Icon 2019-02-25 09:58:29 +01:00
Grzegorz Leoniec
105ab19424
Merge branch 'master' of github.com:Human-Connection/Nitro-Styleguide 2019-02-25 09:55:04 +01:00
Grzegorz Leoniec
7a628d515d
Added editor icons 2019-02-25 09:54:54 +01:00
Grzegorz Leoniec
71258216f2
Update README.md 2019-02-25 08:41:19 +01:00
Grzegorz Leoniec
9c35a70e07
Update README.md 2019-02-25 08:23:56 +01:00
Grzegorz Leoniec
2a8fef8cb6
Try to fix build 2019-02-24 21:00:34 +01:00
Grzegorz Leoniec
3a879a954f
Try to fix build 2019-02-24 20:55:45 +01:00
Grzegorz Leoniec
aea2bb079e
Force yarn on before_install 2019-02-24 20:38:08 +01:00
Grzegorz Leoniec
bdeb23a428
Build as lib 2019-02-24 20:34:05 +01:00
Grzegorz Leoniec
58b3034fc8
Removing yarn service as its invalid 2019-02-24 20:31:50 +01:00
Grzegorz Leoniec
d68357c74d
Merge branch 'master' of github.com:Human-Connection/Nitro-Styleguide 2019-02-24 20:26:45 +01:00
Grzegorz Leoniec
c05e80420f
Fixing travis 2019-02-24 20:26:42 +01:00
Grzegorz Leoniec
2021531ab4
Merge pull request #6 from Human-Connection/dependabot/npm_and_yarn/@babel/standalone-7.3.3
Bump @babel/standalone from 7.3.2 to 7.3.3
2019-02-24 20:25:14 +01:00
Grzegorz Leoniec
fd15352390
Renamed travis.yml 2019-02-24 20:21:16 +01:00
Grzegorz Leoniec
86c8c30bd4
added travis build 2019-02-24 20:19:35 +01:00
dependabot[bot]
0d7fe46cc4
Bump @babel/standalone from 7.3.2 to 7.3.3
Bumps [@babel/standalone](https://github.com/babel/babel-standalone) from 7.3.2 to 7.3.3.
- [Release notes](https://github.com/babel/babel-standalone/releases)
- [Commits](https://github.com/babel/babel-standalone/commits)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-24 19:17:13 +00:00
Grzegorz Leoniec
970613a416
Merge pull request #13 from Human-Connection/dependabot/npm_and_yarn/codemirror-5.44.0
Bump codemirror from 5.43.0 to 5.44.0
2019-02-24 20:14:26 +01:00
Grzegorz Leoniec
2e7acaa68b
Merge pull request #11 from Human-Connection/dependabot/npm_and_yarn/@vue/cli-plugin-unit-jest-3.4.1
Bump @vue/cli-plugin-unit-jest from 3.4.0 to 3.4.1
2019-02-24 20:14:03 +01:00
Grzegorz Leoniec
cc456da586
Merge pull request #16 from Human-Connection/search-input
Search input
2019-02-24 20:11:56 +01:00
Grzegorz Leoniec
47291993d2
Merge pull request #15 from Human-Connection/changes-for-editor
Improve input.js for editor use
2019-02-24 20:14:32 +01:00
Grzegorz Leoniec
58f40fbed8
Improve imput.js for editor use 2019-02-24 20:04:38 +01:00
Grzegorz Leoniec
3975ccfd71
Merge remote-tracking branch 'origin/master' into search-input 2019-02-24 17:22:08 +01:00
Grzegorz Leoniec
05dce9254c
added changes to use select as search input 2019-02-24 17:21:00 +01:00
dependabot[bot]
85c994e94c
Bump codemirror from 5.43.0 to 5.44.0
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.43.0 to 5.44.0.
- [Release notes](https://github.com/codemirror/CodeMirror/releases)
- [Changelog](https://github.com/codemirror/CodeMirror/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codemirror/CodeMirror/compare/5.43.0...5.44.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-22 04:44:32 +00:00
dependabot[bot]
ea16195296
Bump @vue/cli-plugin-unit-jest from 3.4.0 to 3.4.1
Bumps [@vue/cli-plugin-unit-jest](https://github.com/vuejs/vue-cli/tree/HEAD/packages/@vue/cli-plugin-unit-jest) from 3.4.0 to 3.4.1.
- [Release notes](https://github.com/vuejs/vue-cli/releases)
- [Changelog](https://github.com/vuejs/vue-cli/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-cli/commits/v3.4.1/packages/@vue/cli-plugin-unit-jest)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-21 21:46:27 +00:00
dependabot[bot]
01d0d4819f
Merge pull request #12 from Human-Connection/dependabot/npm_and_yarn/theo-8.1.2 2019-02-21 21:42:59 +00:00
dependabot[bot]
3089f99312
Bump theo from 8.1.1 to 8.1.2
Bumps [theo](https://github.com/salesforce-ux/theo) from 8.1.1 to 8.1.2.
- [Release notes](https://github.com/salesforce-ux/theo/releases)
- [Changelog](https://github.com/salesforce-ux/theo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/salesforce-ux/theo/compare/v8.1.1...v8.1.2)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2019-02-21 05:16:42 +00:00
Grzegorz Leoniec
c141674c65
Merge pull request #2 from Human-Connection/fix-missing-files
[WIP] Fix missing files
2019-02-16 10:33:36 +01:00
Grzegorz Leoniec
1a84af06ff
Reactivate source maps in production 2019-02-16 10:31:22 +01:00
Grzegorz Leoniec
da25934f4c
Remove generated files again 2019-02-16 10:28:54 +01:00
Grzegorz Leoniec
e767ee5870
Fixed build 2019-02-15 21:52:54 +01:00
Grzegorz Leoniec
746c48a35f
Try to fix build issues 2019-02-15 21:36:15 +01:00
Grzegorz Leoniec
8d759834e1
Stop fixing issues 2019-02-15 21:23:32 +01:00
Grzegorz Leoniec
f97029dc07
Do not automatically fix prettiere issues 2019-02-15 20:49:21 +01:00
Grzegorz Leoniec
2212cf70f5
Downgrade prettier 2019-02-15 20:40:03 +01:00
Grzegorz Leoniec
659c8b5106
Fixed missing files 2019-02-15 20:38:21 +01:00
Grzegorz Leoniec
3cdd06b252
Moved Styleguide to its own repo 2019-02-15 19:04:41 +01:00
1335 changed files with 54319 additions and 18233 deletions

View File

@ -12,6 +12,7 @@ docker: &docker
webapp: &webapp webapp: &webapp
- '.github/workflows/test-webapp.yml' - '.github/workflows/test-webapp.yml'
- 'webapp/**/*' - 'webapp/**/*'
- 'styleguide/**/*'
- 'package.json' - 'package.json'
docs-check: &docs-check docs-check: &docs-check

View File

@ -11,7 +11,7 @@ jobs:
documentation: ${{ steps.changes.outputs.documentation }} documentation: ${{ steps.changes.outputs.documentation }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Check for markdown file changes - name: Check for markdown file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
@ -28,13 +28,13 @@ jobs:
if: needs.files-changed.outputs.markdown == 'true' if: needs.files-changed.outputs.markdown == 'true'
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Remove uncheckable documentation files - name: Remove uncheckable documentation files
run: rm -rf ./CHANGELOG.md # workaround until https://github.com/gaurav-nelson/github-action-markdown-link-check/pull/183 has been done run: rm -rf ./CHANGELOG.md # workaround until https://github.com/gaurav-nelson/github-action-markdown-link-check/pull/183 has been done
- name: Check Markdown Links - name: Check Markdown Links
uses: gaurav-nelson/github-action-markdown-link-check@1b916f2cf6c36510a6059943104e3c42ce6c16bc # 1.0.15 uses: gaurav-nelson/github-action-markdown-link-check@3c3b66f1f7d0900e37b71eca45b63ea9eedfce31 # 1.0.15
with: with:
use-quiet-mode: 'yes' use-quiet-mode: 'yes'
use-verbose-mode: 'no' use-verbose-mode: 'no'
@ -51,10 +51,10 @@ jobs:
if: needs.files-changed.outputs.documentation == 'true' if: needs.files-changed.outputs.documentation == 'true'
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Setup Node 20 - name: Setup Node 20
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v4.0.3 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4.0.3
with: with:
node-version: '20' node-version: '20'

View File

@ -22,7 +22,7 @@ jobs:
continue-on-error: true continue-on-error: true
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Cleanup - name: Cleanup
run: | run: |

View File

@ -13,7 +13,7 @@ jobs:
documentation: ${{ steps.changes.outputs.documentation }} documentation: ${{ steps.changes.outputs.documentation }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Check for file changes - name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
@ -27,10 +27,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Setup Node 20 - name: Setup Node 20
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v4.0.3 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4.0.3
with: with:
node-version: 20 node-version: 20

View File

@ -24,27 +24,27 @@ jobs:
file: backend/Dockerfile file: backend/Dockerfile
target: production target: production
- name: webapp-base - name: webapp-base
context: webapp context: .
file: webapp/Dockerfile file: webapp/Dockerfile
target: base target: base
- name: webapp-build - name: webapp-build
context: webapp context: .
file: webapp/Dockerfile file: webapp/Dockerfile
target: build target: build
- name: webapp - name: webapp
context: webapp context: .
file: webapp/Dockerfile file: webapp/Dockerfile
target: production target: production
- name: maintenance-base - name: maintenance-base
context: webapp context: .
file: webapp/Dockerfile.maintenance file: webapp/Dockerfile.maintenance
target: base target: base
- name: maintenance-build - name: maintenance-build
context: webapp context: .
file: webapp/Dockerfile.maintenance file: webapp/Dockerfile.maintenance
target: build target: build
- name: maintenance - name: maintenance
context: webapp context: .
file: webapp/Dockerfile.maintenance file: webapp/Dockerfile.maintenance
target: production target: production
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -59,16 +59,16 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: | tags: |

View File

@ -14,9 +14,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
with: with:
fetch-depth: 0 # Fetch full History for changelog fetch-depth: 0 # Fetch full History for changelog
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4.0.3
with:
node-version-file: '.nvmrc'
- name: Setup env - name: Setup env
run: | run: |
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
@ -54,9 +58,13 @@ jobs:
needs: [github_tag] needs: [github_tag]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
with: with:
fetch-depth: 0 # Fetch full History for changelog fetch-depth: 0 # Fetch full History for changelog
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4.0.3
with:
node-version-file: '.nvmrc'
- name: Setup env - name: Setup env
run: | run: |
echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV echo "VERSION=$(node -p -e "require('./package.json').version")" >> $GITHUB_ENV
@ -64,7 +72,7 @@ jobs:
echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV echo "BUILD_COMMIT=${GITHUB_SHA}" >> $GITHUB_ENV
- run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV - run: echo "BUILD_VERSION=${VERSION}-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
#- name: Repository Dispatch #- name: Repository Dispatch
# uses: peter-evans/repository-dispatch@7279ea08e172078316f128ed1118df40d2904f0f # v3.0.0 # uses: peter-evans/repository-dispatch@cf70392543065ca62813db6712a06df1c4f4ae9f # v3.0.0
# with: # with:
# token: ${{ github.token }} # token: ${{ github.token }}
# event-type: trigger-ocelot-build-success # event-type: trigger-ocelot-build-success
@ -72,7 +80,7 @@ jobs:
# client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}' # client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
- name: Repository Dispatch stage.ocelot.social - name: Repository Dispatch stage.ocelot.social
uses: peter-evans/repository-dispatch@7279ea08e172078316f128ed1118df40d2904f0f # v3.0.0 uses: peter-evans/repository-dispatch@cf70392543065ca62813db6712a06df1c4f4ae9f # v3.0.0
with: with:
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
event-type: trigger-ocelot-build-success event-type: trigger-ocelot-build-success
@ -80,7 +88,7 @@ jobs:
client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "GITHUB_RUN_NUMBER": "${{ env.GITHUB_RUN_NUMBER }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}' client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "GITHUB_RUN_NUMBER": "${{ env.GITHUB_RUN_NUMBER }}", "VERSION": "${VERSION}", "BUILD_DATE": "${BUILD_DATE}", "BUILD_COMMIT": "${BUILD_COMMIT}", "BUILD_VERSION": "${BUILD_VERSION}"}'
- name: Repository Dispatch stage.yunite.me - name: Repository Dispatch stage.yunite.me
uses: peter-evans/repository-dispatch@7279ea08e172078316f128ed1118df40d2904f0f # v3.0.0 uses: peter-evans/repository-dispatch@cf70392543065ca62813db6712a06df1c4f4ae9f # v3.0.0
with: with:
token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository token: ${{ secrets.OCELOT_PUBLISH_EVENT_PAT }} # this token is required to access the other repository
event-type: trigger-ocelot-build-success event-type: trigger-ocelot-build-success

View File

@ -11,7 +11,7 @@ jobs:
backend: ${{ steps.changes.outputs.backend }} backend: ${{ steps.changes.outputs.backend }}
docker: ${{ steps.changes.outputs.docker }} docker: ${{ steps.changes.outputs.docker }}
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Check for backend file changes - name: Check for backend file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
@ -28,7 +28,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Neo4J | Build 'community' image - name: Neo4J | Build 'community' image
run: | run: |
@ -37,7 +37,7 @@ jobs:
- name: Cache docker images - name: Cache docker images
id: cache-neo4j id: cache-neo4j
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: /tmp/neo4j.tar path: /tmp/neo4j.tar
key: ${{ github.run_id }}-backend-neo4j-cache key: ${{ github.run_id }}-backend-neo4j-cache
@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: backend | Build 'test' image - name: backend | Build 'test' image
run: | run: |
@ -58,7 +58,7 @@ jobs:
- name: Cache docker images - name: Cache docker images
id: cache-backend id: cache-backend
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: /tmp/backend.tar path: /tmp/backend.tar
key: ${{ github.run_id }}-backend-cache key: ${{ github.run_id }}-backend-cache
@ -70,7 +70,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4.0.3
with:
node-version-file: 'backend/.nvmrc'
- name: backend | Lint - name: backend | Lint
run: cd backend && yarn && yarn run lint run: cd backend && yarn && yarn run lint
@ -84,17 +89,17 @@ jobs:
checks: write checks: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Restore Neo4J cache - name: Restore Neo4J cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: /tmp/neo4j.tar path: /tmp/neo4j.tar
key: ${{ github.run_id }}-backend-neo4j-cache key: ${{ github.run_id }}-backend-neo4j-cache
fail-on-cache-miss: true fail-on-cache-miss: true
- name: Restore Backend cache - name: Restore Backend cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: /tmp/backend.tar path: /tmp/backend.tar
key: ${{ github.run_id }}-backend-cache key: ${{ github.run_id }}-backend-cache

View File

@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
- name: Copy backend env file - name: Copy backend env file
run: | run: |
@ -31,7 +31,7 @@ jobs:
docker compose -f docker-compose.yml -f docker-compose.test.yml down docker compose -f docker-compose.yml -f docker-compose.test.yml down
- name: Cache docker images - name: Cache docker images
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: | path: |
/tmp/backend.tar /tmp/backend.tar
@ -46,7 +46,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
- name: Copy backend env file - name: Copy backend env file
run: | run: |
@ -59,7 +59,7 @@ jobs:
docker save "ghcr.io/ocelot-social-community/ocelot-social/webapp:test" > /tmp/webapp.tar docker save "ghcr.io/ocelot-social-community/ocelot-social/webapp:test" > /tmp/webapp.tar
- name: Cache docker image - name: Cache docker image
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: /tmp/webapp.tar path: /tmp/webapp.tar
key: ${{ github.run_id }}-e2e-webapp-cache key: ${{ github.run_id }}-e2e-webapp-cache
@ -68,13 +68,16 @@ jobs:
name: Fullstack | prepare cypress name: Fullstack | prepare cypress
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Delete huge unnecessary tools folder
run: rm -rf /opt/hostedtoolcache
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v4.4.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4.4.0
with: with:
node-version-file: 'backend/.tool-versions' node-version-file: 'backend/.nvmrc'
cache: 'yarn' cache: 'yarn'
- name: Copy env files - name: Copy env files
@ -84,7 +87,8 @@ jobs:
- name: Install cypress requirements - name: Install cypress requirements
run: | run: |
wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386" sudo wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386"
sudo chmod +x /opt/cucumber-json-formatter
cd backend cd backend
yarn install yarn install
yarn build yarn build
@ -93,7 +97,7 @@ jobs:
- name: Cache docker image - name: Cache docker image
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: | path: |
/opt/cucumber-json-formatter /opt/cucumber-json-formatter
@ -113,17 +117,20 @@ jobs:
# run copies of the current job in parallel # run copies of the current job in parallel
job: [1, 2, 3, 4, 5, 6, 7, 8] job: [1, 2, 3, 4, 5, 6, 7, 8]
steps: steps:
- name: Delete huge unnecessary tools folder
run: rm -rf /opt/hostedtoolcache
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v4.4.0 uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4.4.0
with: with:
node-version-file: 'backend/.tool-versions' node-version-file: 'backend/.nvmrc'
cache: 'yarn' cache: 'yarn'
- name: Restore cypress cache - name: Restore cypress cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: | path: |
/opt/cucumber-json-formatter /opt/cucumber-json-formatter
@ -133,7 +140,7 @@ jobs:
restore-keys: ${{ github.run_id }}-e2e-cypress restore-keys: ${{ github.run_id }}-e2e-cypress
- name: Restore backend environment cache - name: Restore backend environment cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: | path: |
/tmp/backend.tar /tmp/backend.tar
@ -144,7 +151,7 @@ jobs:
key: ${{ github.run_id }}-e2e-backend-environment-cache key: ${{ github.run_id }}-e2e-backend-environment-cache
- name: Restore webapp cache - name: Restore webapp cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: /tmp/webapp.tar path: /tmp/webapp.tar
key: ${{ github.run_id }}-e2e-webapp-cache key: ${{ github.run_id }}-e2e-webapp-cache
@ -175,7 +182,7 @@ jobs:
- name: Full stack tests | if tests failed, upload report - name: Full stack tests | if tests failed, upload report
id: e2e-report id: e2e-report
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with: with:
name: ocelot-e2e-test-report-pr${{ needs.docker_preparation.outputs.pr-number }} name: ocelot-e2e-test-report-pr${{ needs.docker_preparation.outputs.pr-number }}
path: /home/runner/work/Ocelot-Social/Ocelot-Social/cypress/reports/cucumber_html_report path: /home/runner/work/Ocelot-Social/Ocelot-Social/cypress/reports/cucumber_html_report
@ -187,7 +194,7 @@ jobs:
continue-on-error: true continue-on-error: true
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
- name: Full stack tests | cleanup cache - name: Full stack tests | cleanup cache
run: | run: |

View File

@ -11,7 +11,7 @@ jobs:
docker: ${{ steps.changes.outputs.docker }} docker: ${{ steps.changes.outputs.docker }}
webapp: ${{ steps.changes.outputs.webapp }} webapp: ${{ steps.changes.outputs.webapp }}
steps: steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Check for frontend file changes - name: Check for frontend file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
@ -28,7 +28,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4.0.3
with:
node-version-file: 'webapp/.nvmrc'
- name: Check translation files - name: Check translation files
run: | run: |
@ -42,15 +47,15 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Webapp | Build 'test' image - name: Webapp | Build 'test' image
run: | run: |
docker build --target test -t "ocelotsocialnetwork/webapp:test" webapp/ docker build --target test -f webapp/Dockerfile -t "ocelotsocialnetwork/webapp:test" .
docker save "ocelotsocialnetwork/webapp:test" > /tmp/webapp.tar docker save "ocelotsocialnetwork/webapp:test" > /tmp/webapp.tar
- name: Cache docker image - name: Cache docker image
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: /tmp/webapp.tar path: /tmp/webapp.tar
key: ${{ github.run_id }}-webapp-cache key: ${{ github.run_id }}-webapp-cache
@ -62,7 +67,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v4.0.3
with:
node-version-file: 'webapp/.nvmrc'
- name: webapp | Lint - name: webapp | Lint
run: cd webapp && yarn && yarn run lint run: cd webapp && yarn && yarn run lint
@ -76,10 +86,10 @@ jobs:
checks: write checks: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.1.7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.7
- name: Restore webapp cache - name: Restore webapp cache
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.0.2 uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4.0.2
with: with:
path: /tmp/webapp.tar path: /tmp/webapp.tar
key: ${{ github.run_id }}-webapp-cache key: ${{ github.run_id }}-webapp-cache

View File

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.actor != 'dependabot[bot]' }} if: ${{ github.actor != 'dependabot[bot]' }}
steps: steps:
- uses: amannn/action-semantic-pull-request@e7d011b07ef37e089bea6539210f6a0d360d8af9 # v5.5.3 - uses: amannn/action-semantic-pull-request@069817c298f23fab00a8f29a2e556a5eac0f6390 # v5.5.3
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
@ -32,6 +32,7 @@ jobs:
webapp webapp
maintenance maintenance
database database
e2e
docu docu
docker docker
release release

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "styleguide"]
path = styleguide
url = https://github.com/Human-Connection/Nitro-Styleguide.git
[submodule "deployment/configurations/stage.ocelot.social"] [submodule "deployment/configurations/stage.ocelot.social"]
path = deployment/configurations/stage.ocelot.social path = deployment/configurations/stage.ocelot.social
url = git@github.com:Ocelot-Social-Community/stage.ocelot.social.git url = git@github.com:Ocelot-Social-Community/stage.ocelot.social.git

2
.nvmrc
View File

@ -1 +1 @@
v24.2.0 v25.3.0

View File

@ -1 +0,0 @@
nodejs 20.12.1

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ SMTP_PASSWORD=
SMTP_SECURE="false" # true for 465, false for other ports SMTP_SECURE="false" # true for 465, false for other ports
SMTP_DKIM_DOMAINNAME= SMTP_DKIM_DOMAINNAME=
SMTP_DKIM_KEYSELECTOR= SMTP_DKIM_KEYSELECTOR=
SMTP_DKIM_PRIVATKEY= SMTP_DKIM_PRIVATEKEY=
# E-Mail settings for our 'docker compose up mailserver' # E-Mail settings for our 'docker compose up mailserver'
# SMTP_HOST=localhost # SMTP_HOST=localhost
# SMTP_PORT=1025 # SMTP_PORT=1025
@ -48,3 +48,4 @@ IMAGOR_SECRET=mysecret
CATEGORIES_ACTIVE=false CATEGORIES_ACTIVE=false
MAX_PINNED_POSTS=1 MAX_PINNED_POSTS=1
MAX_GROUP_PINNED_POSTS=1

View File

@ -19,7 +19,7 @@ SMTP_PASSWORD=
SMTP_SECURE="false" # true for 465, false for other ports SMTP_SECURE="false" # true for 465, false for other ports
SMTP_DKIM_DOMAINNAME= SMTP_DKIM_DOMAINNAME=
SMTP_DKIM_KEYSELECTOR= SMTP_DKIM_KEYSELECTOR=
SMTP_DKIM_PRIVATKEY= SMTP_DKIM_PRIVATEKEY=
JWT_SECRET="b/&&7b78BF&fv/Vd" JWT_SECRET="b/&&7b78BF&fv/Vd"
JWT_EXPIRES="2y" JWT_EXPIRES="2y"
@ -40,3 +40,4 @@ IMAGOR_SECRET=mysecret
CATEGORIES_ACTIVE=false CATEGORIES_ACTIVE=false
MAX_PINNED_POSTS=1 MAX_PINNED_POSTS=1
MAX_GROUP_PINNED_POSTS=1

View File

@ -14,7 +14,6 @@ module.exports = {
'plugin:import/recommended', 'plugin:import/recommended',
'plugin:import/typescript', 'plugin:import/typescript',
'plugin:promise/recommended', 'plugin:promise/recommended',
'plugin:security/recommended-legacy',
'plugin:@eslint-community/eslint-comments/recommended', 'plugin:@eslint-community/eslint-comments/recommended',
'prettier', 'prettier',
], ],
@ -175,6 +174,10 @@ module.exports = {
'@eslint-community/eslint-comments/require-description': 'off', '@eslint-community/eslint-comments/require-description': 'off',
}, },
overrides: [ overrides: [
{
files: ['*.js', '*.cjs', '*.ts', '*.tsx'],
extends: ['plugin:security/recommended-legacy'],
},
// only for ts files // only for ts files
{ {
files: ['*.ts', '*.tsx'], files: ['*.ts', '*.tsx'],
@ -228,5 +231,33 @@ module.exports = {
files: ['*.json', '*.json5', '*.jsonc'], files: ['*.json', '*.json5', '*.jsonc'],
parser: 'jsonc-eslint-parser', parser: 'jsonc-eslint-parser',
}, },
{
files: ['*.graphql', '*.gql'],
parser: '@graphql-eslint/eslint-plugin',
plugins: ['@graphql-eslint'],
extends: ['plugin:@graphql-eslint/schema-recommended'],
rules: {
'@graphql-eslint/description-style': ['error', { style: 'inline' }],
'@graphql-eslint/require-description': 'off',
'@graphql-eslint/naming-convention': 'off',
'@graphql-eslint/strict-id-in-types': 'off',
'@graphql-eslint/no-typename-prefix': 'off',
// incompatible: `depends on a GraphQL validation rule "XXX" but it's not available in the "graphql" version you are using. Skipping…`
'@graphql-eslint/known-directives': 'off',
'@graphql-eslint/known-argument-names': 'off',
'@graphql-eslint/known-type-names': 'off',
'@graphql-eslint/lone-schema-definition': 'off',
'@graphql-eslint/provided-required-arguments': 'off',
'@graphql-eslint/unique-directive-names': 'off',
'@graphql-eslint/unique-directive-names-per-location': 'off',
'@graphql-eslint/unique-field-definition-names': 'off',
'@graphql-eslint/unique-operation-types': 'off',
'@graphql-eslint/unique-type-names': 'off',
},
parserOptions: {
schema: './src/graphql/types/**/*.gql',
assumeValid: true,
},
},
], ],
} }

View File

@ -1 +1 @@
v24.2.0 v25.3.0

View File

@ -1 +0,0 @@
nodejs 24.2.0

View File

@ -1,4 +1,4 @@
FROM node:24.8.0-alpine AS base FROM node:25.5.0-alpine AS base
LABEL org.label-schema.name="ocelot.social:backend" LABEL org.label-schema.name="ocelot.social:backend"
LABEL org.label-schema.description="Backend of the Social Network Software ocelot.social" LABEL org.label-schema.description="Backend of the Social Network Software ocelot.social"
LABEL org.label-schema.usage="https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/README.md" LABEL org.label-schema.usage="https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/README.md"

View File

@ -19,18 +19,16 @@ Wait a little until your backend is up and running at [http://localhost:4000/](h
## Installation without Docker ## Installation without Docker
For the local installation you need a recent version of For the local installation you need a recent version of
[Node](https://nodejs.org/en/) (&gt;= `v16.19.0`). We are using [Node](https://nodejs.org/en/). We are using
`v24.2.0` and therefore we recommend to use the same version `v25.3.0` and therefore we recommend to use the same version. You can use the
([see](https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4082)
some known problems with more recent node versions). You can use the
[node version manager](https://github.com/nvm-sh/nvm) `nvm` to switch [node version manager](https://github.com/nvm-sh/nvm) `nvm` to switch
between different local Node versions: between different local Node versions:
```sh ```sh
# install Node # install Node using '.nvmrc' file
$ cd backend $ cd backend
$ nvm install v24.2.0 $ nvm install
$ nvm use v24.2.0 $ nvm use
``` ```
Install node dependencies with [yarn](https://yarnpkg.com/en/): Install node dependencies with [yarn](https://yarnpkg.com/en/):

View File

@ -1,6 +1,6 @@
{ {
"name": "ocelot-social-backend", "name": "ocelot-social-backend",
"version": "3.12.2", "version": "3.13.1",
"description": "GraphQL Backend for ocelot.social", "description": "GraphQL Backend for ocelot.social",
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social", "repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
"author": "ocelot.social Community", "author": "ocelot.social Community",
@ -12,7 +12,7 @@
"build": "tsc && tsc-alias && ./scripts/build.copy.files.sh", "build": "tsc && tsc-alias && ./scripts/build.copy.files.sh",
"dev": "nodemon --exec ts-node --require tsconfig-paths/register src/index.ts -e js,ts,gql", "dev": "nodemon --exec ts-node --require tsconfig-paths/register src/index.ts -e js,ts,gql",
"dev:debug": "nodemon --exec node --inspect=0.0.0.0:9229 build/src/index.js -e js,ts,gql", "dev:debug": "nodemon --exec node --inspect=0.0.0.0:9229 build/src/index.js -e js,ts,gql",
"lint": "eslint --max-warnings=0 --report-unused-disable-directives --ext .js,.ts,.cjs,.json,.json5,.jsonc .", "lint": "eslint --max-warnings=0 --report-unused-disable-directives --ext .js,.ts,.cjs,.json,.json5,.jsonc,.graphql,.gql .",
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles", "test": "cross-env NODE_ENV=test NODE_OPTIONS=--max-old-space-size=8192 jest --runInBand --coverage --forceExit --detectOpenHandles",
"db:reset": "ts-node --require tsconfig-paths/register src/db/reset.ts", "db:reset": "ts-node --require tsconfig-paths/register src/db/reset.ts",
"db:reset:withmigrations": "ts-node --require tsconfig-paths/register src/db/reset-with-migrations.ts", "db:reset:withmigrations": "ts-node --require tsconfig-paths/register src/db/reset-with-migrations.ts",
@ -23,26 +23,29 @@
"db:data:categories": "ts-node --require tsconfig-paths/register src/db/categories.ts", "db:data:categories": "ts-node --require tsconfig-paths/register src/db/categories.ts",
"db:migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --store ./src/db/migrate/store.ts", "db:migrate": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --store ./src/db/migrate/store.ts",
"db:migrate:create": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create", "db:migrate:create": "migrate --compiler 'ts:./src/db/compiler.ts' --migrations-dir ./src/db/migrations --template-file ./src/db/migrate/template.ts --date-format 'yyyymmddHHmmss' create",
"db:func:disable:notifications": "ts-node --require tsconfig-paths/register src/db/disable-notifications.ts",
"prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js", "prod:migrate": "migrate --migrations-dir ./build/src/db/migrations --store ./build/src/db/migrate/store.js",
"prod:db:data:branding": "node build/src/db/data-branding.js", "prod:db:data:branding": "node build/src/db/data-branding.js",
"prod:db:data:categories": "node build/src/db/categories.js" "prod:db:data:categories": "node build/src/db/categories.js",
"prod:db:data:admin": "node build/src/db/admin.js",
"prod:db:func:disable:notifications": "node build/src/db/disable-notifications.js"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.888.0", "@aws-sdk/client-s3": "^3.980.0",
"@aws-sdk/lib-storage": "^3.888.0", "@aws-sdk/lib-storage": "^3.980.0",
"@sentry/node": "^5.15.4", "@sentry/node": "^5.30.0",
"@types/mime-types": "^3.0.1", "@types/mime-types": "^3.0.1",
"apollo-server": "~2.14.2", "apollo-server": "~2.14.2",
"apollo-server-express": "^2.14.2", "apollo-server-express": "^2.14.2",
"bcryptjs": "~3.0.2", "bcryptjs": "~3.0.3",
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
"cheerio": "~1.1.2", "cheerio": "~1.2.0",
"cross-env": "~10.0.0", "cross-env": "~10.1.0",
"dotenv": "~17.0.1", "dotenv": "~17.0.1",
"email-templates": "^12.0.3", "email-templates": "^12.0.3",
"express": "^5.1.0", "express": "^4.22.1",
"graphql": "^14.6.0", "graphql": "^14.6.0",
"graphql-middleware": "~4.0.2", "graphql-middleware": "~6.1.35",
"graphql-middleware-sentry": "^3.2.1", "graphql-middleware-sentry": "^3.2.1",
"graphql-redis-subscriptions": "^2.7.0", "graphql-redis-subscriptions": "^2.7.0",
"graphql-shield": "~7.2.2", "graphql-shield": "~7.2.2",
@ -50,55 +53,56 @@
"graphql-tag": "~2.10.3", "graphql-tag": "~2.10.3",
"graphql-upload": "^13.0.0", "graphql-upload": "^13.0.0",
"helmet": "~8.1.0", "helmet": "~8.1.0",
"ioredis": "^5.7.0", "ioredis": "^5.9.2",
"jsonwebtoken": "~8.5.1", "jsonwebtoken": "~8.5.1",
"languagedetect": "^2.0.0", "languagedetect": "^2.0.0",
"linkify-html": "^4.3.2", "linkify-html": "^4.3.2",
"linkifyjs": "^4.3.2", "linkifyjs": "^4.3.2",
"lodash": "~4.17.21", "lodash": "~4.17.23",
"merge-graphql-schemas": "^1.7.8", "merge-graphql-schemas": "^1.7.8",
"metascraper": "^5.49.2", "metascraper": "^5.49.19",
"metascraper-author": "^5.49.2", "metascraper-author": "^5.49.19",
"metascraper-date": "^5.49.2", "metascraper-date": "^5.49.19",
"metascraper-description": "^5.49.2", "metascraper-description": "^5.49.19",
"metascraper-image": "^5.49.2", "metascraper-image": "^5.49.19",
"metascraper-lang": "^5.49.2", "metascraper-lang": "^5.49.19",
"metascraper-lang-detector": "^4.10.2", "metascraper-lang-detector": "^4.10.2",
"metascraper-logo": "^5.49.2", "metascraper-logo": "^5.49.19",
"metascraper-publisher": "^5.49.2", "metascraper-publisher": "^5.49.19",
"metascraper-soundcloud": "^5.34.4", "metascraper-soundcloud": "^5.34.4",
"metascraper-title": "^5.49.2", "metascraper-title": "^5.49.19",
"metascraper-url": "^5.49.2", "metascraper-url": "^5.49.19",
"metascraper-video": "^5.49.2", "metascraper-video": "^5.49.19",
"metascraper-youtube": "^5.49.2", "metascraper-youtube": "^5.49.20",
"migrate": "^2.1.0", "migrate": "^2.1.0",
"mime-types": "^3.0.1", "mime-types": "^3.0.2",
"minimatch": "^10.0.3", "minimatch": "^10.1.1",
"mustache": "^4.2.0", "mustache": "^4.2.0",
"neo4j-driver": "^4.4.11", "neo4j-driver": "^4.4.11",
"neo4j-graphql-js": "^2.11.5", "neo4j-graphql-js": "2.11.5",
"neode": "^0.4.9", "neode": "^0.4.9",
"node-fetch": "^2.7.0", "node-fetch": "^2.7.0",
"nodemailer": "^7.0.6", "nodemailer": "^7.0.12",
"nodemailer-html-to-text": "^3.2.0", "nodemailer-html-to-text": "^3.2.0",
"preview-email": "^3.1.0", "preview-email": "^3.1.1",
"pug": "^3.0.3", "pug": "^3.0.3",
"sanitize-html": "~2.17.0", "sanitize-html": "~2.17.0",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"trunc-html": "~1.1.2", "trunc-html": "~1.1.2",
"tslog": "^4.9.3", "tslog": "^4.10.2",
"uuid": "~9.0.1", "uuid": "~9.0.1",
"validator": "^13.15.15", "validator": "^13.15.26",
"xregexp": "^5.1.2" "xregexp": "^5.1.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.5.0", "@eslint-community/eslint-plugin-eslint-comments": "^4.6.0",
"@faker-js/faker": "9.9.0", "@faker-js/faker": "9.9.0",
"@graphql-eslint/eslint-plugin": "^3.20.1",
"@types/email-templates": "^10.0.4", "@types/email-templates": "^10.0.4",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/jsonwebtoken": "~8.5.1", "@types/jsonwebtoken": "~8.5.1",
"@types/lodash": "^4.17.20", "@types/lodash": "^4.17.23",
"@types/node": "^24.4.0", "@types/node": "^25.1.0",
"@types/request": "^2.48.13", "@types/request": "^2.48.13",
"@types/slug": "^5.0.9", "@types/slug": "^5.0.9",
"@types/uuid": "~9.0.1", "@types/uuid": "~9.0.1",
@ -110,19 +114,19 @@
"eslint-config-standard": "^17.1.0", "eslint-config-standard": "^17.1.0",
"eslint-import-resolver-typescript": "^4.4.4", "eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.0.1", "eslint-plugin-jest": "^29.12.1",
"eslint-plugin-jsonc": "^2.20.1", "eslint-plugin-jsonc": "^2.21.0",
"eslint-plugin-n": "^17.21.3", "eslint-plugin-n": "^17.23.2",
"eslint-plugin-no-catch-all": "^1.1.0", "eslint-plugin-no-catch-all": "^1.1.0",
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-promise": "^7.2.1", "eslint-plugin-promise": "^7.2.1",
"eslint-plugin-security": "^3.0.1", "eslint-plugin-security": "^3.0.1",
"jest": "^30.1.3", "jest": "^30.2.0",
"nodemon": "~3.1.10", "nodemon": "~3.1.11",
"prettier": "^3.6.2", "prettier": "^3.8.1",
"require-json5": "^1.3.0", "require-json5": "^1.3.0",
"rosie": "^2.1.1", "rosie": "^2.1.1",
"ts-jest": "^29.4.1", "ts-jest": "^29.4.6",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsc-alias": "^1.8.16", "tsc-alias": "^1.8.16",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
@ -133,7 +137,9 @@
"**/graphql-upload": "^11.0.0", "**/graphql-upload": "^11.0.0",
"**/strip-ansi": "6.0.1", "**/strip-ansi": "6.0.1",
"**/string-width": "4.2.0", "**/string-width": "4.2.0",
"**/wrap-ansi": "7.0.0" "**/wrap-ansi": "7.0.0",
"**/jwa": "^2.0.1",
"**/@types/express": "4.17.25"
}, },
"engines": { "engines": {
"node": ">=20.12.1" "node": ">=20.12.1"

View File

@ -48,7 +48,7 @@ const SMTP_PASSWORD = env.SMTP_PASSWORD
const SMTP_DKIM_DOMAINNAME = env.SMTP_DKIM_DOMAINNAME const SMTP_DKIM_DOMAINNAME = env.SMTP_DKIM_DOMAINNAME
const SMTP_DKIM_KEYSELECTOR = env.SMTP_DKIM_KEYSELECTOR const SMTP_DKIM_KEYSELECTOR = env.SMTP_DKIM_KEYSELECTOR
// PEM format = https://docs.progress.com/bundle/datadirect-hybrid-data-pipeline-installation-46/page/PEM-file-format.html // PEM format = https://docs.progress.com/bundle/datadirect-hybrid-data-pipeline-installation-46/page/PEM-file-format.html
const SMTP_DKIM_PRIVATKEY = env.SMTP_DKIM_PRIVATKEY?.replace(/\\n/g, '\n') // replace all "\n" in .env string by real line break const SMTP_DKIM_PRIVATEKEY = env.SMTP_DKIM_PRIVATEKEY?.replace(/\\n/g, '\n') // replace all "\n" in .env string by real line break
const SMTP_MAX_CONNECTIONS = (env.SMTP_MAX_CONNECTIONS && parseInt(env.SMTP_MAX_CONNECTIONS)) || 5 const SMTP_MAX_CONNECTIONS = (env.SMTP_MAX_CONNECTIONS && parseInt(env.SMTP_MAX_CONNECTIONS)) || 5
const SMTP_MAX_MESSAGES = (env.SMTP_MAX_MESSAGES && parseInt(env.SMTP_MAX_MESSAGES)) || 100 const SMTP_MAX_MESSAGES = (env.SMTP_MAX_MESSAGES && parseInt(env.SMTP_MAX_MESSAGES)) || 100
@ -67,11 +67,11 @@ if (SMTP_USERNAME && SMTP_PASSWORD) {
pass: SMTP_PASSWORD, pass: SMTP_PASSWORD,
} }
} }
if (SMTP_DKIM_DOMAINNAME && SMTP_DKIM_KEYSELECTOR && SMTP_DKIM_PRIVATKEY) { if (SMTP_DKIM_DOMAINNAME && SMTP_DKIM_KEYSELECTOR && SMTP_DKIM_PRIVATEKEY) {
nodemailerTransportOptions.dkim = { nodemailerTransportOptions.dkim = {
domainName: SMTP_DKIM_DOMAINNAME, domainName: SMTP_DKIM_DOMAINNAME,
keySelector: SMTP_DKIM_KEYSELECTOR, keySelector: SMTP_DKIM_KEYSELECTOR,
privateKey: SMTP_DKIM_PRIVATKEY, privateKey: SMTP_DKIM_PRIVATEKEY,
} }
} }
@ -138,6 +138,9 @@ const options = {
MAX_PINNED_POSTS: Number.isNaN(Number(process.env.MAX_PINNED_POSTS)) MAX_PINNED_POSTS: Number.isNaN(Number(process.env.MAX_PINNED_POSTS))
? 1 ? 1
: Number(process.env.MAX_PINNED_POSTS), : Number(process.env.MAX_PINNED_POSTS),
MAX_GROUP_PINNED_POSTS: Number.isNaN(Number(process.env.MAX_GROUP_PINNED_POSTS))
? 1
: Number(process.env.MAX_GROUP_PINNED_POSTS),
} }
const language = { const language = {

View File

@ -0,0 +1,61 @@
import databaseContext from '@context/database'
const run = async () => {
const args = process.argv.slice(2)
if (args.length !== 1) {
// eslint-disable-next-line no-console
console.error('Usage: yarn run db:func:disable-notifications <email>')
// eslint-disable-next-line n/no-process-exit
process.exit(1)
}
const email = args[0]
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
// eslint-disable-next-line no-console
console.error('Error: Invalid email address format')
// eslint-disable-next-line n/no-process-exit
process.exit(1)
}
const { write } = databaseContext()
const result = (
await write({
query: `
MATCH (:EmailAddress {email: $email})-[:BELONGS_TO]->(user:User)
SET user.emailNotificationsFollowingUsers = false
SET user.emailNotificationsPostInGroup = false
SET user.emailNotificationsCommentOnObservedPost = false
SET user.emailNotificationsMention = false
SET user.emailNotificationsChatMessage = false
SET user.emailNotificationsGroupMemberJoined = false
SET user.emailNotificationsGroupMemberLeft = false
SET user.emailNotificationsGroupMemberRemoved = false
SET user.emailNotificationsGroupMemberRoleChanged = false
RETURN toString(count(user)) as count
`,
variables: {
email,
},
})
).records[0].get('count') as string
if (result !== '1') {
// eslint-disable-next-line no-console
console.error(`User with email address ${email} not found`)
// eslint-disable-next-line n/no-process-exit
process.exit(1)
}
// eslint-disable-next-line no-console
console.log(`Notifications for User with email address ${email} disabled`)
// eslint-disable-next-line n/no-process-exit
process.exit(0)
}
void (async function () {
await run()
})()

View File

@ -18,7 +18,7 @@ export default {
}, },
title: { type: 'string', disallow: [null], min: 3 }, title: { type: 'string', disallow: [null], min: 3 },
slug: { type: 'string', allow: [null], unique: 'true' }, slug: { type: 'string', allow: [null], unique: 'true' },
content: { type: 'string', disallow: [null], min: 3 }, content: { type: 'string', disallow: [null], required: true, min: 3 },
contentExcerpt: { type: 'string', allow: [null] }, contentExcerpt: { type: 'string', allow: [null] },
deleted: { type: 'boolean', default: false }, deleted: { type: 'boolean', default: false },
disabled: { type: 'boolean', default: false }, disabled: { type: 'boolean', default: false },
@ -58,6 +58,7 @@ export default {
}, },
}, },
pinned: { type: 'boolean', default: null, valid: [null, true] }, pinned: { type: 'boolean', default: null, valid: [null, true] },
groupPinned: { type: 'boolean', default: null, valid: [null, true] },
postType: { type: 'string', default: 'Article', valid: ['Article', 'Event'] }, postType: { type: 'string', default: 'Article', valid: ['Article', 'Event'] },
observes: { observes: {
type: 'relationship', type: 'relationship',

View File

@ -1,36 +1,49 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable n/no-missing-require */
/* eslint-disable n/global-require */
// NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm // NOTE: We cannot use `fs` here to clean up the code. Cypress breaks on any npm
// module that is not browser-compatible. Node's `fs` module is server-side only // module that is not browser-compatible. Node's `fs` module is server-side only
// eslint-disable-next-line @typescript-eslint/no-explicit-any //
declare let Cypress: any | undefined // We use static imports instead of dynamic require() to ensure compatibility
// with both Node.js and Webpack (used by Cypress cucumber preprocessor).
import Badge from './Badge'
import Category from './Category'
import Comment from './Comment'
import Donations from './Donations'
import EmailAddress from './EmailAddress'
import File from './File'
import Group from './Group'
import Image from './Image'
import InviteCode from './InviteCode'
import Location from './Location'
import Migration from './Migration'
import Post from './Post'
import Report from './Report'
import SocialMedia from './SocialMedia'
import Tag from './Tag'
import UnverifiedEmailAddress from './UnverifiedEmailAddress'
import User from './User'
import type Neode from 'neode'
// Type assertion needed because TypeScript infers literal types from the model
// objects (e.g., type: 'string' as literal), but Neode expects the broader
// SchemaObject type with PropertyTypes union. The Neode type definitions are
// incomplete/incorrect, so we use double assertion to bypass the check.
export default { export default {
File: typeof Cypress !== 'undefined' ? require('./File') : require('./File').default, Badge,
Image: typeof Cypress !== 'undefined' ? require('./Image') : require('./Image').default, Category,
Badge: typeof Cypress !== 'undefined' ? require('./Badge') : require('./Badge').default, Comment,
User: typeof Cypress !== 'undefined' ? require('./User') : require('./User').default, Donations,
Group: typeof Cypress !== 'undefined' ? require('./Group') : require('./Group').default, EmailAddress,
EmailAddress: File,
typeof Cypress !== 'undefined' ? require('./EmailAddress') : require('./EmailAddress').default, Group,
UnverifiedEmailAddress: Image,
typeof Cypress !== 'undefined' InviteCode,
? require('./UnverifiedEmailAddress') Location,
: require('./UnverifiedEmailAddress').default, Migration,
SocialMedia: Post,
typeof Cypress !== 'undefined' ? require('./SocialMedia') : require('./SocialMedia').default, Report,
Post: typeof Cypress !== 'undefined' ? require('./Post') : require('./Post').default, SocialMedia,
Comment: typeof Cypress !== 'undefined' ? require('./Comment') : require('./Comment').default, Tag,
Category: typeof Cypress !== 'undefined' ? require('./Category') : require('./Category').default, UnverifiedEmailAddress,
Tag: typeof Cypress !== 'undefined' ? require('./Tag') : require('./Tag').default, User,
Location: typeof Cypress !== 'undefined' ? require('./Location') : require('./Location').default, } as unknown as Record<string, Neode.SchemaObject>
Donations:
typeof Cypress !== 'undefined' ? require('./Donations') : require('./Donations').default,
Report: typeof Cypress !== 'undefined' ? require('./Report') : require('./Report').default,
Migration:
typeof Cypress !== 'undefined' ? require('./Migration') : require('./Migration').default,
InviteCode:
typeof Cypress !== 'undefined' ? require('./InviteCode') : require('./InviteCode').default,
}

View File

@ -2,7 +2,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import path from 'node:path' import path from 'node:path'
import Email from 'email-templates' import Email from 'email-templates'
@ -94,8 +93,8 @@ export const sendNotificationMail = async (notification: any): Promise<OriginalM
: notification?.from?.title, : notification?.from?.title,
postUrl: new URL( postUrl: new URL(
notification?.from?.__typename === 'Comment' notification?.from?.__typename === 'Comment'
? `/post/${notification?.from?.post?.id}/${notification?.from?.post?.slug}` ? `/post/${encodeURIComponent(notification?.from?.post?.id)}/${encodeURIComponent(notification?.from?.post?.slug)}`
: `/post/${notification?.from?.id}/${notification?.from?.slug}`, : `/post/${encodeURIComponent(notification?.from?.id)}/${encodeURIComponent(notification?.from?.slug)}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
), ),
postAuthorName: postAuthorName:
@ -106,7 +105,7 @@ export const sendNotificationMail = async (notification: any): Promise<OriginalM
notification?.from?.__typename === 'Comment' notification?.from?.__typename === 'Comment'
? undefined ? undefined
: new URL( : new URL(
`profile/${notification?.from?.author?.id}/${notification?.from?.author?.slug}`, `profile/${encodeURIComponent(notification?.from?.author?.id)}/${encodeURIComponent(notification?.from?.author?.slug)}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
), ),
commenterName: commenterName:
@ -116,14 +115,14 @@ export const sendNotificationMail = async (notification: any): Promise<OriginalM
commenterUrl: commenterUrl:
notification?.from?.__typename === 'Comment' notification?.from?.__typename === 'Comment'
? new URL( ? new URL(
`/profile/${notification?.from?.author?.id}/${notification?.from?.author?.slug}`, `/profile/${encodeURIComponent(notification?.from?.author?.id)}/${encodeURIComponent(notification?.from?.author?.slug)}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
) )
: undefined, : undefined,
commentUrl: commentUrl:
notification?.from?.__typename === 'Comment' notification?.from?.__typename === 'Comment'
? new URL( ? new URL(
`/post/${notification?.from?.post?.id}/${notification?.from?.post?.slug}#commentId-${notification?.from?.id}`, `/post/${encodeURIComponent(notification?.from?.post?.id)}/${encodeURIComponent(notification?.from?.post?.slug)}#commentId-${encodeURIComponent(notification?.from?.id)}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
) )
: undefined, : undefined,
@ -132,7 +131,7 @@ export const sendNotificationMail = async (notification: any): Promise<OriginalM
groupUrl: groupUrl:
notification?.from?.__typename === 'Group' notification?.from?.__typename === 'Group'
? new URL( ? new URL(
`/groups/${notification?.from?.id}/${notification?.from?.slug}`, `/groups/${encodeURIComponent(notification?.from?.id)}/${encodeURIComponent(notification?.from?.slug)}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
) )
: undefined, : undefined,
@ -143,7 +142,7 @@ export const sendNotificationMail = async (notification: any): Promise<OriginalM
groupRelatedUserUrl: groupRelatedUserUrl:
notification?.from?.__typename === 'Group' notification?.from?.__typename === 'Group'
? new URL( ? new URL(
`/profile/${notification?.relatedUser?.id}/${notification?.relatedUser?.slug}`, `/profile/${encodeURIComponent(notification?.relatedUser?.id)}/${encodeURIComponent(notification?.relatedUser?.slug)}`,
CONFIG.CLIENT_URI, CONFIG.CLIENT_URI,
) )
: undefined, : undefined,
@ -177,7 +176,10 @@ export const sendChatMessageMail = async (
locale: recipientUser.locale, locale: recipientUser.locale,
name: recipientUser.name, name: recipientUser.name,
chattingUser: senderUser.name, chattingUser: senderUser.name,
chattingUserUrl: new URL(`/profile/${senderUser.id}/${senderUser.slug}`, CONFIG.CLIENT_URI), chattingUserUrl: new URL(
`/profile/${encodeURIComponent(senderUser.id)}/${encodeURIComponent(senderUser.slug)}`,
CONFIG.CLIENT_URI,
),
chatUrl: new URL('/chat', CONFIG.CLIENT_URI), chatUrl: new URL('/chat', CONFIG.CLIENT_URI),
}, },
}) })

View File

@ -3,10 +3,14 @@ import gql from 'graphql-tag'
export const ChangeGroupMemberRole = gql` export const ChangeGroupMemberRole = gql`
mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) { mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) {
ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) { ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) {
id user {
name id
slug name
myRoleInGroup slug
}
membership {
role
}
} }
} }
` `

View File

@ -8,6 +8,8 @@ export const CreateComment = gql`
author { author {
name name
} }
isPostObservedByMe
postObservingUsersCount
} }
} }
` `

View File

@ -45,6 +45,7 @@ export const CreatePost = gql`
} }
isObservedByMe isObservedByMe
observingUsersCount observingUsersCount
language
} }
} }
` `

View File

@ -0,0 +1,14 @@
import gql from 'graphql-tag'
export const CreateSocialMedia = gql`
mutation ($url: String!) {
CreateSocialMedia(url: $url) {
id
url
url
ownedBy {
name
}
}
}
`

View File

@ -3,10 +3,14 @@ import gql from 'graphql-tag'
export const GroupMembers = gql` export const GroupMembers = gql`
query GroupMembers($id: ID!) { query GroupMembers($id: ID!) {
GroupMembers(id: $id) { GroupMembers(id: $id) {
id user {
name id
slug name
myRoleInGroup slug
}
membership {
role
}
} }
} }
` `

View File

@ -3,10 +3,14 @@ import gql from 'graphql-tag'
export const JoinGroup = gql` export const JoinGroup = gql`
mutation ($groupId: ID!, $userId: ID!) { mutation ($groupId: ID!, $userId: ID!) {
JoinGroup(groupId: $groupId, userId: $userId) { JoinGroup(groupId: $groupId, userId: $userId) {
id user {
name id
slug name
myRoleInGroup slug
}
membership {
role
}
} }
} }
` `

View File

@ -3,10 +3,14 @@ import gql from 'graphql-tag'
export const LeaveGroup = gql` export const LeaveGroup = gql`
mutation ($groupId: ID!, $userId: ID!) { mutation ($groupId: ID!, $userId: ID!) {
LeaveGroup(groupId: $groupId, userId: $userId) { LeaveGroup(groupId: $groupId, userId: $userId) {
id user {
name id
slug name
myRoleInGroup slug
}
membership {
role
}
} }
} }
` `

View File

@ -6,10 +6,34 @@ export const Post = gql`
id id
title title
content content
contentExcerpt
eventStart eventStart
pinned pinned
createdAt createdAt
pinnedAt pinnedAt
isObservedByMe
observingUsersCount
clickedCount
emotionsCount
emotions {
emotion
User {
id
}
}
author {
id
name
}
shoutedBy {
id
}
tags {
id
}
comments {
content
}
} }
} }
` `

View File

@ -3,10 +3,14 @@ import gql from 'graphql-tag'
export const RemoveUserFromGroup = gql` export const RemoveUserFromGroup = gql`
mutation ($groupId: ID!, $userId: ID!) { mutation ($groupId: ID!, $userId: ID!) {
RemoveUserFromGroup(groupId: $groupId, userId: $userId) { RemoveUserFromGroup(groupId: $groupId, userId: $userId) {
id user {
name id
slug name
myRoleInGroup slug
}
membership {
role
}
} }
} }
` `

View File

@ -8,6 +8,8 @@ export const SignupVerification = gql`
$slug: String $slug: String
$nonce: String! $nonce: String!
$termsAndConditionsAgreedVersion: String! $termsAndConditionsAgreedVersion: String!
$about: String
$locale: String
) { ) {
SignupVerification( SignupVerification(
email: $email email: $email
@ -16,9 +18,13 @@ export const SignupVerification = gql`
slug: $slug slug: $slug
nonce: $nonce nonce: $nonce
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
about: $about
locale: $locale
) { ) {
id id
slug slug
termsAndConditionsAgreedVersion
termsAndConditionsAgreedAt
} }
} }
` `

View File

@ -1,10 +1,44 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const UpdatePost = gql` export const UpdatePost = gql`
mutation ($id: ID!, $title: String!, $postContent: String!, $categoryIds: [ID]!) { mutation (
UpdatePost(id: $id, content: $postContent, title: $title, categoryIds: $categoryIds) { $id: ID!
$title: String!
$content: String!
$image: ImageInput
$categoryIds: [ID]
$postType: PostType
$eventInput: _EventInput
) {
UpdatePost(
id: $id
title: $title
content: $content
image: $image
categoryIds: $categoryIds
postType: $postType
eventInput: $eventInput
) {
id
title title
content content
author {
name
slug
}
createdAt
updatedAt
categories {
id
}
postType
eventStart
eventLocationName
eventVenue
eventLocation {
lng
lat
}
} }
} }
` `

View File

@ -1,9 +1,38 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const UpdateUser = gql` export const UpdateUser = gql`
mutation ($id: ID!, $name: String) { mutation (
UpdateUser(id: $id, name: $name) { $id: ID!
$name: String
$termsAndConditionsAgreedVersion: String
$locationName: String # empty string '' sets it to null
$emailNotificationSettings: [EmailNotificationSettingsInput]
) {
UpdateUser(
id: $id
name: $name
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
locationName: $locationName
emailNotificationSettings: $emailNotificationSettings
) {
id
name name
termsAndConditionsAgreedVersion
termsAndConditionsAgreedAt
locationName
location {
name
nameDE
nameEN
nameRU
}
emailNotificationSettings {
type
settings {
name
value
}
}
} }
} }
` `

View File

@ -1,9 +1,165 @@
import gql from 'graphql-tag' import gql from 'graphql-tag'
export const User = gql` export const User = gql`
query ($name: String) { query ($id: ID, $name: String, $email: String) {
User(name: $name) { User(id: $id, name: $name, email: $email) {
email id
name
badgeTrophiesCount
badgeTrophies {
id
}
badgeVerification {
id
isDefault
}
badgeTrophiesSelected {
id
isDefault
}
followedBy {
id
}
followedByCurrentUser
following {
name
slug
about
avatar {
url
}
comments {
content
contentExcerpt
}
contributions {
title
slug
image {
url
}
content
contentExcerpt
}
}
isMuted
isBlocked
location {
distanceToMe
}
activeCategories
}
}
`
export const UserEmailNotificationSettings = gql`
query ($id: ID, $name: String, $email: String) {
User(id: $id, name: $name, email: $email) {
id
name
badgeTrophiesCount
badgeTrophies {
id
}
badgeVerification {
id
isDefault
}
badgeTrophiesSelected {
id
isDefault
}
followedBy {
id
}
followedByCurrentUser
following {
name
slug
about
avatar {
url
}
comments {
content
contentExcerpt
}
contributions {
title
slug
image {
url
}
content
contentExcerpt
}
}
isMuted
isBlocked
location {
distanceToMe
}
emailNotificationSettings {
type
settings {
name
value
}
}
activeCategories
}
}
`
export const UserEmail = gql`
query ($id: ID, $name: String, $email: String) {
User(id: $id, name: $name, email: $email) {
id
name
email
badgeTrophiesCount
badgeTrophies {
id
}
badgeVerification {
id
isDefault
}
badgeTrophiesSelected {
id
isDefault
}
followedBy {
id
}
followedByCurrentUser
following {
name
slug
about
avatar {
url
}
comments {
content
contentExcerpt
}
contributions {
title
slug
image {
url
}
content
contentExcerpt
}
}
isMuted
isBlocked
location {
distanceToMe
}
activeCategories
} }
} }
` `

View File

@ -0,0 +1,7 @@
import gql from 'graphql-tag'
export const availableRoles = gql`
query {
availableRoles
}
`

View File

@ -3,6 +3,15 @@ import gql from 'graphql-tag'
export const currentUser = gql` export const currentUser = gql`
query currentUser { query currentUser {
currentUser { currentUser {
id
slug
name
avatar {
url
}
email
role
activeCategories
following { following {
name name
} }

View File

@ -3,6 +3,7 @@ import gql from 'graphql-tag'
export const followUser = gql` export const followUser = gql`
mutation ($id: ID!) { mutation ($id: ID!) {
followUser(id: $id) { followUser(id: $id) {
id
name name
followedBy { followedBy {
id id

View File

@ -3,6 +3,7 @@ import gql from 'graphql-tag'
export const markAllAsRead = gql` export const markAllAsRead = gql`
mutation { mutation {
markAllAsRead { markAllAsRead {
id
from { from {
__typename __typename
... on Post { ... on Post {

View File

@ -3,14 +3,23 @@ import gql from 'graphql-tag'
export const notifications = gql` export const notifications = gql`
query ($read: Boolean, $orderBy: NotificationOrdering) { query ($read: Boolean, $orderBy: NotificationOrdering) {
notifications(read: $read, orderBy: $orderBy) { notifications(read: $read, orderBy: $orderBy) {
reason
relatedUser {
id
}
from { from {
__typename __typename
... on Post { ... on Post {
id
content content
} }
... on Comment { ... on Comment {
id
content content
} }
... on Group {
id
}
} }
read read
createdAt createdAt

View File

@ -0,0 +1,25 @@
import gql from 'graphql-tag'
export const pinGroupPost = gql`
mutation ($id: ID!) {
pinGroupPost(id: $id) {
id
title
content
author {
name
slug
}
pinnedBy {
id
name
role
}
createdAt
updatedAt
pinnedAt
pinned
groupPinned
}
}
`

View File

@ -11,6 +11,7 @@ export const profilePagePosts = gql`
id id
title title
content content
groupPinned
} }
} }
` `

View File

@ -5,6 +5,7 @@ export const searchPosts = gql`
searchPosts(query: $query, firstPosts: $firstPosts, postsOffset: $postsOffset) { searchPosts(query: $query, firstPosts: $firstPosts, postsOffset: $postsOffset) {
postCount postCount
posts { posts {
__typename
id id
title title
content content

View File

@ -0,0 +1,25 @@
import gql from 'graphql-tag'
export const unpinGroupPost = gql`
mutation ($id: ID!) {
unpinGroupPost(id: $id) {
id
title
content
author {
name
slug
}
pinnedBy {
id
name
role
}
createdAt
updatedAt
pinned
pinnedAt
groupPinned
}
}
`

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { GraphQLUpload } from 'graphql-upload' import { GraphQLUpload } from 'graphql-upload'
export default { export default {

View File

@ -130,10 +130,13 @@ export const attachments = (config: S3Config) => {
const { upload } = fileInput const { upload } = fileInput
if (!upload) throw new UserInputError('Cannot find attachment for given resource') if (!upload) throw new UserInputError('Cannot find attachment for given resource')
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const uploadFile = await upload const uploadFile = await upload
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
const { name: fileName, ext } = path.parse(uploadFile.filename) const { name: fileName, ext } = path.parse(uploadFile.filename)
const uniqueFilename = `${uuid()}-${slug(fileName)}${ext}` const uniqueFilename = `${uuid()}-${slug(fileName)}${ext}`
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const url = await s3.uploadFile({ const url = await s3.uploadFile({
...uploadFile, ...uploadFile,
uniqueFilename, uniqueFilename,

View File

@ -1,14 +1,13 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import gql from 'graphql-tag'
import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges' import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import { revokeBadge } from '@graphql/queries/revokeBadge' import { revokeBadge } from '@graphql/queries/revokeBadge'
import { rewardTrophyBadge } from '@graphql/queries/rewardTrophyBadge' import { rewardTrophyBadge } from '@graphql/queries/rewardTrophyBadge'
import { setTrophyBadgeSelected } from '@graphql/queries/setTrophyBadgeSelected' import { setTrophyBadgeSelected } from '@graphql/queries/setTrophyBadgeSelected'
import { setVerificationBadge } from '@graphql/queries/setVerificationBadge' import { setVerificationBadge } from '@graphql/queries/setVerificationBadge'
import { User } from '@graphql/queries/User'
import type { ApolloTestSetup } from '@root/test/helpers' import type { ApolloTestSetup } from '@root/test/helpers'
import { createApolloTestSetup } from '@root/test/helpers' import { createApolloTestSetup } from '@root/test/helpers'
import type { Context } from '@src/context' import type { Context } from '@src/context'
@ -800,24 +799,6 @@ describe('Badges', () => {
describe('check test setup', () => { describe('check test setup', () => {
it('user has one badge and has it selected', async () => { it('user has one badge and has it selected', async () => {
authenticatedUser = await regularUser.toJson() authenticatedUser = await regularUser.toJson()
const userQuery = gql`
{
User(id: "regular-user-id") {
badgeTrophiesCount
badgeTrophies {
id
}
badgeVerification {
id
isDefault
}
badgeTrophiesSelected {
id
isDefault
}
}
}
`
const expected = { const expected = {
data: { data: {
User: [ User: [
@ -871,7 +852,9 @@ describe('Badges', () => {
}, },
errors: undefined, errors: undefined,
} }
await expect(query({ query: userQuery })).resolves.toMatchObject(expected) await expect(
query({ query: User, variables: { id: 'regular-user-id' } }),
).resolves.toMatchObject(expected)
}) })
}) })

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'

View File

@ -2,59 +2,39 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import { getDriver, getNeode } from '@db/neo4j'
import { followUser } from '@graphql/queries/followUser' import { followUser } from '@graphql/queries/followUser'
import { unfollowUser } from '@graphql/queries/unfollowUser' import { unfollowUser } from '@graphql/queries/unfollowUser'
import createServer from '@src/server' import { User } from '@graphql/queries/User'
import { createApolloTestSetup } from '@root/test/helpers'
import type { ApolloTestSetup } from '@root/test/helpers'
import type { Context } from '@src/context'
const driver = getDriver() let authenticatedUser: Context['user']
const neode = getNeode() const context = () => ({ authenticatedUser })
let mutate: ApolloTestSetup['mutate']
let query let query: ApolloTestSetup['query']
let mutate let database: ApolloTestSetup['database']
let authenticatedUser let server: ApolloTestSetup['server']
let user1 let user1
let user2 let user2
let variables let variables
const userQuery = gql`
query ($id: ID) {
User(id: $id) {
followedBy {
id
}
followedByCurrentUser
}
}
`
beforeAll(async () => { beforeAll(async () => {
await cleanDatabase() await cleanDatabase()
const apolloSetup = createApolloTestSetup({ context })
const { server } = createServer({ mutate = apolloSetup.mutate
context: () => ({ query = apolloSetup.query
driver, database = apolloSetup.database
neode, server = apolloSetup.server
user: authenticatedUser,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
}),
})
const testClient = createTestClient(server)
query = testClient.query
mutate = testClient.mutate
}) })
afterAll(async () => { afterAll(async () => {
await cleanDatabase() await cleanDatabase()
await driver.close() void server.stop()
void database.driver.close()
database.neode.close()
}) })
beforeEach(async () => { beforeEach(async () => {
@ -129,7 +109,7 @@ describe('follow', () => {
mutation: followUser, mutation: followUser,
variables, variables,
}) })
const relation = await neode.cypher( const relation = await database.neode.cypher(
'MATCH (user:User {id: $id})-[relationship:FOLLOWS]->(followed:User) WHERE relationship.createdAt IS NOT NULL RETURN relationship', 'MATCH (user:User {id: $id})-[relationship:FOLLOWS]->(followed:User) WHERE relationship.createdAt IS NOT NULL RETURN relationship',
{ id: 'u1' }, { id: 'u1' },
) )
@ -152,7 +132,7 @@ describe('follow', () => {
} }
await expect( await expect(
query({ query({
query: userQuery, query: User,
variables: { id: user1.id }, variables: { id: user1.id },
}), }),
).resolves.toMatchObject({ ).resolves.toMatchObject({

View File

@ -891,8 +891,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
JoinGroup: { JoinGroup: {
id: 'owner-of-closed-group', user: {
myRoleInGroup: 'usual', id: 'owner-of-closed-group',
},
membership: {
role: 'usual',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -914,8 +918,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
JoinGroup: { JoinGroup: {
id: 'current-user', user: {
myRoleInGroup: 'owner', id: 'current-user',
},
membership: {
role: 'owner',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -939,8 +947,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
JoinGroup: { JoinGroup: {
id: 'current-user', user: {
myRoleInGroup: 'pending', id: 'current-user',
},
membership: {
role: 'pending',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -962,8 +974,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
JoinGroup: { JoinGroup: {
id: 'owner-of-closed-group', user: {
myRoleInGroup: 'owner', id: 'owner-of-closed-group',
},
membership: {
role: 'owner',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -1001,8 +1017,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
JoinGroup: { JoinGroup: {
id: 'owner-of-hidden-group', user: {
myRoleInGroup: 'owner', id: 'owner-of-hidden-group',
},
membership: {
role: 'owner',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -1208,16 +1228,28 @@ describe('in mode', () => {
data: { data: {
GroupMembers: expect.arrayContaining([ GroupMembers: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: 'current-user', user: expect.objectContaining({
myRoleInGroup: 'owner', id: 'current-user',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-closed-group', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-hidden-group', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
]), ]),
}, },
@ -1241,16 +1273,28 @@ describe('in mode', () => {
data: { data: {
GroupMembers: expect.arrayContaining([ GroupMembers: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: 'current-user', user: expect.objectContaining({
myRoleInGroup: 'owner', id: 'current-user',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-closed-group', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-hidden-group', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
]), ]),
}, },
@ -1274,16 +1318,28 @@ describe('in mode', () => {
data: { data: {
GroupMembers: expect.arrayContaining([ GroupMembers: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: 'current-user', user: expect.objectContaining({
myRoleInGroup: 'owner', id: 'current-user',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-closed-group', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-hidden-group', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
]), ]),
}, },
@ -1317,16 +1373,28 @@ describe('in mode', () => {
data: { data: {
GroupMembers: expect.arrayContaining([ GroupMembers: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: 'current-user', user: expect.objectContaining({
myRoleInGroup: 'pending', id: 'current-user',
}),
membership: expect.objectContaining({
role: 'pending',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-closed-group', user: expect.objectContaining({
myRoleInGroup: 'owner', id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-hidden-group', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
]), ]),
}, },
@ -1350,16 +1418,28 @@ describe('in mode', () => {
data: { data: {
GroupMembers: expect.arrayContaining([ GroupMembers: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: 'current-user', user: expect.objectContaining({
myRoleInGroup: 'pending', id: 'current-user',
}),
membership: expect.objectContaining({
role: 'pending',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-closed-group', user: expect.objectContaining({
myRoleInGroup: 'owner', id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-hidden-group', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
]), ]),
}, },
@ -1415,20 +1495,36 @@ describe('in mode', () => {
data: { data: {
GroupMembers: expect.arrayContaining([ GroupMembers: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: 'pending-user', user: expect.objectContaining({
myRoleInGroup: 'pending', id: 'pending-user',
}),
membership: expect.objectContaining({
role: 'pending',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'current-user', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'current-user',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-closed-group', user: expect.objectContaining({
myRoleInGroup: 'admin', id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'admin',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-hidden-group', user: expect.objectContaining({
myRoleInGroup: 'owner', id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}), }),
]), ]),
}, },
@ -1452,20 +1548,36 @@ describe('in mode', () => {
data: { data: {
GroupMembers: expect.arrayContaining([ GroupMembers: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: 'pending-user', user: expect.objectContaining({
myRoleInGroup: 'pending', id: 'pending-user',
}),
membership: expect.objectContaining({
role: 'pending',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'current-user', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'current-user',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-closed-group', user: expect.objectContaining({
myRoleInGroup: 'admin', id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'admin',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-hidden-group', user: expect.objectContaining({
myRoleInGroup: 'owner', id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}), }),
]), ]),
}, },
@ -1489,20 +1601,36 @@ describe('in mode', () => {
data: { data: {
GroupMembers: expect.arrayContaining([ GroupMembers: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
id: 'pending-user', user: expect.objectContaining({
myRoleInGroup: 'pending', id: 'pending-user',
}),
membership: expect.objectContaining({
role: 'pending',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'current-user', user: expect.objectContaining({
myRoleInGroup: 'usual', id: 'current-user',
}),
membership: expect.objectContaining({
role: 'usual',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-closed-group', user: expect.objectContaining({
myRoleInGroup: 'admin', id: 'owner-of-closed-group',
}),
membership: expect.objectContaining({
role: 'admin',
}),
}), }),
expect.objectContaining({ expect.objectContaining({
id: 'owner-of-hidden-group', user: expect.objectContaining({
myRoleInGroup: 'owner', id: 'owner-of-hidden-group',
}),
membership: expect.objectContaining({
role: 'owner',
}),
}), }),
]), ]),
}, },
@ -1600,8 +1728,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
ChangeGroupMemberRole: { ChangeGroupMemberRole: {
id: 'usual-member-user', user: {
myRoleInGroup: 'usual', id: 'usual-member-user',
},
membership: {
role: 'usual',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -1638,8 +1770,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
ChangeGroupMemberRole: { ChangeGroupMemberRole: {
id: 'admin-member-user', user: {
myRoleInGroup: 'admin', id: 'admin-member-user',
},
membership: {
role: 'admin',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -1673,8 +1809,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
ChangeGroupMemberRole: { ChangeGroupMemberRole: {
id: 'second-owner-member-user', user: {
myRoleInGroup: 'owner', id: 'second-owner-member-user',
},
membership: {
role: 'owner',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -1759,8 +1899,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
ChangeGroupMemberRole: { ChangeGroupMemberRole: {
id: 'owner-member-user', user: {
myRoleInGroup: 'owner', id: 'owner-member-user',
},
membership: {
role: 'owner',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -1869,8 +2013,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
ChangeGroupMemberRole: { ChangeGroupMemberRole: {
id: 'admin-member-user', user: {
myRoleInGroup: 'owner', id: 'admin-member-user',
},
membership: {
role: 'owner',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -2047,8 +2195,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
ChangeGroupMemberRole: { ChangeGroupMemberRole: {
id: 'usual-member-user', user: {
myRoleInGroup: 'admin', id: 'usual-member-user',
},
membership: {
role: 'admin',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -2073,8 +2225,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
ChangeGroupMemberRole: { ChangeGroupMemberRole: {
id: 'usual-member-user', user: {
myRoleInGroup: 'usual', id: 'usual-member-user',
},
membership: {
role: 'usual',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -2234,8 +2390,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
ChangeGroupMemberRole: { ChangeGroupMemberRole: {
id: 'pending-member-user', user: {
myRoleInGroup: 'usual', id: 'pending-member-user',
},
membership: {
role: 'usual',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -2260,8 +2420,12 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
ChangeGroupMemberRole: { ChangeGroupMemberRole: {
id: 'pending-member-user', user: {
myRoleInGroup: 'pending', id: 'pending-member-user',
},
membership: {
role: 'pending',
},
}, },
}, },
errors: undefined, errors: undefined,
@ -2413,7 +2577,7 @@ describe('in mode', () => {
}, },
}) })
return result.data?.GroupMembers return result.data?.GroupMembers
? !!result.data.GroupMembers.find((member) => member.id === userId) ? !!result.data.GroupMembers.find((member) => member.user.id === userId)
: null : null
} }
@ -2440,8 +2604,10 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
LeaveGroup: { LeaveGroup: {
id: 'pending-member-user', user: {
myRoleInGroup: null, id: 'pending-member-user',
},
membership: null,
}, },
}, },
errors: undefined, errors: undefined,
@ -2467,8 +2633,10 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
LeaveGroup: { LeaveGroup: {
id: 'usual-member-user', user: {
myRoleInGroup: null, id: 'usual-member-user',
},
membership: null,
}, },
}, },
errors: undefined, errors: undefined,
@ -2494,8 +2662,10 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
LeaveGroup: { LeaveGroup: {
id: 'admin-member-user', user: {
myRoleInGroup: null, id: 'admin-member-user',
},
membership: null,
}, },
}, },
errors: undefined, errors: undefined,
@ -3021,8 +3191,10 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
RemoveUserFromGroup: expect.objectContaining({ RemoveUserFromGroup: expect.objectContaining({
id: 'usual-member-user', user: expect.objectContaining({
myRoleInGroup: null, id: 'usual-member-user',
}),
membership: null,
}), }),
}, },
errors: undefined, errors: undefined,
@ -3093,8 +3265,10 @@ describe('in mode', () => {
).resolves.toMatchObject({ ).resolves.toMatchObject({
data: { data: {
RemoveUserFromGroup: expect.objectContaining({ RemoveUserFromGroup: expect.objectContaining({
id: 'usual-member-user', user: {
myRoleInGroup: null, id: 'usual-member-user',
},
membership: null,
}), }),
}, },
errors: undefined, errors: undefined,

View File

@ -24,9 +24,6 @@ export default {
Query: { Query: {
Group: async (_object, params, context: Context, _resolveInfo) => { Group: async (_object, params, context: Context, _resolveInfo) => {
const { isMember, id, slug, first, offset } = params const { isMember, id, slug, first, offset } = params
let pagination = ''
const orderBy = 'ORDER BY group.createdAt DESC'
if (first !== undefined && offset !== undefined) pagination = `SKIP ${offset} LIMIT ${first}`
const matchParams = { id, slug } const matchParams = { id, slug }
removeUndefinedNullValuesFromObject(matchParams) removeUndefinedNullValuesFromObject(matchParams)
const session = context.driver.session() const session = context.driver.session()
@ -34,43 +31,22 @@ export default {
if (!context.user) { if (!context.user) {
throw new Error('Missing authenticated user.') throw new Error('Missing authenticated user.')
} }
const groupMatchParamsCypher = convertObjectToCypherMapLiteral(matchParams, true) const transactionResponse = await txc.run(
let groupCypher
if (isMember === true) {
groupCypher = `
MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group:Group${groupMatchParamsCypher})
WITH group, membership
WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])
RETURN group {.*, myRole: membership.role}
${orderBy}
${pagination}
` `
} else { MATCH (group:Group${convertObjectToCypherMapLiteral(matchParams, true)})
if (isMember === false) { OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
groupCypher = ` WITH group, membership
MATCH (group:Group${groupMatchParamsCypher}) ${(isMember === true && "WHERE membership IS NOT NULL AND (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])") || ''}
WHERE (NOT (:User {id: $userId})-[:MEMBER_OF]->(group)) ${(isMember === false && "WHERE membership IS NULL AND (group.groupType IN ['public', 'closed'])") || ''}
WITH group ${(isMember === undefined && "WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])") || ''}
WHERE group.groupType IN ['public', 'closed'] RETURN group {.*, myRole: membership.role}
RETURN group {.*, myRole: NULL} ORDER BY group.createdAt DESC
${orderBy} ${first !== undefined && offset !== undefined ? `SKIP ${offset} LIMIT ${first}` : ''}
${pagination} `,
` {
} else { userId: context.user.id,
groupCypher = ` },
MATCH (group:Group${groupMatchParamsCypher}) )
OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group)
WITH group, membership
WHERE (group.groupType IN ['public', 'closed']) OR (group.groupType = 'hidden' AND membership.role IN ['usual', 'admin', 'owner'])
RETURN group {.*, myRole: membership.role}
${orderBy}
${pagination}
`
}
}
const transactionResponse = await txc.run(groupCypher, {
userId: context.user.id,
})
return transactionResponse.records.map((record) => record.get('group')) return transactionResponse.records.map((record) => record.get('group'))
}) })
try { try {
@ -87,7 +63,7 @@ export default {
const readTxResultPromise = session.readTransaction(async (txc) => { const readTxResultPromise = session.readTransaction(async (txc) => {
const groupMemberCypher = ` const groupMemberCypher = `
MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId}) MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId})
RETURN user {.*, myRoleInGroup: membership.role} RETURN user {.*}, membership {.*}
SKIP toInteger($offset) LIMIT toInteger($first) SKIP toInteger($offset) LIMIT toInteger($first)
` `
const transactionResponse = await txc.run(groupMemberCypher, { const transactionResponse = await txc.run(groupMemberCypher, {
@ -95,7 +71,9 @@ export default {
first, first,
offset, offset,
}) })
return transactionResponse.records.map((record) => record.get('user')) return transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
}) })
try { try {
return await readTxResultPromise return await readTxResultPromise
@ -297,8 +275,8 @@ export default {
const session = context.driver.session() const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => { const writeTxResultPromise = session.writeTransaction(async (transaction) => {
const joinGroupCypher = ` const joinGroupCypher = `
MATCH (member:User {id: $userId}), (group:Group {id: $groupId}) MATCH (user:User {id: $userId}), (group:Group {id: $groupId})
MERGE (member)-[membership:MEMBER_OF]->(group) MERGE (user)-[membership:MEMBER_OF]->(group)
ON CREATE SET ON CREATE SET
membership.createdAt = toString(datetime()), membership.createdAt = toString(datetime()),
membership.updatedAt = null, membership.updatedAt = null,
@ -307,14 +285,15 @@ export default {
THEN 'usual' THEN 'usual'
ELSE 'pending' ELSE 'pending'
END END
RETURN member {.*, myRoleInGroup: membership.role} RETURN user {.*}, membership {.*}
` `
const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId }) const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId })
const [member] = transactionResponse.records.map((record) => record.get('member')) return transactionResponse.records.map((record) => {
return member return { user: record.get('user'), membership: record.get('membership') }
})
}) })
try { try {
return await writeTxResultPromise return (await writeTxResultPromise)[0]
} catch (error) { } catch (error) {
throw new Error(error) throw new Error(error)
} finally { } finally {
@ -361,7 +340,7 @@ export default {
membership.updatedAt = toString(datetime()), membership.updatedAt = toString(datetime()),
membership.role = $roleInGroup membership.role = $roleInGroup
${postRestrictionCypher} ${postRestrictionCypher}
RETURN member {.*, myRoleInGroup: membership.role} RETURN member {.*} as user, membership {.*}
` `
const transactionResponse = await transaction.run(joinGroupCypher, { const transactionResponse = await transaction.run(joinGroupCypher, {
@ -369,7 +348,9 @@ export default {
userId, userId,
roleInGroup, roleInGroup,
}) })
const [member] = transactionResponse.records.map((record) => record.get('member')) const [member] = transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
return member return member
}) })
try { try {
@ -460,6 +441,23 @@ export default {
}, },
}, },
Group: { Group: {
myRole: async (parent, _args, context: Context, _resolveInfo) => {
if (!parent.id) {
throw new Error('Can not identify selected Group!')
}
return (
await context.database.query({
query: `
MATCH (:User {id: $user.id})-[membership:MEMBER_OF]->(group:Group {id: $parent.id})
RETURN membership.role as role
`,
variables: {
user: context.user,
parent,
},
})
).records.map((r) => r.get('role'))[0]
},
inviteCodes: async (parent, _args, context: Context, _resolveInfo) => { inviteCodes: async (parent, _args, context: Context, _resolveInfo) => {
if (!parent.id) { if (!parent.id) {
throw new Error('Can not identify selected Group!') throw new Error('Can not identify selected Group!')
@ -478,6 +476,18 @@ export default {
}) })
).records.map((r) => r.get('inviteCodes')) ).records.map((r) => r.get('inviteCodes'))
}, },
currentlyPinnedPostsCount: async (parent, _args, context: Context, _resolveInfo) => {
if (!parent.id) {
throw new Error('Can not identify selected Group!')
}
const result = await context.database.query({
query: `
MATCH (:User)-[pinned:GROUP_PINNED]->(pinnedPosts:Post)-[:IN]->(:Group {id: $group.id})
RETURN toString(count(pinnedPosts)) as count`,
variables: { group: parent },
})
return result.records[0].get('count')
},
...Resolver('Group', { ...Resolver('Group', {
undefinedToNull: ['deleted', 'disabled', 'locationName', 'about'], undefinedToNull: ['deleted', 'disabled', 'locationName', 'about'],
hasMany: { hasMany: {
@ -523,14 +533,16 @@ const removeUserFromGroupWriteTxResultPromise = async (session, groupId, userId)
WITH user, collect(p) AS posts WITH user, collect(p) AS posts
FOREACH (post IN posts | FOREACH (post IN posts |
MERGE (user)-[:CANNOT_SEE]->(post)) MERGE (user)-[:CANNOT_SEE]->(post))
RETURN user {.*, myRoleInGroup: NULL} RETURN user {.*}, NULL as membership
` `
const transactionResponse = await transaction.run(removeUserFromGroupCypher, { const transactionResponse = await transaction.run(removeUserFromGroupCypher, {
groupId, groupId,
userId, userId,
}) })
const [user] = await transactionResponse.records.map((record) => record.get('user')) const [user] = await transactionResponse.records.map((record) => {
return { user: record.get('user'), membership: record.get('membership') }
})
return user return user
}) })
} }

View File

@ -84,9 +84,12 @@ export const images = (config: S3Config) => {
const uploadImageFile = async (uploadPromise: Promise<FileUpload> | undefined) => { const uploadImageFile = async (uploadPromise: Promise<FileUpload> | undefined) => {
if (!uploadPromise) return undefined if (!uploadPromise) return undefined
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const upload = await uploadPromise const upload = await uploadPromise
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
const { name, ext } = path.parse(upload.filename) const { name, ext } = path.parse(upload.filename)
const uniqueFilename = `${uuid()}-${slug(name)}${ext}` const uniqueFilename = `${uuid()}-${slug(name)}${ext}`
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return await s3.uploadFile({ ...upload, uniqueFilename }) return await s3.uploadFile({ ...upload, uniqueFilename })
} }

View File

@ -1089,16 +1089,24 @@ describe('redeemInviteCode', () => {
data: { data: {
GroupMembers: expect.arrayContaining([ GroupMembers: expect.arrayContaining([
{ {
id: 'inviting-user', user: {
myRoleInGroup: 'owner', id: 'inviting-user',
name: 'Inviting User', name: 'Inviting User',
slug: 'inviting-user', slug: 'inviting-user',
},
membership: {
role: 'owner',
},
}, },
{ {
id: 'other-user', user: {
myRoleInGroup: 'pending', id: 'other-user',
name: 'Other User', name: 'Other User',
slug: 'other-user', slug: 'other-user',
},
membership: {
role: 'pending',
},
}, },
]), ]),
}, },

View File

@ -1,37 +1,34 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j' import { UpdateUser } from '@graphql/queries/UpdateUser'
import createServer from '@src/server' import { User } from '@graphql/queries/User'
import { createApolloTestSetup } from '@root/test/helpers'
import type { ApolloTestSetup } from '@root/test/helpers'
import type { Context } from '@src/context'
let query, mutate, authenticatedUser let authenticatedUser: Context['user']
const context = () => ({ authenticatedUser })
const driver = getDriver() let mutate: ApolloTestSetup['mutate']
const neode = getNeode() let query: ApolloTestSetup['query']
let database: ApolloTestSetup['database']
let server: ApolloTestSetup['server']
beforeAll(async () => { beforeAll(async () => {
await cleanDatabase() await cleanDatabase()
const apolloSetup = createApolloTestSetup({ context })
const { server } = createServer({ mutate = apolloSetup.mutate
context: () => { query = apolloSetup.query
return { database = apolloSetup.database
driver, server = apolloSetup.server
neode,
user: authenticatedUser,
}
},
})
query = createTestClient(server).query
mutate = createTestClient(server).mutate
}) })
afterAll(async () => { afterAll(async () => {
await cleanDatabase() await cleanDatabase()
await driver.close() void server.stop()
void database.driver.close()
database.neode.close()
}) })
// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543
@ -43,17 +40,6 @@ describe('resolvers', () => {
describe('Location', () => { describe('Location', () => {
describe('custom mutation, not handled by neo4j-graphql-js', () => { describe('custom mutation, not handled by neo4j-graphql-js', () => {
let variables let variables
const updateUserMutation = gql`
mutation ($id: ID!, $name: String) {
UpdateUser(id: $id, name: $name) {
name
location {
name: nameRU
nameEN
}
}
}
`
beforeEach(async () => { beforeEach(async () => {
variables = { variables = {
@ -78,12 +64,12 @@ describe('resolvers', () => {
}) })
it('returns `null` if location translation is not available', async () => { it('returns `null` if location translation is not available', async () => {
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: UpdateUser, variables })).resolves.toMatchObject({
data: { data: {
UpdateUser: { UpdateUser: {
name: 'John Doughnut', name: 'John Doughnut',
location: { location: {
name: null, nameRU: null,
nameEN: 'Paris', nameEN: 'Paris',
}, },
}, },
@ -95,15 +81,6 @@ describe('resolvers', () => {
}) })
}) })
const distanceToMeQuery = gql`
query ($id: ID!) {
User(id: $id) {
location {
distanceToMe
}
}
}
`
let user, myPlaceUser, otherPlaceUser, noCordsPlaceUser, noPlaceUser let user, myPlaceUser, otherPlaceUser, noCordsPlaceUser, noPlaceUser
describe('distanceToMe', () => { describe('distanceToMe', () => {
@ -191,21 +168,19 @@ describe('distanceToMe', () => {
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
const targetUser = await user.toJson() const targetUser = await user.toJson()
await expect( await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }), query({ query: User, variables: { id: targetUser.id } }),
).resolves.toEqual( ).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { User: [
User: [ expect.objectContaining({
{ location: {
location: { distanceToMe: 0,
distanceToMe: 0,
},
}, },
], }),
}, ],
errors: undefined, },
}), errors: undefined,
) })
}) })
}) })
@ -214,21 +189,19 @@ describe('distanceToMe', () => {
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
const targetUser = await myPlaceUser.toJson() const targetUser = await myPlaceUser.toJson()
await expect( await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }), query({ query: User, variables: { id: targetUser.id } }),
).resolves.toEqual( ).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { User: [
User: [ expect.objectContaining({
{ location: {
location: { distanceToMe: 0,
distanceToMe: 0,
},
}, },
], }),
}, ],
errors: undefined, },
}), errors: undefined,
) })
}) })
}) })
@ -237,21 +210,19 @@ describe('distanceToMe', () => {
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
const targetUser = await otherPlaceUser.toJson() const targetUser = await otherPlaceUser.toJson()
await expect( await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }), query({ query: User, variables: { id: targetUser.id } }),
).resolves.toEqual( ).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { User: [
User: [ expect.objectContaining({
{ location: {
location: { distanceToMe: 746,
distanceToMe: 746,
},
}, },
], }),
}, ],
errors: undefined, },
}), errors: undefined,
) })
}) })
}) })
@ -260,21 +231,19 @@ describe('distanceToMe', () => {
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
const targetUser = await noCordsPlaceUser.toJson() const targetUser = await noCordsPlaceUser.toJson()
await expect( await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }), query({ query: User, variables: { id: targetUser.id } }),
).resolves.toEqual( ).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { User: [
User: [ expect.objectContaining({
{ location: {
location: { distanceToMe: null,
distanceToMe: null,
},
}, },
], }),
}, ],
errors: undefined, },
}), errors: undefined,
) })
}) })
}) })
@ -283,19 +252,17 @@ describe('distanceToMe', () => {
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
const targetUser = await noPlaceUser.toJson() const targetUser = await noPlaceUser.toJson()
await expect( await expect(
query({ query: distanceToMeQuery, variables: { id: targetUser.id } }), query({ query: User, variables: { id: targetUser.id } }),
).resolves.toEqual( ).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { User: [
User: [ expect.objectContaining({
{ location: null,
location: null, }),
}, ],
], },
}, errors: undefined,
errors: undefined, })
}),
)
}) })
}) })
}) })

View File

@ -165,44 +165,46 @@ describe('given some notifications', () => {
describe('no filters', () => { describe('no filters', () => {
it('returns all notifications of current user', async () => { it('returns all notifications of current user', async () => {
const expected = [
{
from: {
__typename: 'Comment',
content: 'You have seen this comment mentioning already',
},
read: true,
createdAt: '2019-08-30T15:33:48.651Z',
},
{
from: {
__typename: 'Post',
content: 'Already seen post mention',
},
read: true,
createdAt: '2019-08-30T17:33:48.651Z',
},
{
from: {
__typename: 'Comment',
content: 'You have been mentioned in a comment',
},
read: false,
createdAt: '2019-08-30T19:33:48.651Z',
},
{
from: {
__typename: 'Post',
content: 'You have been mentioned in a post',
},
read: false,
createdAt: '2019-08-31T17:33:48.651Z',
},
]
await expect(query({ query: notifications, variables })).resolves.toMatchObject({ await expect(query({ query: notifications, variables })).resolves.toMatchObject({
data: { data: {
notifications: expect.arrayContaining(expected), notifications: expect.arrayContaining([
expect.objectContaining({
from: {
__typename: 'Comment',
content: 'You have seen this comment mentioning already',
id: 'c1',
},
read: true,
createdAt: '2019-08-30T15:33:48.651Z',
}),
expect.objectContaining({
from: {
__typename: 'Post',
content: 'Already seen post mention',
id: 'p2',
},
read: true,
createdAt: '2019-08-30T17:33:48.651Z',
}),
expect.objectContaining({
from: {
__typename: 'Comment',
content: 'You have been mentioned in a comment',
id: 'c2',
},
read: false,
createdAt: '2019-08-30T19:33:48.651Z',
}),
expect.objectContaining({
from: {
__typename: 'Post',
content: 'You have been mentioned in a post',
id: 'p3',
},
read: false,
createdAt: '2019-08-31T17:33:48.651Z',
}),
]),
}, },
errors: undefined, errors: undefined,
}) })
@ -211,33 +213,34 @@ describe('given some notifications', () => {
describe('filter for read: false', () => { describe('filter for read: false', () => {
it('returns only unread notifications of current user', async () => { it('returns only unread notifications of current user', async () => {
const expected = expect.objectContaining({
data: {
notifications: expect.arrayContaining([
{
from: {
__typename: 'Comment',
content: 'You have been mentioned in a comment',
},
read: false,
createdAt: '2019-08-30T19:33:48.651Z',
},
{
from: {
__typename: 'Post',
content: 'You have been mentioned in a post',
},
read: false,
createdAt: '2019-08-31T17:33:48.651Z',
},
]),
},
})
const response = await query({ const response = await query({
query: notifications, query: notifications,
variables: { ...variables, read: false }, variables: { ...variables, read: false },
}) })
await expect(response).toMatchObject(expected) await expect(response).toMatchObject({
data: {
notifications: expect.arrayContaining([
expect.objectContaining({
from: {
__typename: 'Comment',
content: 'You have been mentioned in a comment',
id: 'c2',
},
read: false,
createdAt: '2019-08-30T19:33:48.651Z',
}),
expect.objectContaining({
from: {
__typename: 'Post',
content: 'You have been mentioned in a post',
id: 'p3',
},
read: false,
createdAt: '2019-08-31T17:33:48.651Z',
}),
]),
},
})
await expect(response.data?.notifications).toHaveLength(2) // double-check await expect(response.data?.notifications).toHaveLength(2) // double-check
}) })
@ -394,11 +397,13 @@ describe('given some notifications', () => {
{ {
createdAt: '2019-08-30T19:33:48.651Z', createdAt: '2019-08-30T19:33:48.651Z',
from: { __typename: 'Comment', content: 'You have been mentioned in a comment' }, from: { __typename: 'Comment', content: 'You have been mentioned in a comment' },
id: 'mentioned_in_comment/c2/you',
read: true, read: true,
}, },
{ {
createdAt: '2019-08-31T17:33:48.651Z', createdAt: '2019-08-31T17:33:48.651Z',
from: { __typename: 'Post', content: 'You have been mentioned in a post' }, from: { __typename: 'Post', content: 'You have been mentioned in a post' },
id: 'mentioned_in_post/p3/you',
read: true, read: true,
}, },
]), ]),

View File

@ -0,0 +1,368 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import Factory, { cleanDatabase } from '@db/factories'
import { ChangeGroupMemberRole } from '@graphql/queries/ChangeGroupMemberRole'
import { CreateGroup } from '@graphql/queries/CreateGroup'
import { CreatePost } from '@graphql/queries/CreatePost'
import { pinGroupPost } from '@graphql/queries/pinGroupPost'
import { profilePagePosts } from '@graphql/queries/profilePagePosts'
import { unpinGroupPost } from '@graphql/queries/unpinGroupPost'
import type { ApolloTestSetup } from '@root/test/helpers'
import { createApolloTestSetup } from '@root/test/helpers'
import type { Context } from '@src/context'
const defaultConfig = {
CATEGORIES_ACTIVE: false,
}
let config: Partial<Context['config']>
let anyUser
let allGroupsUser
let publicUser
let publicAdminUser
let authenticatedUser: Context['user']
const context = () => ({ authenticatedUser, config })
let mutate: ApolloTestSetup['mutate']
let query: ApolloTestSetup['query']
let database: ApolloTestSetup['database']
let server: ApolloTestSetup['server']
beforeAll(async () => {
await cleanDatabase()
const apolloSetup = createApolloTestSetup({ context })
mutate = apolloSetup.mutate
query = apolloSetup.query
database = apolloSetup.database
server = apolloSetup.server
})
afterAll(() => {
void server.stop()
void database.driver.close()
database.neode.close()
})
beforeEach(async () => {
config = { ...defaultConfig }
authenticatedUser = null
anyUser = await Factory.build('user', {
id: 'any-user',
name: 'Any User',
about: 'I am just an ordinary user and do not belong to any group.',
})
allGroupsUser = await Factory.build('user', {
id: 'all-groups-user',
name: 'All Groups User',
about: 'I am a member of all groups.',
})
publicUser = await Factory.build('user', {
id: 'public-user',
name: 'Public User',
about: 'I am the owner of the public group.',
})
publicAdminUser = await Factory.build('user', {
id: 'public-admin-user',
name: 'Public Admin User',
about: 'I am the admin of the public group.',
})
authenticatedUser = await publicUser.toJson()
await mutate({
mutation: CreateGroup,
variables: {
id: 'public-group',
name: 'The Public Group',
about: 'The public group!',
description: 'Anyone can see the posts of this group.',
groupType: 'public',
actionRadius: 'regional',
},
})
await mutate({
mutation: ChangeGroupMemberRole,
variables: {
groupId: 'public-group',
userId: 'all-groups-user',
roleInGroup: 'usual',
},
})
await mutate({
mutation: ChangeGroupMemberRole,
variables: {
groupId: 'public-group',
userId: 'public-admin-user',
roleInGroup: 'admin',
},
})
await mutate({
mutation: ChangeGroupMemberRole,
variables: {
groupId: 'closed-group',
userId: 'all-groups-user',
roleInGroup: 'usual',
},
})
authenticatedUser = await anyUser.toJson()
await mutate({
mutation: CreatePost,
variables: {
id: 'post-without-group',
title: 'A post without a group',
content: 'I am a user who does not belong to a group yet.',
},
})
authenticatedUser = await publicUser.toJson()
await mutate({
mutation: CreatePost,
variables: {
id: 'post-1-to-public-group',
title: 'Post 1 to a public group',
content: 'I am posting into a public group as a member of the group',
groupId: 'public-group',
},
})
await mutate({
mutation: CreatePost,
variables: {
id: 'post-2-to-public-group',
title: 'Post 1 to a public group',
content: 'I am posting into a public group as a member of the group',
groupId: 'public-group',
},
})
await mutate({
mutation: CreatePost,
variables: {
id: 'post-3-to-public-group',
title: 'Post 1 to a public group',
content: 'I am posting into a public group as a member of the group',
groupId: 'public-group',
},
})
})
afterEach(async () => {
await cleanDatabase()
})
describe('pin groupPosts', () => {
describe('unauthenticated', () => {
it('throws authorization error', async () => {
authenticatedUser = null
await expect(
mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorized!' }],
data: { pinGroupPost: null },
})
})
})
describe('ordinary users', () => {
it('throws authorization error', async () => {
authenticatedUser = await anyUser.toJson()
await expect(
mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorized!' }],
data: { pinGroupPost: null },
})
})
})
describe('group usual', () => {
it('throws authorization error', async () => {
authenticatedUser = await allGroupsUser.toJson()
await expect(
mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } }),
).resolves.toMatchObject({
errors: [{ message: 'Not Authorized!' }],
data: { pinGroupPost: null },
})
})
})
describe('group admin', () => {
it('resolves without error', async () => {
authenticatedUser = await publicAdminUser.toJson()
await expect(
mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } }),
).resolves.toMatchObject({
errors: undefined,
data: { pinGroupPost: { id: 'post-1-to-public-group', groupPinned: true } },
})
})
})
describe('group owner', () => {
it('resolves without error', async () => {
authenticatedUser = await publicUser.toJson()
await expect(
mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } }),
).resolves.toMatchObject({
errors: undefined,
data: { pinGroupPost: { id: 'post-1-to-public-group', groupPinned: true } },
})
})
})
describe('MAX_GROUP_PINNED_POSTS is 1', () => {
beforeEach(async () => {
config = { ...defaultConfig, MAX_GROUP_PINNED_POSTS: 1 }
authenticatedUser = await publicUser.toJson()
})
it('returns post-1-to-public-group as first, pinned post', async () => {
await mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } })
await expect(
query({
query: profilePagePosts,
variables: {
filter: { group: { id: 'public-group' } },
orderBy: ['groupPinned_asc', 'sortDate_desc'],
},
}),
).resolves.toMatchObject({
errors: undefined,
data: {
profilePagePosts: [
expect.objectContaining({ id: 'post-1-to-public-group', groupPinned: true }),
expect.objectContaining({ id: 'post-3-to-public-group', groupPinned: null }),
expect.objectContaining({ id: 'post-2-to-public-group', groupPinned: null }),
],
},
})
})
it('no error thrown when pinned post was pinned again', async () => {
await mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } })
await expect(
mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } }),
).resolves.toMatchObject({
errors: undefined,
data: { pinGroupPost: { id: 'post-1-to-public-group', groupPinned: true } },
})
})
it('returns post-2-to-public-group as first, pinned post', async () => {
authenticatedUser = await publicUser.toJson()
await mutate({ mutation: pinGroupPost, variables: { id: 'post-2-to-public-group' } })
await expect(
query({
query: profilePagePosts,
variables: {
filter: { group: { id: 'public-group' } },
orderBy: ['groupPinned_asc', 'sortDate_desc'],
},
}),
).resolves.toMatchObject({
errors: undefined,
data: {
profilePagePosts: [
expect.objectContaining({ id: 'post-2-to-public-group', groupPinned: true }),
expect.objectContaining({ id: 'post-3-to-public-group', groupPinned: null }),
expect.objectContaining({ id: 'post-1-to-public-group', groupPinned: null }),
],
},
})
})
it('returns post-3-to-public-group as first, pinned post, when multiple are pinned', async () => {
authenticatedUser = await publicUser.toJson()
await mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } })
await mutate({ mutation: pinGroupPost, variables: { id: 'post-2-to-public-group' } })
await mutate({ mutation: pinGroupPost, variables: { id: 'post-3-to-public-group' } })
await expect(
query({
query: profilePagePosts,
variables: {
filter: { group: { id: 'public-group' } },
orderBy: ['groupPinned_asc', 'sortDate_desc'],
},
}),
).resolves.toMatchObject({
errors: undefined,
data: {
profilePagePosts: [
expect.objectContaining({ id: 'post-3-to-public-group', groupPinned: true }),
expect.objectContaining({ id: 'post-2-to-public-group', groupPinned: null }),
expect.objectContaining({ id: 'post-1-to-public-group', groupPinned: null }),
],
},
})
})
})
describe('MAX_GROUP_PINNED_POSTS is 2', () => {
beforeEach(async () => {
config = { ...defaultConfig, MAX_GROUP_PINNED_POSTS: 2 }
authenticatedUser = await publicUser.toJson()
})
it('returns post-1-to-public-group as first, post-2-to-public-group as second pinned post', async () => {
await mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } })
await mutate({ mutation: pinGroupPost, variables: { id: 'post-2-to-public-group' } })
await expect(
query({
query: profilePagePosts,
variables: {
filter: { group: { id: 'public-group' } },
orderBy: ['groupPinned_asc', 'sortDate_desc'],
},
}),
).resolves.toMatchObject({
errors: undefined,
data: {
profilePagePosts: [
expect.objectContaining({ id: 'post-2-to-public-group', groupPinned: true }),
expect.objectContaining({ id: 'post-1-to-public-group', groupPinned: true }),
expect.objectContaining({ id: 'post-3-to-public-group', groupPinned: null }),
],
},
})
})
it('throws an error when three posts are pinned', async () => {
await mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } })
await mutate({ mutation: pinGroupPost, variables: { id: 'post-2-to-public-group' } })
await expect(
mutate({ mutation: pinGroupPost, variables: { id: 'post-3-to-public-group' } }),
).resolves.toMatchObject({
errors: [{ message: 'Reached maxed pinned posts already. Unpin a post first.' }],
data: {
pinGroupPost: null,
},
})
})
it('throws no error when first unpinned before a third post is pinned', async () => {
await mutate({ mutation: pinGroupPost, variables: { id: 'post-1-to-public-group' } })
await mutate({ mutation: pinGroupPost, variables: { id: 'post-2-to-public-group' } })
await mutate({ mutation: unpinGroupPost, variables: { id: 'post-1-to-public-group' } })
await expect(
mutate({ mutation: pinGroupPost, variables: { id: 'post-3-to-public-group' } }),
).resolves.toMatchObject({
errors: undefined,
})
await expect(
query({
query: profilePagePosts,
variables: {
filter: { group: { id: 'public-group' } },
orderBy: ['groupPinned_asc', 'sortDate_desc'],
},
}),
).resolves.toMatchObject({
errors: undefined,
data: {
profilePagePosts: [
expect.objectContaining({ id: 'post-3-to-public-group', groupPinned: true }),
expect.objectContaining({ id: 'post-2-to-public-group', groupPinned: true }),
expect.objectContaining({ id: 'post-1-to-public-group', groupPinned: null }),
],
},
})
})
})
})

View File

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import gql from 'graphql-tag'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import { CreateComment } from '@graphql/queries/CreateComment'
import { CreatePost } from '@graphql/queries/CreatePost' import { CreatePost } from '@graphql/queries/CreatePost'
import { Post } from '@graphql/queries/Post'
import { toggleObservePost } from '@graphql/queries/toggleObservePost' import { toggleObservePost } from '@graphql/queries/toggleObservePost'
import type { ApolloTestSetup } from '@root/test/helpers' import type { ApolloTestSetup } from '@root/test/helpers'
import { createApolloTestSetup } from '@root/test/helpers' import { createApolloTestSetup } from '@root/test/helpers'
@ -20,25 +20,6 @@ let query: ApolloTestSetup['query']
let database: ApolloTestSetup['database'] let database: ApolloTestSetup['database']
let server: ApolloTestSetup['server'] let server: ApolloTestSetup['server']
const createCommentMutation = gql`
mutation ($id: ID, $postId: ID!, $content: String!) {
CreateComment(id: $id, postId: $postId, content: $content) {
id
isPostObservedByMe
postObservingUsersCount
}
}
`
const postQuery = gql`
query Post($id: ID) {
Post(id: $id) {
isObservedByMe
observingUsersCount
}
}
`
beforeAll(async () => { beforeAll(async () => {
await cleanDatabase() await cleanDatabase()
const apolloSetup = createApolloTestSetup({ context }) const apolloSetup = createApolloTestSetup({ context })
@ -101,7 +82,7 @@ describe('observing posts', () => {
it('has another user NOT observing the post BEFORE commenting it', async () => { it('has another user NOT observing the post BEFORE commenting it', async () => {
await expect( await expect(
query({ query({
query: postQuery, query: Post,
variables: { id: 'p2' }, variables: { id: 'p2' },
}), }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
@ -120,7 +101,7 @@ describe('observing posts', () => {
it('has another user observing the post AFTER commenting it', async () => { it('has another user observing the post AFTER commenting it', async () => {
await expect( await expect(
mutate({ mutate({
mutation: createCommentMutation, mutation: CreateComment,
variables: { variables: {
postId: 'p2', postId: 'p2',
content: 'After commenting the post, I should observe the post automatically', content: 'After commenting the post, I should observe the post automatically',
@ -137,7 +118,7 @@ describe('observing posts', () => {
await expect( await expect(
query({ query({
query: postQuery, query: Post,
variables: { id: 'p2' }, variables: { id: 'p2' },
}), }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
@ -185,7 +166,7 @@ describe('observing posts', () => {
it('does NOT alter the observation state', async () => { it('does NOT alter the observation state', async () => {
await expect( await expect(
mutate({ mutate({
mutation: createCommentMutation, mutation: CreateComment,
variables: { variables: {
postId: 'p2', postId: 'p2',
content: content:
@ -203,7 +184,7 @@ describe('observing posts', () => {
await expect( await expect(
query({ query({
query: postQuery, query: Post,
variables: { id: 'p2' }, variables: { id: 'p2' },
}), }),
).resolves.toMatchObject({ ).resolves.toMatchObject({

View File

@ -2,8 +2,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import gql from 'graphql-tag'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import Image from '@db/models/Image' import Image from '@db/models/Image'
import { AddPostEmotions } from '@graphql/queries/AddPostEmotions' import { AddPostEmotions } from '@graphql/queries/AddPostEmotions'
@ -18,6 +16,7 @@ import { pushPost } from '@graphql/queries/pushPost'
import { RemovePostEmotions } from '@graphql/queries/RemovePostEmotions' import { RemovePostEmotions } from '@graphql/queries/RemovePostEmotions'
import { unpinPost } from '@graphql/queries/unpinPost' import { unpinPost } from '@graphql/queries/unpinPost'
import { unpushPost } from '@graphql/queries/unpushPost' import { unpushPost } from '@graphql/queries/unpushPost'
import { UpdatePost } from '@graphql/queries/UpdatePost'
import type { ApolloTestSetup } from '@root/test/helpers' import type { ApolloTestSetup } from '@root/test/helpers'
import { createApolloTestSetup } from '@root/test/helpers' import { createApolloTestSetup } from '@root/test/helpers'
import type { Context } from '@src/context' import type { Context } from '@src/context'
@ -133,18 +132,14 @@ describe('Post', () => {
describe('no filter', () => { describe('no filter', () => {
it('returns all posts', async () => { it('returns all posts', async () => {
const postQueryNoFilters = gql`
query Post($filter: _PostFilter) {
Post(filter: $filter) {
id
}
}
`
const expected = [{ id: 'happy-post' }, { id: 'cry-post' }, { id: 'post-by-followed-user' }]
variables = { filter: {} } variables = { filter: {} }
await expect(query({ query: postQueryNoFilters, variables })).resolves.toMatchObject({ await expect(query({ query: Post, variables })).resolves.toMatchObject({
data: { data: {
Post: expect.arrayContaining(expected), Post: expect.arrayContaining([
expect.objectContaining({ id: 'happy-post' }),
expect.objectContaining({ id: 'cry-post' }),
expect.objectContaining({ id: 'post-by-followed-user' }),
]),
}, },
}) })
}) })
@ -178,17 +173,6 @@ describe('Post', () => {
}) */ }) */
describe('by emotions', () => { describe('by emotions', () => {
const postQueryFilteredByEmotions = gql`
query Post($filter: _PostFilter) {
Post(filter: $filter) {
id
emotions {
emotion
}
}
}
`
it('filters by single emotion', async () => { it('filters by single emotion', async () => {
const expected = { const expected = {
data: { data: {
@ -202,30 +186,25 @@ describe('Post', () => {
} }
await user.relateTo(happyPost, 'emoted', { emotion: 'happy' }) await user.relateTo(happyPost, 'emoted', { emotion: 'happy' })
variables = { ...variables, filter: { emotions_some: { emotion_in: ['happy'] } } } variables = { ...variables, filter: { emotions_some: { emotion_in: ['happy'] } } }
await expect( await expect(query({ query: Post, variables })).resolves.toMatchObject(expected)
query({ query: postQueryFilteredByEmotions, variables }),
).resolves.toMatchObject(expected)
}) })
it('filters by multiple emotions', async () => { it('filters by multiple emotions', async () => {
const expected = [
{
id: 'happy-post',
emotions: [{ emotion: 'happy' }],
},
{
id: 'cry-post',
emotions: [{ emotion: 'cry' }],
},
]
await user.relateTo(happyPost, 'emoted', { emotion: 'happy' }) await user.relateTo(happyPost, 'emoted', { emotion: 'happy' })
await user.relateTo(cryPost, 'emoted', { emotion: 'cry' }) await user.relateTo(cryPost, 'emoted', { emotion: 'cry' })
variables = { ...variables, filter: { emotions_some: { emotion_in: ['happy', 'cry'] } } } variables = { ...variables, filter: { emotions_some: { emotion_in: ['happy', 'cry'] } } }
await expect( await expect(query({ query: Post, variables })).resolves.toMatchObject({
query({ query: postQueryFilteredByEmotions, variables }),
).resolves.toMatchObject({
data: { data: {
Post: expect.arrayContaining(expected), Post: expect.arrayContaining([
expect.objectContaining({
id: 'happy-post',
emotions: [expect.objectContaining({ emotion: 'happy' })],
}),
expect.objectContaining({
id: 'cry-post',
emotions: [expect.objectContaining({ emotion: 'cry' })],
}),
]),
}, },
errors: undefined, errors: undefined,
}) })
@ -233,22 +212,9 @@ describe('Post', () => {
}) })
it('by followed-by', async () => { it('by followed-by', async () => {
const postQueryFilteredByUsersFollowed = gql`
query Post($filter: _PostFilter) {
Post(filter: $filter) {
id
author {
name
}
}
}
`
await user.relateTo(followedUser, 'following') await user.relateTo(followedUser, 'following')
variables = { filter: { author: { followedBy_some: { id: 'current-user' } } } } variables = { filter: { author: { followedBy_some: { id: 'current-user' } } } }
await expect( await expect(query({ query: Post, variables })).resolves.toMatchObject({
query({ query: postQueryFilteredByUsersFollowed, variables }),
).resolves.toMatchObject({
data: { data: {
Post: [ Post: [
{ {
@ -655,48 +621,6 @@ describe('CreatePost', () => {
describe('UpdatePost', () => { describe('UpdatePost', () => {
let author, newlyCreatedPost let author, newlyCreatedPost
const updatePostMutation = gql`
mutation (
$id: ID!
$title: String!
$content: String!
$image: ImageInput
$categoryIds: [ID]
$postType: PostType
$eventInput: _EventInput
) {
UpdatePost(
id: $id
title: $title
content: $content
image: $image
categoryIds: $categoryIds
postType: $postType
eventInput: $eventInput
) {
id
title
content
author {
name
slug
}
createdAt
updatedAt
categories {
id
}
postType
eventStart
eventLocationName
eventVenue
eventLocation {
lng
lat
}
}
}
`
beforeEach(async () => { beforeEach(async () => {
author = await Factory.build('user', { slug: 'the-author' }) author = await Factory.build('user', { slug: 'the-author' })
authenticatedUser = await author.toJson() authenticatedUser = await author.toJson()
@ -719,7 +643,7 @@ describe('UpdatePost', () => {
describe('unauthenticated', () => { describe('unauthenticated', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
authenticatedUser = null authenticatedUser = null
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: UpdatePost, variables })).resolves.toMatchObject({
errors: [{ message: 'Not Authorized!' }], errors: [{ message: 'Not Authorized!' }],
data: { UpdatePost: null }, data: { UpdatePost: null },
}) })
@ -732,7 +656,7 @@ describe('UpdatePost', () => {
}) })
it('throws authorization error', async () => { it('throws authorization error', async () => {
const { errors } = await mutate({ mutation: updatePostMutation, variables }) const { errors } = await mutate({ mutation: UpdatePost, variables })
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!') expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!')
}) })
}) })
@ -747,9 +671,7 @@ describe('UpdatePost', () => {
data: { UpdatePost: { id: newlyCreatedPost.id, content: 'New content' } }, data: { UpdatePost: { id: newlyCreatedPost.id, content: 'New content' } },
errors: undefined, errors: undefined,
} }
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( await expect(mutate({ mutation: UpdatePost, variables })).resolves.toMatchObject(expected)
expected,
)
}) })
it('updates a post, but maintains non-updated attributes', async () => { it('updates a post, but maintains non-updated attributes', async () => {
@ -763,18 +685,16 @@ describe('UpdatePost', () => {
}, },
errors: undefined, errors: undefined,
} }
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( await expect(mutate({ mutation: UpdatePost, variables })).resolves.toMatchObject(expected)
expected,
)
}) })
it('updates the updatedAt attribute', async () => { it('updates the updatedAt attribute', async () => {
const { const {
data: { UpdatePost }, data: { UpdatePost: UpdatePostData },
} = (await mutate({ mutation: updatePostMutation, variables })) as any // eslint-disable-line @typescript-eslint/no-explicit-any } = (await mutate({ mutation: UpdatePost, variables })) as any // eslint-disable-line @typescript-eslint/no-explicit-any
expect(UpdatePost.updatedAt).toBeTruthy() expect(UpdatePostData.updatedAt).toBeTruthy()
expect(Date.parse(UpdatePost.updatedAt)).toEqual(expect.any(Number)) expect(Date.parse(UpdatePostData.updatedAt)).toEqual(expect.any(Number))
expect(newlyCreatedPost.updatedAt).not.toEqual(UpdatePost.updatedAt) expect(newlyCreatedPost.updatedAt).not.toEqual(UpdatePostData.updatedAt)
}) })
describe('no new category ids provided for update', () => { describe('no new category ids provided for update', () => {
@ -788,9 +708,7 @@ describe('UpdatePost', () => {
}, },
errors: undefined, errors: undefined,
} }
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( await expect(mutate({ mutation: UpdatePost, variables })).resolves.toMatchObject(expected)
expected,
)
}) })
}) })
@ -809,9 +727,7 @@ describe('UpdatePost', () => {
}, },
errors: undefined, errors: undefined,
} }
await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( await expect(mutate({ mutation: UpdatePost, variables })).resolves.toMatchObject(expected)
expected,
)
}) })
}) })
@ -820,7 +736,7 @@ describe('UpdatePost', () => {
it('throws an error', async () => { it('throws an error', async () => {
await expect( await expect(
mutate({ mutate({
mutation: updatePostMutation, mutation: UpdatePost,
variables: { ...variables, postType: 'Event' }, variables: { ...variables, postType: 'Event' },
}), }),
).resolves.toMatchObject({ ).resolves.toMatchObject({
@ -837,7 +753,7 @@ describe('UpdatePost', () => {
it('throws an error', async () => { it('throws an error', async () => {
await expect( await expect(
mutate({ mutate({
mutation: updatePostMutation, mutation: UpdatePost,
variables: { variables: {
...variables, ...variables,
postType: 'Event', postType: 'Event',
@ -861,7 +777,7 @@ describe('UpdatePost', () => {
const now = new Date() const now = new Date()
await expect( await expect(
mutate({ mutate({
mutation: updatePostMutation, mutation: UpdatePost,
variables: { variables: {
...variables, ...variables,
postType: 'Event', postType: 'Event',
@ -885,7 +801,7 @@ describe('UpdatePost', () => {
const now = new Date() const now = new Date()
await expect( await expect(
mutate({ mutate({
mutation: updatePostMutation, mutation: UpdatePost,
variables: { variables: {
...variables, ...variables,
postType: 'Event', postType: 'Event',
@ -910,7 +826,7 @@ describe('UpdatePost', () => {
const now = new Date() const now = new Date()
await expect( await expect(
mutate({ mutate({
mutation: updatePostMutation, mutation: UpdatePost,
variables: { variables: {
...variables, ...variables,
postType: 'Event', postType: 'Event',
@ -936,7 +852,7 @@ describe('UpdatePost', () => {
const now = new Date() const now = new Date()
await expect( await expect(
mutate({ mutate({
mutation: updatePostMutation, mutation: UpdatePost,
variables: { variables: {
...variables, ...variables,
postType: 'Event', postType: 'Event',
@ -976,7 +892,7 @@ describe('UpdatePost', () => {
await expect( await expect(
database.neode.first<typeof Image>('Image', { sensitive: true }, undefined), database.neode.first<typeof Image>('Image', { sensitive: true }, undefined),
).resolves.toBeFalsy() ).resolves.toBeFalsy()
await mutate({ mutation: updatePostMutation, variables }) await mutate({ mutation: UpdatePost, variables })
await expect( await expect(
database.neode.first<typeof Image>('Image', { sensitive: true }, undefined), database.neode.first<typeof Image>('Image', { sensitive: true }, undefined),
).resolves.toBeTruthy() ).resolves.toBeTruthy()
@ -989,7 +905,7 @@ describe('UpdatePost', () => {
}) })
it('deletes the image', async () => { it('deletes the image', async () => {
await expect(database.neode.all('Image')).resolves.toHaveLength(6) await expect(database.neode.all('Image')).resolves.toHaveLength(6)
await mutate({ mutation: updatePostMutation, variables }) await mutate({ mutation: UpdatePost, variables })
await expect(database.neode.all('Image')).resolves.toHaveLength(5) await expect(database.neode.all('Image')).resolves.toHaveLength(5)
}) })
}) })
@ -1002,7 +918,7 @@ describe('UpdatePost', () => {
await expect( await expect(
database.neode.first<typeof Image>('Image', { sensitive: true }, undefined), database.neode.first<typeof Image>('Image', { sensitive: true }, undefined),
).resolves.toBeFalsy() ).resolves.toBeFalsy()
await mutate({ mutation: updatePostMutation, variables }) await mutate({ mutation: UpdatePost, variables })
await expect( await expect(
database.neode.first<typeof Image>('Image', { sensitive: true }, undefined), database.neode.first<typeof Image>('Image', { sensitive: true }, undefined),
).resolves.toBeFalsy() ).resolves.toBeFalsy()
@ -2131,25 +2047,6 @@ describe('DeletePost', () => {
describe('emotions', () => { describe('emotions', () => {
let author, postToEmote let author, postToEmote
const PostsEmotionsCountQuery = gql`
query ($id: ID!) {
Post(id: $id) {
emotionsCount
}
}
`
const PostsEmotionsQuery = gql`
query ($id: ID!) {
Post(id: $id) {
emotions {
emotion
User {
id
}
}
}
}
`
beforeEach(async () => { beforeEach(async () => {
author = await database.neode.create('User', { id: 'u257' }) author = await database.neode.create('User', { id: 'u257' })
@ -2226,8 +2123,8 @@ describe('emotions', () => {
await mutate({ mutation: AddPostEmotions, variables }) await mutate({ mutation: AddPostEmotions, variables })
await mutate({ mutation: AddPostEmotions, variables }) await mutate({ mutation: AddPostEmotions, variables })
await expect( await expect(
query({ query: PostsEmotionsCountQuery, variables: postsEmotionsQueryVariables }), query({ query: Post, variables: postsEmotionsQueryVariables }),
).resolves.toEqual(expect.objectContaining(expected)) ).resolves.toMatchObject(expected)
}) })
it('allows a user to add more than one emotion', async () => { it('allows a user to add more than one emotion', async () => {
@ -2247,8 +2144,8 @@ describe('emotions', () => {
variables = { ...variables, data: { emotion: 'surprised' } } variables = { ...variables, data: { emotion: 'surprised' } }
await mutate({ mutation: AddPostEmotions, variables }) await mutate({ mutation: AddPostEmotions, variables })
await expect( await expect(
query({ query: PostsEmotionsQuery, variables: postsEmotionsQueryVariables }), query({ query: Post, variables: postsEmotionsQueryVariables }),
).resolves.toEqual(expect.objectContaining(expected)) ).resolves.toMatchObject(expected)
}) })
}) })
@ -2351,8 +2248,8 @@ describe('emotions', () => {
variables: removePostEmotionsVariables, variables: removePostEmotionsVariables,
}) })
await expect( await expect(
query({ query: PostsEmotionsQuery, variables: postsEmotionsQueryVariables }), query({ query: Post, variables: postsEmotionsQueryVariables }),
).resolves.toEqual(expect.objectContaining(expectedResponse)) ).resolves.toMatchObject(expectedResponse)
}) })
}) })
}) })

View File

@ -29,6 +29,20 @@ const maintainPinnedPosts = (params) => {
return params return params
} }
const maintainGroupPinnedPosts = (params) => {
// only show GroupPinnedPosts when Groups is selected
if (!params.filter?.group) {
return params
}
const pinnedPostFilter = { groupPinned: true, group: params.filter.group }
if (isEmpty(params.filter)) {
params.filter = { OR: [pinnedPostFilter, {}] }
} else {
params.filter = { OR: [pinnedPostFilter, { ...params.filter }] }
}
return params
}
const filterEventDates = (params) => { const filterEventDates = (params) => {
if (params.filter?.eventStart_gte) { if (params.filter?.eventStart_gte) {
const date = params.filter.eventStart_gte const date = params.filter.eventStart_gte
@ -52,6 +66,7 @@ export default {
params = await filterPostsOfMyGroups(params, context) params = await filterPostsOfMyGroups(params, context)
params = await filterInvisiblePosts(params, context) params = await filterInvisiblePosts(params, context)
params = await filterForMutedUsers(params, context) params = await filterForMutedUsers(params, context)
params = await maintainGroupPinnedPosts(params)
return neo4jgraphql(object, params, context, resolveInfo) return neo4jgraphql(object, params, context, resolveInfo)
}, },
PostsEmotionsCountByEmotion: async (_object, params, context, _resolveInfo) => { PostsEmotionsCountByEmotion: async (_object, params, context, _resolveInfo) => {
@ -154,7 +169,7 @@ export default {
)` )`
} }
const categoriesCypher = const categoriesCypher =
config.CATEGORIES_ACTIVE && categoryIds config.CATEGORIES_ACTIVE && categoryIds && categoryIds.length > 0
? `WITH post ? `WITH post
UNWIND $categoryIds AS categoryId UNWIND $categoryIds AS categoryId
MATCH (category:Category {id: categoryId}) MATCH (category:Category {id: categoryId})
@ -453,6 +468,68 @@ export default {
} }
return unpinnedPost return unpinnedPost
}, },
pinGroupPost: async (_parent, params, context: Context, _resolveInfo) => {
if (!context.user) {
throw new Error('Missing authenticated user.')
}
const { config } = context
if (config.MAX_GROUP_PINNED_POSTS === 0) {
throw new Error('Pinned posts are not allowed!')
}
// If MAX_GROUP_PINNED_POSTS === 1 -> Delete old pin
if (config.MAX_GROUP_PINNED_POSTS === 1) {
await context.database.write({
query: `
MATCH (post:Post {id: $params.id})-[:IN]->(group:Group)
MATCH (:User)-[pinned:GROUP_PINNED]->(oldPinnedPost:Post)-[:IN]->(:Group {id: group.id})
REMOVE oldPinnedPost.groupPinned
DELETE pinned`,
variables: { user: context.user, params },
})
// If MAX_GROUP_PINNED_POSTS !== 1 -> Check if max is reached
} else {
const result = await context.database.query({
query: `
MATCH (post:Post {id: $params.id})-[:IN]->(group:Group)
MATCH (:User)-[pinned:GROUP_PINNED]->(pinnedPosts:Post)-[:IN]->(:Group {id: group.id})
RETURN toString(count(pinnedPosts)) as count`,
variables: { user: context.user, params },
})
if (result.records[0].get('count') >= config.MAX_GROUP_PINNED_POSTS) {
throw new Error('Reached maxed pinned posts already. Unpin a post first.')
}
}
// Set new pin
const result = await context.database.write({
query: `
MATCH (user:User {id: $user.id})
MATCH (post:Post {id: $params.id})-[:IN]->(group:Group)
MERGE (user)-[pinned:GROUP_PINNED {createdAt: toString(datetime())}]->(post)
SET post.groupPinned = true
RETURN post {.*, pinnedAt: pinned.createdAt}`,
variables: { user: context.user, params },
})
// Return post
return result.records[0].get('post')
},
unpinGroupPost: async (_parent, params, context, _resolveInfo) => {
const result = await context.database.write({
query: `
MATCH (post:Post {id: $postId})
OPTIONAL MATCH (:User)-[pinned:GROUP_PINNED]->(post)
DELETE pinned
REMOVE post.groupPinned
RETURN post {.*}`,
variables: { postId: params.id },
})
// Return post
return result.records[0].get('post')
},
markTeaserAsViewed: async (_parent, params, context, _resolveInfo) => { markTeaserAsViewed: async (_parent, params, context, _resolveInfo) => {
const session = context.driver.session() const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async (transaction) => { const writeTxResultPromise = session.writeTransaction(async (transaction) => {
@ -550,6 +627,7 @@ export default {
'language', 'language',
'pinnedAt', 'pinnedAt',
'pinned', 'pinned',
'groupPinned',
'eventVenue', 'eventVenue',
'eventLocation', 'eventLocation',
'eventLocationName', 'eventLocationName',
@ -589,6 +667,21 @@ export default {
'MATCH (this)<-[obs:OBSERVES]-(related:User {id: $cypherParams.currentUserId}) WHERE obs.active = true RETURN COUNT(related) >= 1', 'MATCH (this)<-[obs:OBSERVES]-(related:User {id: $cypherParams.currentUserId}) WHERE obs.active = true RETURN COUNT(related) >= 1',
}, },
}), }),
// As long as we rely on the filter capabilities of the neo4jgraphql library,
// we cannot filter on a relation or their properties.
// Hence we need to save the value to the group node in the database.
/* groupPinned: async (parent, _params, context, _resolveInfo) => {
return (
(
await context.database.query({
query: `
MATCH (:User)-[pinned:GROUP_PINNED]->(:Post {id: $parent.id})
RETURN pinned`,
variables: { parent },
})
).records.length === 1
)
}, */
relatedContributions: async (parent, _params, context, _resolveInfo) => { relatedContributions: async (parent, _params, context, _resolveInfo) => {
if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions if (typeof parent.relatedContributions !== 'undefined') return parent.relatedContributions
const { id } = parent const { id } = parent

View File

@ -1,12 +1,11 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import gql from 'graphql-tag'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import EmailAddress from '@db/models/EmailAddress' import EmailAddress from '@db/models/EmailAddress'
import User from '@db/models/User' import User from '@db/models/User'
import { Signup } from '@graphql/queries/Signup' import { Signup } from '@graphql/queries/Signup'
import { SignupVerification } from '@graphql/queries/SignupVerification'
import type { ApolloTestSetup } from '@root/test/helpers' import type { ApolloTestSetup } from '@root/test/helpers'
import { createApolloTestSetup } from '@root/test/helpers' import { createApolloTestSetup } from '@root/test/helpers'
import type { Context } from '@src/context' import type { Context } from '@src/context'
@ -158,31 +157,6 @@ describe('Signup', () => {
}) })
describe('SignupVerification', () => { describe('SignupVerification', () => {
const mutation = gql`
mutation (
$name: String!
$password: String!
$email: String!
$nonce: String!
$about: String
$termsAndConditionsAgreedVersion: String!
$locale: String
) {
SignupVerification(
name: $name
password: $password
email: $email
nonce: $nonce
about: $about
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
locale: $locale
) {
id
termsAndConditionsAgreedVersion
termsAndConditionsAgreedAt
}
}
`
describe('given valid password and email', () => { describe('given valid password and email', () => {
beforeEach(() => { beforeEach(() => {
variables = { variables = {
@ -219,7 +193,9 @@ describe('SignupVerification', () => {
}) })
it('rejects', async () => { it('rejects', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(
mutate({ mutation: SignupVerification, variables }),
).resolves.toMatchObject({
errors: [{ message: 'Invalid email or nonce' }], errors: [{ message: 'Invalid email or nonce' }],
}) })
}) })
@ -237,7 +213,9 @@ describe('SignupVerification', () => {
describe('sending a valid nonce', () => { describe('sending a valid nonce', () => {
it('creates a user account', async () => { it('creates a user account', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(
mutate({ mutation: SignupVerification, variables }),
).resolves.toMatchObject({
data: { data: {
SignupVerification: expect.objectContaining({ SignupVerification: expect.objectContaining({
id: expect.any(String), id: expect.any(String),
@ -247,7 +225,7 @@ describe('SignupVerification', () => {
}) })
it('sets `verifiedAt` attribute of EmailAddress', async () => { it('sets `verifiedAt` attribute of EmailAddress', async () => {
await mutate({ mutation, variables }) await mutate({ mutation: SignupVerification, variables })
const email = await database.neode.first( const email = await database.neode.first(
'EmailAddress', 'EmailAddress',
{ email: 'john@example.org' }, { email: 'john@example.org' },
@ -265,14 +243,14 @@ describe('SignupVerification', () => {
MATCH(email:EmailAddress)-[:BELONGS_TO]->(u:User {name: $name}) MATCH(email:EmailAddress)-[:BELONGS_TO]->(u:User {name: $name})
RETURN email RETURN email
` `
await mutate({ mutation, variables }) await mutate({ mutation: SignupVerification, variables })
const { records: emails } = await database.neode.cypher(cypher, { name: 'John Doe' }) const { records: emails } = await database.neode.cypher(cypher, { name: 'John Doe' })
expect(emails).toHaveLength(1) expect(emails).toHaveLength(1)
}) })
it('sets `about` attribute of User', async () => { it('sets `about` attribute of User', async () => {
variables = { ...variables, about: 'Find this description in the user profile' } variables = { ...variables, about: 'Find this description in the user profile' }
await mutate({ mutation, variables }) await mutate({ mutation: SignupVerification, variables })
const user = await database.neode.first<typeof User>( const user = await database.neode.first<typeof User>(
'User', 'User',
{ name: 'John Doe' }, { name: 'John Doe' },
@ -285,7 +263,9 @@ describe('SignupVerification', () => {
it('allowing the about field to be an empty string', async () => { it('allowing the about field to be an empty string', async () => {
variables = { ...variables, about: '' } variables = { ...variables, about: '' }
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(
mutate({ mutation: SignupVerification, variables }),
).resolves.toMatchObject({
data: { data: {
SignupVerification: expect.objectContaining({ SignupVerification: expect.objectContaining({
id: expect.any(String), id: expect.any(String),
@ -299,13 +279,15 @@ describe('SignupVerification', () => {
MATCH(email:EmailAddress)<-[:PRIMARY_EMAIL]-(u:User {name: $name}) MATCH(email:EmailAddress)<-[:PRIMARY_EMAIL]-(u:User {name: $name})
RETURN email RETURN email
` `
await mutate({ mutation, variables }) await mutate({ mutation: SignupVerification, variables })
const { records: emails } = await database.neode.cypher(cypher, { name: 'John Doe' }) const { records: emails } = await database.neode.cypher(cypher, { name: 'John Doe' })
expect(emails).toHaveLength(1) expect(emails).toHaveLength(1)
}) })
it('updates termsAndConditionsAgreedVersion', async () => { it('updates termsAndConditionsAgreedVersion', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(
mutate({ mutation: SignupVerification, variables }),
).resolves.toMatchObject({
data: { data: {
SignupVerification: expect.objectContaining({ SignupVerification: expect.objectContaining({
termsAndConditionsAgreedVersion: '0.1.0', termsAndConditionsAgreedVersion: '0.1.0',
@ -315,7 +297,9 @@ describe('SignupVerification', () => {
}) })
it('updates termsAndConditionsAgreedAt', async () => { it('updates termsAndConditionsAgreedAt', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(
mutate({ mutation: SignupVerification, variables }),
).resolves.toMatchObject({
data: { data: {
SignupVerification: expect.objectContaining({ SignupVerification: expect.objectContaining({
termsAndConditionsAgreedAt: expect.any(String), termsAndConditionsAgreedAt: expect.any(String),
@ -326,7 +310,9 @@ describe('SignupVerification', () => {
it('rejects if version of terms and conditions is missing', async () => { it('rejects if version of terms and conditions is missing', async () => {
variables = { ...variables, termsAndConditionsAgreedVersion: null } variables = { ...variables, termsAndConditionsAgreedVersion: null }
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(
mutate({ mutation: SignupVerification, variables }),
).resolves.toMatchObject({
errors: [ errors: [
{ {
message: message:
@ -338,7 +324,9 @@ describe('SignupVerification', () => {
it('rejects if version of terms and conditions has wrong format', async () => { it('rejects if version of terms and conditions has wrong format', async () => {
variables = { ...variables, termsAndConditionsAgreedVersion: 'invalid version format' } variables = { ...variables, termsAndConditionsAgreedVersion: 'invalid version format' }
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(
mutate({ mutation: SignupVerification, variables }),
).resolves.toMatchObject({
errors: [{ message: 'Invalid version format!' }], errors: [{ message: 'Invalid version format!' }],
}) })
}) })
@ -350,7 +338,9 @@ describe('SignupVerification', () => {
}) })
it('rejects', async () => { it('rejects', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(
mutate({ mutation: SignupVerification, variables }),
).resolves.toMatchObject({
errors: [{ message: 'Invalid email or nonce' }], errors: [{ message: 'Invalid email or nonce' }],
}) })
}) })

View File

@ -0,0 +1,103 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ApolloServerTestClient, createTestClient } from 'apollo-server-testing'
import Factory, { cleanDatabase } from '@db/factories'
import { getDriver, getNeode } from '@db/neo4j'
import { availableRoles } from '@graphql/queries/availableRoles'
import createServer from '@src/server'
const instance = getNeode()
const driver = getDriver()
describe('availableRoles', () => {
let authenticatedUser
let query: ApolloServerTestClient['query']
beforeAll(async () => {
await cleanDatabase()
const { server } = createServer({
context: () => {
return {
driver,
neode: instance,
user: authenticatedUser,
}
},
})
query = createTestClient(server).query
})
afterAll(async () => {
await cleanDatabase()
await driver.close()
})
afterEach(async () => {
await cleanDatabase()
})
describe('unauthenticated', () => {
it('throws authorization error', async () => {
authenticatedUser = null
const { data, errors } = await query({ query: availableRoles })
expect(data).toEqual(null)
expect(errors).toEqual([expect.objectContaining({ message: 'Not Authorized!' })])
})
})
describe('authenticated', () => {
describe('as user', () => {
beforeEach(async () => {
const user = await Factory.build(
'user',
{ id: 'user-id', role: 'user' },
{ email: 'user@example.org', password: '1234' },
)
authenticatedUser = await user.toJson()
})
it('throws authorization error', async () => {
const { data, errors } = await query({ query: availableRoles })
expect(data).toEqual(null)
expect(errors).toEqual([expect.objectContaining({ message: 'Not Authorized!' })])
})
})
describe('as moderator', () => {
beforeEach(async () => {
const moderator = await Factory.build(
'user',
{ id: 'moderator-id', role: 'moderator' },
{ email: 'moderator@example.org', password: '1234' },
)
authenticatedUser = await moderator.toJson()
})
it('throws authorization error', async () => {
const { data, errors } = await query({ query: availableRoles })
expect(data).toEqual(null)
expect(errors).toEqual([expect.objectContaining({ message: 'Not Authorized!' })])
})
})
describe('as admin', () => {
beforeEach(async () => {
const admin = await Factory.build(
'user',
{ id: 'admin-id', role: 'admin' },
{ email: 'admin@example.org', password: '1234' },
)
authenticatedUser = await admin.toJson()
})
it('returns available roles', async () => {
const { data, errors } = await query({ query: availableRoles })
expect(errors).toBeUndefined()
expect(data?.availableRoles).toEqual(['admin', 'moderator', 'user'])
})
})
})
})

View File

@ -2,10 +2,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j' import { getNeode, getDriver } from '@db/neo4j'
import { searchPosts } from '@graphql/queries/searchPosts'
import { searchResults } from '@graphql/queries/searchResults' import { searchResults } from '@graphql/queries/searchResults'
import createServer from '@src/server' import createServer from '@src/server'
@ -34,19 +34,6 @@ afterAll(async () => {
await driver.close() await driver.close()
neode.close() neode.close()
}) })
const searchPostQuery = gql`
query ($query: String!, $firstPosts: Int, $postsOffset: Int) {
searchPosts(query: $query, firstPosts: $firstPosts, postsOffset: $postsOffset) {
postCount
posts {
__typename
id
title
content
}
}
}
`
describe('resolvers/searches', () => { describe('resolvers/searches', () => {
let variables let variables
@ -605,7 +592,7 @@ und hinter tausend Stäben keine Welt.`,
describe('query with limit 1', () => { describe('query with limit 1', () => {
it('has a count greater than 1', async () => { it('has a count greater than 1', async () => {
variables = { query: 'beitrag', firstPosts: 1, postsOffset: 0 } variables = { query: 'beitrag', firstPosts: 1, postsOffset: 0 }
await expect(query({ query: searchPostQuery, variables })).resolves.toMatchObject({ await expect(query({ query: searchPosts, variables })).resolves.toMatchObject({
data: { data: {
searchPosts: { searchPosts: {
postCount: 2, postCount: 2,

View File

@ -3,10 +3,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j' import { getNeode, getDriver } from '@db/neo4j'
import { Post } from '@graphql/queries/Post'
import { shout } from '@graphql/queries/shout' import { shout } from '@graphql/queries/shout'
import { unshout } from '@graphql/queries/unshout' import { unshout } from '@graphql/queries/unshout'
import createServer from '@src/server' import createServer from '@src/server'
@ -14,16 +14,6 @@ import createServer from '@src/server'
let mutate, query, authenticatedUser, variables let mutate, query, authenticatedUser, variables
const instance = getNeode() const instance = getNeode()
const driver = getDriver() const driver = getDriver()
const queryPost = gql`
query ($id: ID!) {
Post(id: $id) {
id
shoutedBy {
id
}
}
}
`
describe('shout and unshout posts', () => { describe('shout and unshout posts', () => {
let currentUser, postAuthor let currentUser, postAuthor
@ -38,6 +28,9 @@ describe('shout and unshout posts', () => {
driver, driver,
neode: instance, neode: instance,
user: authenticatedUser, user: authenticatedUser,
cypherParams: {
currentUserId: authenticatedUser ? authenticatedUser.id : null,
},
} }
}, },
}) })
@ -122,7 +115,7 @@ describe('shout and unshout posts', () => {
await expect(mutate({ mutation: shout, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: shout, variables })).resolves.toMatchObject({
data: { shout: true }, data: { shout: true },
}) })
await expect(query({ query: queryPost, variables })).resolves.toMatchObject({ await expect(query({ query: Post, variables })).resolves.toMatchObject({
data: { Post: [{ id: 'another-user-post-id', shoutedBy: [{ id: 'current-user-id' }] }] }, data: { Post: [{ id: 'another-user-post-id', shoutedBy: [{ id: 'current-user-id' }] }] },
errors: undefined, errors: undefined,
}) })
@ -149,7 +142,7 @@ describe('shout and unshout posts', () => {
await expect(mutate({ mutation: shout, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: shout, variables })).resolves.toMatchObject({
data: { shout: false }, data: { shout: false },
}) })
await expect(query({ query: queryPost, variables })).resolves.toMatchObject({ await expect(query({ query: Post, variables })).resolves.toMatchObject({
data: { Post: [{ id: 'current-user-post-id', shoutedBy: [] }] }, data: { Post: [{ id: 'current-user-post-id', shoutedBy: [] }] },
errors: undefined, errors: undefined,
}) })
@ -191,7 +184,7 @@ describe('shout and unshout posts', () => {
await expect(mutate({ mutation: unshout, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: unshout, variables })).resolves.toMatchObject({
data: { unshout: true }, data: { unshout: true },
}) })
await expect(query({ query: queryPost, variables })).resolves.toMatchObject({ await expect(query({ query: Post, variables })).resolves.toMatchObject({
data: { Post: [{ id: 'posted-by-another-user', shoutedBy: [] }] }, data: { Post: [{ id: 'posted-by-another-user', shoutedBy: [] }] },
errors: undefined, errors: undefined,
}) })

View File

@ -4,10 +4,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import { getDriver } from '@db/neo4j' import { getDriver } from '@db/neo4j'
import { CreateSocialMedia } from '@graphql/queries/CreateSocialMedia'
import { DeleteSocialMedia } from '@graphql/queries/DeleteSocialMedia' import { DeleteSocialMedia } from '@graphql/queries/DeleteSocialMedia'
import { UpdateSocialMedia } from '@graphql/queries/UpdateSocialMedia' import { UpdateSocialMedia } from '@graphql/queries/UpdateSocialMedia'
import createServer from '@src/server' import createServer from '@src/server'
@ -84,24 +84,16 @@ describe('SocialMedia', () => {
}) })
describe('create social media', () => { describe('create social media', () => {
let mutation, variables let variables
beforeEach(() => { beforeEach(() => {
mutation = gql`
mutation ($url: String!) {
CreateSocialMedia(url: $url) {
id
url
}
}
`
variables = { url } variables = { url }
}) })
describe('unauthenticated', () => { describe('unauthenticated', () => {
it('throws authorization error', async () => { it('throws authorization error', async () => {
const user = null const user = null
const result = await socialMediaAction(user, mutation, variables) const result = await socialMediaAction(user, CreateSocialMedia, variables)
expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!') expect(result.errors[0]).toHaveProperty('message', 'Not Authorized!')
}) })
@ -115,21 +107,19 @@ describe('SocialMedia', () => {
}) })
it('creates social media with the given url', async () => { it('creates social media with the given url', async () => {
await expect(socialMediaAction(user, mutation, variables)).resolves.toEqual( await expect(socialMediaAction(user, CreateSocialMedia, variables)).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { CreateSocialMedia: {
CreateSocialMedia: { id: expect.any(String),
id: expect.any(String), url,
url,
},
}, },
}), },
) })
}) })
it('rejects an empty string as url', async () => { it('rejects an empty string as url', async () => {
variables = { url: '' } variables = { url: '' }
const result = await socialMediaAction(user, mutation, variables) const result = await socialMediaAction(user, CreateSocialMedia, variables)
expect(result.errors[0].message).toEqual( expect(result.errors[0].message).toEqual(
expect.stringContaining('"url" is not allowed to be empty'), expect.stringContaining('"url" is not allowed to be empty'),
@ -138,7 +128,7 @@ describe('SocialMedia', () => {
it('rejects invalid urls', async () => { it('rejects invalid urls', async () => {
variables = { url: 'not-a-url' } variables = { url: 'not-a-url' }
const result = await socialMediaAction(user, mutation, variables) const result = await socialMediaAction(user, CreateSocialMedia, variables)
expect(result.errors[0].message).toEqual( expect(result.errors[0].message).toEqual(
expect.stringContaining('"url" must be a valid uri'), expect.stringContaining('"url" must be a valid uri'),
@ -147,28 +137,13 @@ describe('SocialMedia', () => {
}) })
describe('ownedBy', () => { describe('ownedBy', () => {
beforeEach(() => {
mutation = gql`
mutation ($url: String!) {
CreateSocialMedia(url: $url) {
url
ownedBy {
name
}
}
}
`
})
it('resolves', async () => { it('resolves', async () => {
const user = someUser const user = someUser
await expect(socialMediaAction(user, mutation, variables)).resolves.toEqual( await expect(socialMediaAction(user, CreateSocialMedia, variables)).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { CreateSocialMedia: { url, ownedBy: { name: 'Kalle Blomqvist' } },
CreateSocialMedia: { url, ownedBy: { name: 'Kalle Blomqvist' } }, },
}, })
}),
)
}) })
}) })
}) })

View File

@ -6,12 +6,12 @@
/* eslint-disable promise/prefer-await-to-callbacks */ /* eslint-disable promise/prefer-await-to-callbacks */
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable jest/unbound-method */ /* eslint-disable jest/unbound-method */
import gql from 'graphql-tag'
import { verify } from 'jsonwebtoken' import { verify } from 'jsonwebtoken'
import { categories } from '@constants/categories' import { categories } from '@constants/categories'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import { changePassword } from '@graphql/queries/changePassword' import { changePassword } from '@graphql/queries/changePassword'
import { currentUser } from '@graphql/queries/currentUser'
import { login } from '@graphql/queries/login' import { login } from '@graphql/queries/login'
import { saveCategorySettings } from '@graphql/queries/saveCategorySettings' import { saveCategorySettings } from '@graphql/queries/saveCategorySettings'
import { decode } from '@jwt/decode' import { decode } from '@jwt/decode'
@ -86,24 +86,8 @@ afterEach(async () => {
}) })
describe('currentUser', () => { describe('currentUser', () => {
const currentUserQuery = gql`
{
currentUser {
id
slug
name
avatar {
url
}
email
role
activeCategories
}
}
`
const respondsWith = async (expected) => { const respondsWith = async (expected) => {
await expect(query({ query: currentUserQuery, variables })).resolves.toMatchObject(expected) await expect(query({ query: currentUser, variables })).resolves.toMatchObject(expected)
} }
describe('unauthenticated', () => { describe('unauthenticated', () => {
@ -211,7 +195,7 @@ describe('currentUser', () => {
}) })
it('returns only the saved active categories', async () => { it('returns only the saved active categories', async () => {
const result = await query({ query: currentUserQuery, variables }) const result = await query({ query: currentUser, variables })
expect(result.data?.currentUser.activeCategories).toHaveLength(4) expect(result.data?.currentUser.activeCategories).toHaveLength(4)
expect(result.data?.currentUser.activeCategories).toContain('cat1') expect(result.data?.currentUser.activeCategories).toContain('cat1')
expect(result.data?.currentUser.activeCategories).toContain('cat3') expect(result.data?.currentUser.activeCategories).toContain('cat3')

View File

@ -1,12 +1,9 @@
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { AuthenticationError } from 'apollo-server' import { AuthenticationError } from 'apollo-server'
import bcrypt from 'bcryptjs' import bcrypt from 'bcryptjs'
import { neo4jgraphql } from 'neo4j-graphql-js'
import { getNeode } from '@db/neo4j' import { getNeode } from '@db/neo4j'
import { encode } from '@jwt/encode' import { encode } from '@jwt/encode'
@ -18,8 +15,21 @@ const neode = getNeode()
export default { export default {
Query: { Query: {
currentUser: async (object, params, context, resolveInfo) => currentUser: async (_object, _params, context: Context, _resolveInfo) => {
neo4jgraphql(object, { id: context.user.id }, context, resolveInfo), if (!context.user) {
throw new Error('You must be logged in')
}
const [user] = (
await context.database.query({
query: `
MATCH (user:User {id: $user.id})-[:PRIMARY_EMAIL]->(e:EmailAddress)
RETURN user {.*, email: e.email}
`,
variables: { user: context.user },
})
).records.map((record) => record.get('user'))
return user
},
}, },
Mutation: { Mutation: {
login: async (_, { email, password }, context: Context) => { login: async (_, { email, password }, context: Context) => {

View File

@ -3,8 +3,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
import gql from 'graphql-tag'
import { categories } from '@constants/categories' import { categories } from '@constants/categories'
import pubsubContext from '@context/pubsub' import pubsubContext from '@context/pubsub'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
@ -15,6 +13,8 @@ import { saveCategorySettings } from '@graphql/queries/saveCategorySettings'
import { setTrophyBadgeSelected } from '@graphql/queries/setTrophyBadgeSelected' import { setTrophyBadgeSelected } from '@graphql/queries/setTrophyBadgeSelected'
import { switchUserRole } from '@graphql/queries/switchUserRole' import { switchUserRole } from '@graphql/queries/switchUserRole'
import { updateOnlineStatus } from '@graphql/queries/updateOnlineStatus' import { updateOnlineStatus } from '@graphql/queries/updateOnlineStatus'
import { UpdateUser } from '@graphql/queries/UpdateUser'
import { UserEmailNotificationSettings, User as userQuery } from '@graphql/queries/User'
import type { ApolloTestSetup } from '@root/test/helpers' import type { ApolloTestSetup } from '@root/test/helpers'
import { createApolloTestSetup } from '@root/test/helpers' import { createApolloTestSetup } from '@root/test/helpers'
import type { Context } from '@src/context' import type { Context } from '@src/context'
@ -63,21 +63,12 @@ afterEach(async () => {
describe('User', () => { describe('User', () => {
describe('query by email address', () => { describe('query by email address', () => {
let userQuery
beforeEach(async () => { beforeEach(async () => {
const user = await Factory.build('user', { const user = await Factory.build('user', {
id: 'user', id: 'user',
role: 'user', role: 'user',
}) })
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
userQuery = gql`
query ($email: String) {
User(email: $email) {
name
}
}
`
variables = { variables = {
email: 'any-email-address@example.org', email: 'any-email-address@example.org',
} }
@ -131,35 +122,7 @@ describe('User', () => {
}) })
describe('UpdateUser', () => { describe('UpdateUser', () => {
let updateUserMutation
beforeEach(async () => { beforeEach(async () => {
updateUserMutation = gql`
mutation (
$id: ID!
$name: String
$termsAndConditionsAgreedVersion: String
$locationName: String # empty string '' sets it to null
) {
UpdateUser(
id: $id
name: $name
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
locationName: $locationName
) {
id
name
termsAndConditionsAgreedVersion
termsAndConditionsAgreedAt
locationName
location {
name
nameDE
nameEN
}
}
}
`
variables = { variables = {
id: 'u47', id: 'u47',
name: 'John Doughnut', name: 'John Doughnut',
@ -196,8 +159,10 @@ describe('UpdateUser', () => {
}) })
it('is not allowed to change other user accounts', async () => { it('is not allowed to change other user accounts', async () => {
const { errors } = await mutate({ mutation: updateUserMutation, variables }) await expect(mutate({ mutation: UpdateUser, variables })).resolves.toMatchObject({
expect(errors?.[0]).toHaveProperty('message', 'Not Authorized!') data: { UpdateUser: null },
errors: [{ message: 'Not Authorized!' }],
})
}) })
}) })
@ -216,9 +181,7 @@ describe('UpdateUser', () => {
}, },
errors: undefined, errors: undefined,
} }
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject( await expect(mutate({ mutation: UpdateUser, variables })).resolves.toMatchObject(expected)
expected,
)
}) })
describe('given a new agreed version of terms and conditions', () => { describe('given a new agreed version of terms and conditions', () => {
@ -236,9 +199,7 @@ describe('UpdateUser', () => {
errors: undefined, errors: undefined,
} }
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject( await expect(mutate({ mutation: UpdateUser, variables })).resolves.toMatchObject(expected)
expected,
)
}) })
}) })
@ -257,9 +218,7 @@ describe('UpdateUser', () => {
errors: undefined, errors: undefined,
} }
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject( await expect(mutate({ mutation: UpdateUser, variables })).resolves.toMatchObject(expected)
expected,
)
}) })
}) })
@ -268,7 +227,7 @@ describe('UpdateUser', () => {
...variables, ...variables,
termsAndConditionsAgreedVersion: 'invalid version format', termsAndConditionsAgreedVersion: 'invalid version format',
} }
const { errors } = await mutate({ mutation: updateUserMutation, variables }) const { errors } = await mutate({ mutation: UpdateUser, variables })
expect(errors?.[0]).toHaveProperty('message', 'Invalid version format!') expect(errors?.[0]).toHaveProperty('message', 'Invalid version format!')
}) })
@ -276,7 +235,7 @@ describe('UpdateUser', () => {
describe('change location to "Hamburg, New Jersey, United States"', () => { describe('change location to "Hamburg, New Jersey, United States"', () => {
it('has updated location to "Hamburg, New Jersey, United States"', async () => { it('has updated location to "Hamburg, New Jersey, United States"', async () => {
variables = { ...variables, locationName: 'Hamburg, New Jersey, United States' } variables = { ...variables, locationName: 'Hamburg, New Jersey, United States' }
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: UpdateUser, variables })).resolves.toMatchObject({
data: { data: {
UpdateUser: { UpdateUser: {
locationName: 'Hamburg, New Jersey, United States', locationName: 'Hamburg, New Jersey, United States',
@ -295,7 +254,7 @@ describe('UpdateUser', () => {
describe('change location to unset location', () => { describe('change location to unset location', () => {
it('has updated location to unset location', async () => { it('has updated location to unset location', async () => {
variables = { ...variables, locationName: '' } variables = { ...variables, locationName: '' }
await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation: UpdateUser, variables })).resolves.toMatchObject({
data: { data: {
UpdateUser: { UpdateUser: {
locationName: null, locationName: null,
@ -548,15 +507,10 @@ describe('switch user role', () => {
id: 'user', id: 'user',
role: 'admin', role: 'admin',
} }
await expect(mutate({ mutation: switchUserRole, variables })).resolves.toEqual( await expect(mutate({ mutation: switchUserRole, variables })).resolves.toMatchObject({
expect.objectContaining({ data: { switchUserRole: null },
errors: [ errors: [{ message: 'Not Authorized!' }],
expect.objectContaining({ })
message: 'Not Authorized!',
}),
],
}),
)
}) })
}) })
@ -598,33 +552,6 @@ describe('switch user role', () => {
}) })
let anotherUser let anotherUser
const emailNotificationSettingsQuery = gql`
query ($id: ID!) {
User(id: $id) {
emailNotificationSettings {
type
settings {
name
value
}
}
}
}
`
const emailNotificationSettingsMutation = gql`
mutation ($id: ID!, $emailNotificationSettings: [EmailNotificationSettingsInput]!) {
UpdateUser(id: $id, emailNotificationSettings: $emailNotificationSettings) {
emailNotificationSettings {
type
settings {
name
value
}
}
}
}
`
describe('emailNotificationSettings', () => { describe('emailNotificationSettings', () => {
beforeEach(async () => { beforeEach(async () => {
@ -644,16 +571,11 @@ describe('emailNotificationSettings', () => {
authenticatedUser = await anotherUser.toJson() authenticatedUser = await anotherUser.toJson()
const targetUser = await user.toJson() const targetUser = await user.toJson()
await expect( await expect(
query({ query: emailNotificationSettingsQuery, variables: { id: targetUser.id } }), query({ query: UserEmailNotificationSettings, variables: { id: targetUser.id } }),
).resolves.toEqual( ).resolves.toMatchObject({
expect.objectContaining({ data: { User: [null] },
errors: [ errors: [{ message: 'Not Authorized!' }],
expect.objectContaining({ })
message: 'Not Authorized!',
}),
],
}),
)
}) })
}) })
@ -662,112 +584,13 @@ describe('emailNotificationSettings', () => {
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
await expect( await expect(
query({ query({
query: emailNotificationSettingsQuery, query: UserEmailNotificationSettings,
variables: { id: authenticatedUser?.id }, variables: { id: authenticatedUser?.id },
}), }),
).resolves.toEqual( ).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { User: [
User: [ {
{
emailNotificationSettings: [
{
type: 'post',
settings: [
{
name: 'commentOnObservedPost',
value: true,
},
{
name: 'mention',
value: true,
},
{
name: 'followingUsers',
value: true,
},
{
name: 'postInGroup',
value: true,
},
],
},
{
type: 'chat',
settings: [
{
name: 'chatMessage',
value: true,
},
],
},
{
type: 'group',
settings: [
{
name: 'groupMemberJoined',
value: true,
},
{
name: 'groupMemberLeft',
value: true,
},
{
name: 'groupMemberRemoved',
value: true,
},
{
name: 'groupMemberRoleChanged',
value: true,
},
],
},
],
},
],
},
}),
)
})
})
})
describe('mutate the field', () => {
const emailNotificationSettings = [{ name: 'mention', value: false }]
describe('as another user', () => {
it('throws an error', async () => {
authenticatedUser = await anotherUser.toJson()
const targetUser = await user.toJson()
await expect(
mutate({
mutation: emailNotificationSettingsMutation,
variables: { id: targetUser.id, emailNotificationSettings },
}),
).resolves.toEqual(
expect.objectContaining({
errors: [
expect.objectContaining({
message: 'Not Authorized!',
}),
],
}),
)
})
})
describe('as self', () => {
it('updates the emailNotificationSettings', async () => {
authenticatedUser = (await user.toJson()) as DecodedUser
await expect(
mutate({
mutation: emailNotificationSettingsMutation,
variables: { id: authenticatedUser.id, emailNotificationSettings },
}),
).resolves.toEqual(
expect.objectContaining({
data: {
UpdateUser: {
emailNotificationSettings: [ emailNotificationSettings: [
{ {
type: 'post', type: 'post',
@ -778,7 +601,7 @@ describe('emailNotificationSettings', () => {
}, },
{ {
name: 'mention', name: 'mention',
value: false, value: true,
}, },
{ {
name: 'followingUsers', name: 'followingUsers',
@ -822,9 +645,99 @@ describe('emailNotificationSettings', () => {
}, },
], ],
}, },
}, ],
},
})
})
})
})
describe('mutate the field', () => {
const emailNotificationSettings = [{ name: 'mention', value: false }]
describe('as another user', () => {
it('throws an error', async () => {
authenticatedUser = await anotherUser.toJson()
const targetUser = await user.toJson()
await expect(
mutate({
mutation: UpdateUser,
variables: { id: targetUser.id, emailNotificationSettings },
}), }),
) ).resolves.toMatchObject({
data: { UpdateUser: null },
errors: [{ message: 'Not Authorized!' }],
})
})
})
describe('as self', () => {
it('updates the emailNotificationSettings', async () => {
authenticatedUser = (await user.toJson()) as DecodedUser
await expect(
mutate({
mutation: UpdateUser,
variables: { id: authenticatedUser.id, emailNotificationSettings },
}),
).resolves.toMatchObject({
data: {
UpdateUser: {
emailNotificationSettings: [
{
type: 'post',
settings: [
{
name: 'commentOnObservedPost',
value: true,
},
{
name: 'mention',
value: false,
},
{
name: 'followingUsers',
value: true,
},
{
name: 'postInGroup',
value: true,
},
],
},
{
type: 'chat',
settings: [
{
name: 'chatMessage',
value: true,
},
],
},
{
type: 'group',
settings: [
{
name: 'groupMemberJoined',
value: true,
},
{
name: 'groupMemberLeft',
value: true,
},
{
name: 'groupMemberRemoved',
value: true,
},
{
name: 'groupMemberRoleChanged',
value: true,
},
],
},
],
},
},
})
}) })
}) })
}) })
@ -860,15 +773,10 @@ describe('save category settings', () => {
}) })
it('throws an error', async () => { it('throws an error', async () => {
await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual( await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toMatchObject({
expect.objectContaining({ data: { saveCategorySettings: null },
errors: [ errors: [{ message: 'Not Authorized!' }],
expect.objectContaining({ })
message: 'Not Authorized!',
}),
],
}),
)
}) })
}) })
@ -877,14 +785,6 @@ describe('save category settings', () => {
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
}) })
const userQuery = gql`
query ($id: ID) {
User(id: $id) {
activeCategories
}
}
`
describe('no categories saved', () => { describe('no categories saved', () => {
it('returns true for active categories mutation', async () => { it('returns true for active categories mutation', async () => {
await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual( await expect(mutate({ mutation: saveCategorySettings, variables })).resolves.toEqual(
@ -902,17 +802,15 @@ describe('save category settings', () => {
it('returns the active categories when user is queried', async () => { it('returns the active categories when user is queried', async () => {
await expect( await expect(
query({ query: userQuery, variables: { id: authenticatedUser?.id } }), query({ query: userQuery, variables: { id: authenticatedUser?.id } }),
).resolves.toEqual( ).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { User: [
User: [ {
{ activeCategories: expect.arrayContaining(['cat1', 'cat3', 'cat5']),
activeCategories: expect.arrayContaining(['cat1', 'cat3', 'cat5']), },
}, ],
], },
}, })
}),
)
}) })
}) })
}) })
@ -944,23 +842,21 @@ describe('save category settings', () => {
it('returns the new active categories when user is queried', async () => { it('returns the new active categories when user is queried', async () => {
await expect( await expect(
query({ query: userQuery, variables: { id: authenticatedUser?.id } }), query({ query: userQuery, variables: { id: authenticatedUser?.id } }),
).resolves.toEqual( ).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { User: [
User: [ {
{ activeCategories: expect.arrayContaining([
activeCategories: expect.arrayContaining([ 'cat10',
'cat10', 'cat11',
'cat11', 'cat12',
'cat12', 'cat8',
'cat8', 'cat9',
'cat9', ]),
]), },
}, ],
], },
}, })
}),
)
}) })
}) })
}) })
@ -984,15 +880,10 @@ describe('updateOnlineStatus', () => {
}) })
it('throws an error', async () => { it('throws an error', async () => {
await expect(mutate({ mutation: updateOnlineStatus, variables })).resolves.toEqual( await expect(mutate({ mutation: updateOnlineStatus, variables })).resolves.toMatchObject({
expect.objectContaining({ data: null,
errors: [ errors: [{ message: 'Not Authorized!' }],
expect.objectContaining({ })
message: 'Not Authorized!',
}),
],
}),
)
}) })
}) })
@ -1122,15 +1013,10 @@ describe('setTrophyBadgeSelected', () => {
mutation: setTrophyBadgeSelected, mutation: setTrophyBadgeSelected,
variables: { slot: 0, badgeId: 'trophy_bear' }, variables: { slot: 0, badgeId: 'trophy_bear' },
}), }),
).resolves.toEqual( ).resolves.toMatchObject({
expect.objectContaining({ data: { setTrophyBadgeSelected: null },
errors: [ errors: [{ message: 'Not Authorized!' }],
expect.objectContaining({ })
message: 'Not Authorized!',
}),
],
}),
)
}) })
}) })
@ -1500,15 +1386,10 @@ describe('resetTrophyBadgesSelected', () => {
}) })
it('throws an error', async () => { it('throws an error', async () => {
await expect(mutate({ mutation: resetTrophyBadgesSelected })).resolves.toEqual( await expect(mutate({ mutation: resetTrophyBadgesSelected })).resolves.toMatchObject({
expect.objectContaining({ data: { resetTrophyBadgesSelected: null },
errors: [ errors: [{ message: 'Not Authorized!' }],
expect.objectContaining({ })
message: 'Not Authorized!',
}),
],
}),
)
}) })
}) })

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
@ -353,14 +352,11 @@ export default {
session.close() session.close()
} }
}, },
updateOnlineStatus: async (_object, args, context, _resolveInfo) => { updateOnlineStatus: async (_object, args, context: Context, _resolveInfo) => {
const { status } = args const { status } = args
const {
user: { id },
} = context
const CYPHER_AWAY = ` const CYPHER_AWAY = `
MATCH (user:User {id: $id}) MATCH (user:User {id: $user.id})
WITH user, WITH user,
CASE user.lastOnlineStatus CASE user.lastOnlineStatus
WHEN 'away' THEN user.awaySince WHEN 'away' THEN user.awaySince
@ -370,16 +366,14 @@ export default {
SET user.lastOnlineStatus = $status SET user.lastOnlineStatus = $status
` `
const CYPHER_ONLINE = ` const CYPHER_ONLINE = `
MATCH (user:User {id: $id}) MATCH (user:User {id: $user.id})
SET user.awaySince = null SET user.awaySince = null
SET user.lastOnlineStatus = $status SET user.lastOnlineStatus = $status
` `
// Last Online Time is saved as `lastActiveAt` await context.database.write({
const session = context.driver.session() query: status === 'away' ? CYPHER_AWAY : CYPHER_ONLINE,
await session.writeTransaction((transaction) => { variables: { user: context.user, status },
// return transaction.run(status === 'away' ? CYPHER_AWAY : CYPHER_ONLINE, { id, status })
return transaction.run(status === 'away' ? CYPHER_AWAY : CYPHER_ONLINE, { id, status })
}) })
return true return true
@ -463,6 +457,18 @@ export default {
}, },
}, },
User: { User: {
activeCategories: async (parent, _args, context: Context, _resolveInfo) => {
return (
await context.database.query({
query: `
MATCH (category:Category)
WHERE NOT ((:User{id: $user.id})-[:NOT_INTERESTED_IN]->(category))
RETURN collect(category.id) as categories
`,
variables: { user: parent },
})
).records.map((record) => record.get('categories'))[0]
},
inviteCodes: async (_parent, _args, context: Context, _resolveInfo) => { inviteCodes: async (_parent, _args, context: Context, _resolveInfo) => {
return ( return (
await context.database.query({ await context.database.query({
@ -476,7 +482,7 @@ export default {
}) })
).records.map((record) => record.get('inviteCodes')) ).records.map((record) => record.get('inviteCodes'))
}, },
emailNotificationSettings: async (parent, _params, _context, _resolveInfo) => { emailNotificationSettings: (parent, _params, _context, _resolveInfo) => {
return [ return [
{ {
type: 'post', type: 'post',

View File

@ -2,12 +2,12 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
import gql from 'graphql-tag'
import { cleanDatabase } from '@db/factories' import { cleanDatabase } from '@db/factories'
import { blockedUsers } from '@graphql/queries/blockedUsers' import { blockedUsers } from '@graphql/queries/blockedUsers'
import { blockUser } from '@graphql/queries/blockUser' import { blockUser } from '@graphql/queries/blockUser'
import { Post } from '@graphql/queries/Post'
import { unblockUser } from '@graphql/queries/unblockUser' import { unblockUser } from '@graphql/queries/unblockUser'
import { User } from '@graphql/queries/User'
import { createApolloTestSetup } from '@root/test/helpers' import { createApolloTestSetup } from '@root/test/helpers'
import type { ApolloTestSetup } from '@root/test/helpers' import type { ApolloTestSetup } from '@root/test/helpers'
import type { Context } from '@src/context' import type { Context } from '@src/context'
@ -155,56 +155,37 @@ describe('blockUser', () => {
it('unfollows the user when blocking', async () => { it('unfollows the user when blocking', async () => {
await currentUser.relateTo(blockedUser, 'following') await currentUser.relateTo(blockedUser, 'following')
const queryUser = gql` await expect(query({ query: User, variables: { id: 'u2' } })).resolves.toMatchObject({
query {
User(id: "u2") {
id
isBlocked
followedByCurrentUser
}
}
`
await expect(query({ query: queryUser })).resolves.toMatchObject({
data: { User: [{ id: 'u2', isBlocked: false, followedByCurrentUser: true }] }, data: { User: [{ id: 'u2', isBlocked: false, followedByCurrentUser: true }] },
}) })
await mutate({ mutation: blockUser, variables: { id: 'u2' } }) await mutate({ mutation: blockUser, variables: { id: 'u2' } })
await expect(query({ query: queryUser })).resolves.toMatchObject({ await expect(query({ query: User, variables: { id: 'u2' } })).resolves.toMatchObject({
data: { User: [{ id: 'u2', isBlocked: true, followedByCurrentUser: false }] }, data: { User: [{ id: 'u2', isBlocked: true, followedByCurrentUser: false }] },
}) })
}) })
describe('given both the current user and the to-be-blocked user write a post', () => { describe('given both the current user and the to-be-blocked user write a post', () => {
let postQuery
beforeEach(async () => { beforeEach(async () => {
const post1 = await database.neode.create('Post', { const post1 = await database.neode.create('Post', {
id: 'p12', id: 'p12',
title: 'A post written by the current user', title: 'A post written by the current user',
content: 'content',
}) })
const post2 = await database.neode.create('Post', { const post2 = await database.neode.create('Post', {
id: 'p23', id: 'p23',
title: 'A post written by the blocked user', title: 'A post written by the blocked user',
content: 'content',
}) })
await Promise.all([ await Promise.all([
post1.relateTo(currentUser, 'author'), post1.relateTo(currentUser, 'author'),
post2.relateTo(blockedUser, 'author'), post2.relateTo(blockedUser, 'author'),
]) ])
postQuery = gql`
query {
Post(orderBy: createdAt_asc) {
id
title
author {
id
name
}
}
}
`
}) })
const bothPostsAreInTheNewsfeed = async () => { const bothPostsAreInTheNewsfeed = async () => {
await expect(query({ query: postQuery })).resolves.toMatchObject({ await expect(
query({ query: Post, variables: { orderBy: 'createdAt_asc' } }),
).resolves.toMatchObject({
data: { data: {
Post: [ Post: [
{ {
@ -238,7 +219,9 @@ describe('blockUser', () => {
// TODO: clarify proper behaviour // TODO: clarify proper behaviour
it("the blocked user's post still shows up in the newsfeed of the current user", async () => { it("the blocked user's post still shows up in the newsfeed of the current user", async () => {
await expect(query({ query: postQuery })).resolves.toMatchObject({ await expect(
query({ query: Post, variables: { orderBy: 'createdAt_asc' } }),
).resolves.toMatchObject({
data: { data: {
Post: [ Post: [
{ {
@ -276,19 +259,21 @@ describe('blockUser', () => {
}) })
it("the current user's post will show up in the newsfeed of the blocked user", async () => { it("the current user's post will show up in the newsfeed of the blocked user", async () => {
await expect(query({ query: postQuery })).resolves.toMatchObject({ await expect(
query({ query: Post, variables: { orderBy: 'createdAt_asc' } }),
).resolves.toMatchObject({
data: { data: {
Post: expect.arrayContaining([ Post: expect.arrayContaining([
{ expect.objectContaining({
id: 'p23', id: 'p23',
title: 'A post written by the blocked user', title: 'A post written by the blocked user',
author: { name: 'Blocked User', id: 'u2' }, author: { name: 'Blocked User', id: 'u2' },
}, }),
{ expect.objectContaining({
id: 'p12', id: 'p12',
title: 'A post written by the current user', title: 'A post written by the current user',
author: { name: 'Current User', id: 'u1' }, author: { name: 'Current User', id: 'u1' },
}, }),
]), ]),
}, },
}) })

View File

@ -1,10 +1,9 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
import gql from 'graphql-tag'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import { queryLocations } from '@graphql/queries/queryLocations' import { queryLocations } from '@graphql/queries/queryLocations'
import { UpdateUser } from '@graphql/queries/UpdateUser'
import type { ApolloTestSetup } from '@root/test/helpers' import type { ApolloTestSetup } from '@root/test/helpers'
import { createApolloTestSetup } from '@root/test/helpers' import { createApolloTestSetup } from '@root/test/helpers'
import type { Context } from '@src/context' import type { Context } from '@src/context'
@ -19,13 +18,6 @@ let query: any // eslint-disable-line @typescript-eslint/no-explicit-any
let database: ApolloTestSetup['database'] let database: ApolloTestSetup['database']
let server: ApolloTestSetup['server'] let server: ApolloTestSetup['server']
const updateUserMutation = gql`
mutation ($id: ID!, $name: String!, $locationName: String) {
UpdateUser(id: $id, name: $name, locationName: $locationName) {
locationName
}
}
`
const newlyCreatedNodesWithLocales = [ const newlyCreatedNodesWithLocales = [
{ {
city: { city: {
@ -203,7 +195,7 @@ describe('userMiddleware', () => {
name: 'Updating user', name: 'Updating user',
locationName: 'Welzheim, Baden-Württemberg, Germany', locationName: 'Welzheim, Baden-Württemberg, Germany',
} }
await mutate({ mutation: updateUserMutation, variables }) await mutate({ mutation: UpdateUser, variables })
const locations = await database.neode.cypher( const locations = await database.neode.cypher(
`MATCH (city:Location)-[:IS_IN]->(district:Location)-[:IS_IN]->(state:Location)-[:IS_IN]->(country:Location) return city {.*}, state {.*}, country {.*}`, `MATCH (city:Location)-[:IS_IN]->(district:Location)-[:IS_IN]->(state:Location)-[:IS_IN]->(country:Location) return city {.*}, state {.*}, country {.*}`,
{}, {},

View File

@ -3,13 +3,14 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag'
import { cleanDatabase } from '@db/factories' import { cleanDatabase } from '@db/factories'
import { getNeode, getDriver } from '@db/neo4j' import { getNeode, getDriver } from '@db/neo4j'
import { mutedUsers } from '@graphql/queries/mutedUsers' import { mutedUsers } from '@graphql/queries/mutedUsers'
import { muteUser } from '@graphql/queries/muteUser' import { muteUser } from '@graphql/queries/muteUser'
import { Post } from '@graphql/queries/Post'
import { unmuteUser } from '@graphql/queries/unmuteUser' import { unmuteUser } from '@graphql/queries/unmuteUser'
import { User } from '@graphql/queries/User'
import createServer from '@src/server' import createServer from '@src/server'
const driver = getDriver() const driver = getDriver()
@ -152,85 +153,68 @@ describe('muteUser', () => {
it('unfollows the user', async () => { it('unfollows the user', async () => {
await currentUser.relateTo(mutedUser, 'following') await currentUser.relateTo(mutedUser, 'following')
const queryUser = gql`
query {
User(id: "u2") {
id
isMuted
followedByCurrentUser
}
}
`
const { query } = createTestClient(server) const { query } = createTestClient(server)
await expect(query({ query: queryUser })).resolves.toEqual( await expect(query({ query: User, variables: { id: 'u2' } })).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { User: [{ id: 'u2', isMuted: false, followedByCurrentUser: true }] }, User: expect.arrayContaining([
}), expect.objectContaining({ id: 'u2', isMuted: false, followedByCurrentUser: true }),
) ]),
},
})
await muteAction({ id: 'u2' }) await muteAction({ id: 'u2' })
await expect(query({ query: queryUser })).resolves.toEqual( await expect(query({ query: User, variables: { id: 'u2' } })).resolves.toMatchObject({
expect.objectContaining({ data: {
data: { User: [{ id: 'u2', isMuted: true, followedByCurrentUser: false }] }, User: expect.arrayContaining([
}), expect.objectContaining({ id: 'u2', isMuted: true, followedByCurrentUser: false }),
) ]),
},
})
}) })
describe('given both the current user and the to-be-muted user write a post', () => { describe('given both the current user and the to-be-muted user write a post', () => {
let postQuery
beforeEach(async () => { beforeEach(async () => {
const post1 = await neode.create('Post', { const post1 = await neode.create('Post', {
id: 'p12', id: 'p12',
title: 'A post written by the current user', title: 'A post written by the current user',
content: 'content',
}) })
const post2 = await neode.create('Post', { const post2 = await neode.create('Post', {
id: 'p23', id: 'p23',
title: 'A post written by the muted user', title: 'A post written by the muted user',
content: 'content',
}) })
await Promise.all([ await Promise.all([
post1.relateTo(currentUser, 'author'), post1.relateTo(currentUser, 'author'),
post2.relateTo(mutedUser, 'author'), post2.relateTo(mutedUser, 'author'),
]) ])
postQuery = gql`
query {
Post(orderBy: createdAt_asc) {
id
title
author {
id
name
}
}
}
`
}) })
const bothPostsAreInTheNewsfeed = async () => { const bothPostsAreInTheNewsfeed = async () => {
const { query } = createTestClient(server) const { query } = createTestClient(server)
await expect(query({ query: postQuery })).resolves.toEqual( await expect(
expect.objectContaining({ query({ query: Post, variables: { orderBy: 'createdAt_asc' } }),
data: { ).resolves.toMatchObject({
Post: [ data: {
{ Post: expect.arrayContaining([
id: 'p12', expect.objectContaining({
title: 'A post written by the current user', id: 'p12',
author: { title: 'A post written by the current user',
name: 'Current User', author: {
id: 'u1', name: 'Current User',
}, id: 'u1',
}, },
{ }),
id: 'p23', expect.objectContaining({
title: 'A post written by the muted user', id: 'p23',
author: { title: 'A post written by the muted user',
name: 'Muted User', author: {
id: 'u2', name: 'Muted User',
}, id: 'u2',
}, },
], }),
}, ]),
}), },
) })
} }
describe('from the perspective of the current user', () => { describe('from the perspective of the current user', () => {
@ -243,19 +227,19 @@ describe('muteUser', () => {
it("the muted user's post won't show up in the newsfeed of the current user", async () => { it("the muted user's post won't show up in the newsfeed of the current user", async () => {
const { query } = createTestClient(server) const { query } = createTestClient(server)
await expect(query({ query: postQuery })).resolves.toEqual( await expect(
expect.objectContaining({ query({ query: Post, variables: { orderBy: 'createdAt_asc' } }),
data: { ).resolves.toMatchObject({
Post: [ data: {
{ Post: [
id: 'p12', expect.objectContaining({
title: 'A post written by the current user', id: 'p12',
author: { name: 'Current User', id: 'u1' }, title: 'A post written by the current user',
}, author: { name: 'Current User', id: 'u1' },
], }),
}, ],
}), },
) })
}) })
}) })
}) })
@ -273,24 +257,24 @@ describe('muteUser', () => {
it("the current user's post will show up in the newsfeed of the muted user", async () => { it("the current user's post will show up in the newsfeed of the muted user", async () => {
const { query } = createTestClient(server) const { query } = createTestClient(server)
await expect(query({ query: postQuery })).resolves.toEqual( await expect(
expect.objectContaining({ query({ query: Post, variables: { orderBy: 'createdAt_asc' } }),
data: { ).resolves.toMatchObject({
Post: expect.arrayContaining([ data: {
{ Post: expect.arrayContaining([
id: 'p23', expect.objectContaining({
title: 'A post written by the muted user', id: 'p23',
author: { name: 'Muted User', id: 'u2' }, title: 'A post written by the muted user',
}, author: { name: 'Muted User', id: 'u2' },
{ }),
id: 'p12', expect.objectContaining({
title: 'A post written by the current user', id: 'p12',
author: { name: 'Current User', id: 'u1' }, title: 'A post written by the current user',
}, author: { name: 'Current User', id: 'u1' },
]), }),
}, ]),
}), },
) })
}) })
}) })
}) })

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { makeAugmentedSchema } from 'neo4j-graphql-js' import { makeAugmentedSchema } from 'neo4j-graphql-js'
import typeDefs from '@graphql/types/index' import typeDefs from '@graphql/types/index'

View File

@ -0,0 +1,14 @@
# directive @MutationMeta on FIELD_DEFINITION
# directive @isAuthenticated on FIELD_DEFINITION
# directive @hasRole on FIELD_DEFINITION
# directive @hasScope on FIELD_DEFINITION
# directive @additionalLabels on FIELD_DEFINITION
directive @cypher(statement: String) on FIELD_DEFINITION
directive @relation(
name: String
from: String
to: String
direction: String
) on FIELD_DEFINITION | OBJECT
directive @neo4j_ignore on FIELD_DEFINITION

View File

@ -2,4 +2,4 @@ enum EmailNotificationSettingsType {
post post
chat chat
group group
} }

View File

@ -4,4 +4,4 @@ enum Emotion {
happy happy
angry angry
funny funny
} }

View File

@ -1,4 +1,4 @@
enum ShoutTypeEnum { enum ShoutTypeEnum {
Post Post
Comment Comment
} }

View File

@ -2,4 +2,4 @@ enum Visibility {
public public
friends friends
private private
} }

View File

@ -50,12 +50,16 @@ type Comment {
isPostObservedByMe: Boolean! isPostObservedByMe: Boolean!
@cypher( @cypher(
statement: "MATCH (this)-[:COMMENTS]->(:Post)<-[obs:OBSERVES]-(u:User {id: $cypherParams.currentUserId}) WHERE obs.active = true RETURN COUNT(u) >= 1" statement: "MATCH (this)-[:COMMENTS]->(:Post)<-[obs:OBSERVES]-(u:User {id: $cypherParams.currentUserId}) WHERE obs.active = true RETURN COUNT(u) >= 1"
) )
postObservingUsersCount: Int! postObservingUsersCount: Int!
@cypher(statement: "MATCH (this)-[:COMMENTS]->(:Post)<-[obs:OBSERVES]-(u:User) WHERE obs.active = true AND NOT u.disabled = true AND NOT u.deleted = true RETURN COUNT(DISTINCT u)") @cypher(
statement: "MATCH (this)-[:COMMENTS]->(:Post)<-[obs:OBSERVES]-(u:User) WHERE obs.active = true AND NOT u.disabled = true AND NOT u.deleted = true RETURN COUNT(DISTINCT u)"
)
shoutedByCurrentUser: Boolean! shoutedByCurrentUser: Boolean!
@cypher(statement: "MATCH (this) RETURN EXISTS((this)<-[:SHOUTED]-(:User {id: $cypherParams.currentUserId}))") @cypher(
statement: "MATCH (this) RETURN EXISTS((this)<-[:SHOUTED]-(:User {id: $cypherParams.currentUserId}))"
)
shoutedCount: Int! shoutedCount: Int!
@cypher( @cypher(
@ -77,16 +81,7 @@ type Query {
} }
type Mutation { type Mutation {
CreateComment( CreateComment(id: ID, postId: ID!, content: String!, contentExcerpt: String): Comment
id: ID UpdateComment(id: ID!, content: String!, contentExcerpt: String): Comment
postId: ID!
content: String!
contentExcerpt: String
): Comment
UpdateComment(
id: ID!
content: String!
contentExcerpt: String
): Comment
DeleteComment(id: ID!): Comment DeleteComment(id: ID!): Comment
} }

View File

@ -13,4 +13,4 @@ type Query {
type Mutation { type Mutation {
UpdateDonations(showDonations: Boolean, goal: Int, progress: Int): Donations UpdateDonations(showDonations: Boolean, goal: Int, progress: Int): Donations
} }

View File

@ -9,11 +9,7 @@ type Query {
} }
type Mutation { type Mutation {
Signup( Signup(email: String!, locale: String!, inviteCode: String = null): EmailAddress
email: String!
locale: String!
inviteCode: String = null
): EmailAddress
SignupVerification( SignupVerification(
nonce: String! nonce: String!
email: String! email: String!
@ -27,8 +23,5 @@ type Mutation {
locationName: String = null locationName: String = null
): User ): User
AddEmailAddress(email: String!): EmailAddress AddEmailAddress(email: String!): EmailAddress
VerifyEmailAddress( VerifyEmailAddress(nonce: String!, email: String!): EmailAddress
nonce: String!
email: String!
): EmailAddress
} }

View File

@ -5,7 +5,7 @@ type FILED {
submitter: User submitter: User
} }
# this list equals the strings of an array in file "webapp/constants/modals.js" "this list equals the strings of an array in file `webapp/constants/modals.js`"
enum ReasonCategory { enum ReasonCategory {
other other
discrimination_etc discrimination_etc
@ -26,5 +26,9 @@ type FiledReport {
} }
type Mutation { type Mutation {
fileReport(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): FiledReport fileReport(
} resourceId: ID!
reasonCategory: ReasonCategory!
reasonDescription: String!
): FiledReport
}

View File

@ -1,16 +1,16 @@
type File { type File {
url: ID!, url: ID!
name: String, name: String
#size: Int, type: String
type: String, # size: Int
#audio: Boolean, # audio: Boolean
#duration: Float, # duration: Float
#preview: String, # preview: String
#progress: Int, # progress: Int
} }
input FileInput { input FileInput {
upload: Upload, upload: Upload
name: String, name: String
type: String, type: String
} }

View File

@ -1,19 +1,19 @@
enum _GroupOrdering { # enum _GroupOrdering {
id_asc # id_asc
id_desc # id_desc
name_asc # name_asc
name_desc # name_desc
slug_asc # slug_asc
slug_desc # slug_desc
locationName_asc # locationName_asc
locationName_desc # locationName_desc
about_asc # about_asc
about_desc # about_desc
createdAt_asc # createdAt_asc
createdAt_desc # createdAt_desc
updatedAt_asc # updatedAt_asc
updatedAt_desc # updatedAt_desc
} # }
type Group { type Group {
id: ID! id: ID!
@ -38,18 +38,27 @@ type Group {
categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT") categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT")
membersCount: Int! @cypher(statement: "MATCH (this)<-[:MEMBER_OF]-(r:User) RETURN COUNT(DISTINCT r)") membersCount: Int!
@cypher(statement: "MATCH (this)<-[:MEMBER_OF]-(r:User) RETURN COUNT(DISTINCT r)")
myRole: GroupMemberRole # if 'null' then the current user is no member myRole: GroupMemberRole # if 'null' then the current user is no member
posts: [Post] @relation(name: "IN", direction: "IN") posts: [Post] @relation(name: "IN", direction: "IN")
isMutedByMe: Boolean! @cypher(statement: "MATCH (this) RETURN EXISTS( (this)<-[:MUTED]-(:User {id: $cypherParams.currentUserId}) )") isMutedByMe: Boolean!
@cypher(
statement: "MATCH (this) RETURN EXISTS( (this)<-[:MUTED]-(:User {id: $cypherParams.currentUserId}) )"
)
"inviteCodes to this group the current user has generated" "inviteCodes to this group the current user has generated"
inviteCodes: [InviteCode]! @neo4j_ignore inviteCodes: [InviteCode]! @neo4j_ignore
currentlyPinnedPostsCount: Int! @neo4j_ignore
} }
type GroupMember {
user: User
membership: MEMBER_OF
}
input _GroupFilter { input _GroupFilter {
AND: [_GroupFilter!] AND: [_GroupFilter!]
@ -74,20 +83,16 @@ type Query {
slug: String slug: String
first: Int first: Int
offset: Int offset: Int
# orderBy: [_GroupOrdering] # not implemented yet
# filter: _GroupFilter # not implemented yet
): [Group] ): [Group]
# orderBy: [_GroupOrdering] # not implemented yet
# filter: _GroupFilter # not implemented yet
GroupMembers( GroupMembers(id: ID!, first: Int, offset: Int): [GroupMember]
id: ID! # orderBy: [_UserOrdering] # not implemented yet
first: Int # filter: _UserFilter # not implemented yet
offset: Int
# orderBy: [_UserOrdering] # not implemented yet
# filter: _UserFilter # not implemented yet
): [User]
GroupCount(isMember: Boolean): Int GroupCount(isMember: Boolean): Int
# AvailableGroupTypes: [GroupType]! # AvailableGroupTypes: [GroupType]!
# AvailableGroupActionRadii: [GroupActionRadius]! # AvailableGroupActionRadii: [GroupActionRadius]!
@ -105,7 +110,9 @@ type Mutation {
groupType: GroupType! groupType: GroupType!
actionRadius: GroupActionRadius! actionRadius: GroupActionRadius!
categoryIds: [ID] categoryIds: [ID]
# avatar: ImageInput # a group can not be created with an avatar # avatar: ImageInput # a group can not be created with an avatar
locationName: String # empty string '' sets it to null locationName: String # empty string '' sets it to null
): Group ): Group
@ -115,7 +122,9 @@ type Mutation {
slug: String slug: String
about: String about: String
description: String description: String
# groupType: GroupType # is not possible at the moment and has to be discussed. may be in the stronger direction: public → closed → hidden # groupType: GroupType # is not possible at the moment and has to be discussed. may be in the stronger direction: public → closed → hidden
actionRadius: GroupActionRadius actionRadius: GroupActionRadius
categoryIds: [ID] categoryIds: [ID]
avatar: ImageInput # test this as result avatar: ImageInput # test this as result
@ -124,27 +133,14 @@ type Mutation {
# DeleteGroup(id: ID!): Group # DeleteGroup(id: ID!): Group
JoinGroup( JoinGroup(groupId: ID!, userId: ID!): GroupMember
groupId: ID!
userId: ID!
): User
LeaveGroup( LeaveGroup(groupId: ID!, userId: ID!): GroupMember
groupId: ID!
userId: ID!
): User
ChangeGroupMemberRole( ChangeGroupMemberRole(groupId: ID!, userId: ID!, roleInGroup: GroupMemberRole!): GroupMember
groupId: ID!
userId: ID!
roleInGroup: GroupMemberRole!
): User
RemoveUserFromGroup( RemoveUserFromGroup(groupId: ID!, userId: ID!): GroupMember
groupId: ID!
userId: ID!
): User
muteGroup(groupId: ID!): Group muteGroup(groupId: ID!): Group
unmuteGroup(groupId: ID!): Group unmuteGroup(groupId: ID!): Group
} }

View File

@ -1,21 +1,23 @@
type Image { type Image {
url: ID!, url: ID!
transform(width: Int, height: Int): String transform(width: Int, height: Int): String
# urlW34: String, # urlW34: String,
# urlW160: String, # urlW160: String,
# urlW320: String, # urlW320: String,
# urlW640: String, # urlW640: String,
# urlW1024: String, # urlW1024: String,
alt: String,
sensitive: Boolean, alt: String
aspectRatio: Float, sensitive: Boolean
type: String, aspectRatio: Float
type: String
} }
input ImageInput { input ImageInput {
alt: String, alt: String
upload: Upload, upload: Upload
sensitive: Boolean, sensitive: Boolean
aspectRatio: Float, aspectRatio: Float
type: String, type: String
} }

View File

@ -19,7 +19,11 @@ type Query {
type Mutation { type Mutation {
generatePersonalInviteCode(expiresAt: String = null, comment: String = null): InviteCode! generatePersonalInviteCode(expiresAt: String = null, comment: String = null): InviteCode!
generateGroupInviteCode(groupId: ID!, expiresAt: String = null, comment: String = null): InviteCode! generateGroupInviteCode(
groupId: ID!
expiresAt: String = null
comment: String = null
): InviteCode!
invalidateInviteCode(code: String!): InviteCode invalidateInviteCode(code: String!): InviteCode
redeemInviteCode(code: String!): Boolean! redeemInviteCode(code: String!): Boolean!
} }

View File

@ -18,6 +18,7 @@ type Location {
} }
# This is not smart - we need one location for everything - use the same type everywhere! # This is not smart - we need one location for everything - use the same type everywhere!
type LocationMapBox { type LocationMapBox {
id: ID! id: ID!
place_name: String! place_name: String!

View File

@ -19,8 +19,11 @@ type Message {
senderId: String! @cypher(statement: "MATCH (this)<-[:CREATED]-(user:User) RETURN user.id") senderId: String! @cypher(statement: "MATCH (this)<-[:CREATED]-(user:User) RETURN user.id")
username: String! @cypher(statement: "MATCH (this)<-[:CREATED]-(user:User) RETURN user.name") username: String! @cypher(statement: "MATCH (this)<-[:CREATED]-(user:User) RETURN user.name")
avatar: String @cypher(statement: "MATCH (this)<-[:CREATED]-(:User)-[:AVATAR_IMAGE]->(image:Image) RETURN image.url") avatar: String
date: String! @cypher(statement: "RETURN this.createdAt") @cypher(
statement: "MATCH (this)<-[:CREATED]-(:User)-[:AVATAR_IMAGE]->(image:Image) RETURN image.url"
)
date: String! @cypher(statement: "RETURN this.createdAt")
saved: Boolean saved: Boolean
distributed: Boolean distributed: Boolean
@ -29,22 +32,13 @@ type Message {
} }
type Mutation { type Mutation {
CreateMessage( CreateMessage(roomId: ID!, content: String, files: [FileInput]): Message
roomId: ID!
content: String
files: [FileInput]
): Message
MarkMessagesAsSeen(messageIds: [String!]): Boolean MarkMessagesAsSeen(messageIds: [String!]): Boolean
} }
type Query { type Query {
Message( Message(roomId: ID!, first: Int, offset: Int, orderBy: [_MessageOrdering]): [Message]
roomId: ID!,
first: Int
offset: Int
orderBy: [_MessageOrdering]
): [Message]
} }
type Subscription { type Subscription {

View File

@ -33,7 +33,7 @@ enum NotificationReason {
type Query { type Query {
notifications(read: Boolean, orderBy: NotificationOrdering, first: Int, offset: Int): [NOTIFIED] notifications(read: Boolean, orderBy: NotificationOrdering, first: Int, offset: Int): [NOTIFIED]
} }
type Mutation { type Mutation {
markAsRead(id: ID!): NOTIFIED markAsRead(id: ID!): NOTIFIED
markAllAsRead: [NOTIFIED] markAllAsRead: [NOTIFIED]

View File

@ -1,3 +1,7 @@
input _CategoryFilter {
AND: [_CategoryFilter!]
OR: [_CategoryFilter!]
}
input _PostFilter { input _PostFilter {
AND: [_PostFilter!] AND: [_PostFilter!]
OR: [_PostFilter!] OR: [_PostFilter!]
@ -49,6 +53,7 @@ input _PostFilter {
language_in: [String!] language_in: [String!]
language_not_in: [String!] language_not_in: [String!]
pinned: Boolean # required for `maintainPinnedPost` pinned: Boolean # required for `maintainPinnedPost`
groupPinned: Boolean # required for `maintainGroupPinnedPost`
tags: _TagFilter tags: _TagFilter
tags_not: _TagFilter tags_not: _TagFilter
tags_in: [_TagFilter!] tags_in: [_TagFilter!]
@ -111,9 +116,10 @@ enum _PostOrdering {
pinned_desc pinned_desc
eventStart_asc eventStart_asc
eventStart_desc eventStart_desc
groupPinned_asc
groupPinned_desc
} }
type Post { type Post {
id: ID! id: ID!
activityId: String activityId: String
@ -128,14 +134,16 @@ type Post {
deleted: Boolean deleted: Boolean
disabled: Boolean disabled: Boolean
pinned: Boolean pinned: Boolean
groupPinned: Boolean
createdAt: String createdAt: String
updatedAt: String updatedAt: String
sortDate: String sortDate: String
language: String language: String
pinnedAt: String @cypher( pinnedAt: String
@cypher(
statement: "MATCH (this)<-[pinned:PINNED]-(:User) WHERE NOT this.deleted = true AND NOT this.disabled = true RETURN pinned.createdAt" statement: "MATCH (this)<-[pinned:PINNED]-(:User) WHERE NOT this.deleted = true AND NOT this.disabled = true RETURN pinned.createdAt"
) )
pinnedBy: User @relation(name:"PINNED", direction: "IN") pinnedBy: User @relation(name: "PINNED", direction: "IN")
relatedContributions: [Post]! relatedContributions: [Post]!
@cypher( @cypher(
statement: """ statement: """
@ -160,7 +168,7 @@ type Post {
statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)" statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)"
) )
# Has the currently logged in user shouted that post? "Has the currently logged in user shouted that post?"
shoutedByCurrentUser: Boolean! shoutedByCurrentUser: Boolean!
@cypher( @cypher(
statement: "MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1" statement: "MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1"
@ -173,15 +181,14 @@ type Post {
@cypher( @cypher(
statement: "MATCH (this)<-[:VIEWED_TEASER]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1" statement: "MATCH (this)<-[:VIEWED_TEASER]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1"
) )
emotions: [EMOTED] emotions: [EMOTED]
emotionsCount: Int! emotionsCount: Int!
@cypher(statement: "MATCH (this)<-[emoted:EMOTED]-(:User) RETURN COUNT(DISTINCT emoted)") @cypher(statement: "MATCH (this)<-[emoted:EMOTED]-(:User) RETURN COUNT(DISTINCT emoted)")
group: Group @relation(name: "IN", direction: "OUT") group: Group @relation(name: "IN", direction: "OUT")
postType: [PostType] postType: [PostType] @cypher(statement: "RETURN [l IN labels(this) WHERE NOT l = 'Post']")
@cypher(statement: "RETURN [l IN labels(this) WHERE NOT l = 'Post']")
eventLocationName: String eventLocationName: String
eventLocation: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") eventLocation: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l")
@ -193,9 +200,11 @@ type Post {
isObservedByMe: Boolean! isObservedByMe: Boolean!
@cypher( @cypher(
statement: "MATCH (this)<-[obs:OBSERVES]-(u:User {id: $cypherParams.currentUserId}) WHERE obs.active = true RETURN COUNT(u) >= 1" statement: "MATCH (this)<-[obs:OBSERVES]-(u:User {id: $cypherParams.currentUserId}) WHERE obs.active = true RETURN COUNT(u) >= 1"
) )
observingUsersCount: Int! observingUsersCount: Int!
@cypher(statement: "MATCH (this)<-[obs:OBSERVES]-(u:User) WHERE obs.active = true AND NOT u.deleted = true AND NOT u.disabled = true RETURN COUNT(DISTINCT u)") @cypher(
statement: "MATCH (this)<-[obs:OBSERVES]-(u:User) WHERE obs.active = true AND NOT u.deleted = true AND NOT u.disabled = true RETURN COUNT(DISTINCT u)"
)
} }
input _PostInput { input _PostInput {
@ -216,7 +225,7 @@ type Mutation {
title: String! title: String!
slug: String slug: String
content: String! content: String!
image: ImageInput, image: ImageInput
visibility: Visibility visibility: Visibility
language: String language: String
categoryIds: [ID] categoryIds: [ID]
@ -231,7 +240,7 @@ type Mutation {
slug: String slug: String
content: String! content: String!
contentExcerpt: String contentExcerpt: String
image: ImageInput, image: ImageInput
visibility: Visibility visibility: Visibility
language: String language: String
categoryIds: [ID] categoryIds: [ID]
@ -241,15 +250,19 @@ type Mutation {
DeletePost(id: ID!): Post DeletePost(id: ID!): Post
AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED AddPostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
RemovePostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED RemovePostEmotions(to: _PostInput!, data: _EMOTEDInput!): EMOTED
pinPost(id: ID!): Post pinPost(id: ID!): Post
unpinPost(id: ID!): Post unpinPost(id: ID!): Post
pinGroupPost(id: ID!): Post
unpinGroupPost(id: ID!): Post
markTeaserAsViewed(id: ID!): Post markTeaserAsViewed(id: ID!): Post
pushPost(id: ID!): Post! pushPost(id: ID!): Post!
unpushPost(id: ID!): Post! unpushPost(id: ID!): Post!
# Shout the given Type and ID "Shout the given Type and ID"
shout(id: ID!, type: ShoutTypeEnum!): Boolean! shout(id: ID!, type: ShoutTypeEnum!): Boolean!
# Unshout the given Type and ID "Unshout the given Type and ID"
unshout(id: ID!, type: ShoutTypeEnum!): Boolean! unshout(id: ID!, type: ShoutTypeEnum!): Boolean!
toggleObservePost(id: ID!, value: Boolean!): Post! toggleObservePost(id: ID!, value: Boolean!): Post!

View File

@ -17,7 +17,13 @@ enum ReportRule {
} }
type Query { type Query {
reports(orderBy: ReportOrdering, first: Int, offset: Int, reviewed: Boolean, closed: Boolean): [Report] reports(
orderBy: ReportOrdering
first: Int
offset: Int
reviewed: Boolean
closed: Boolean
): [Report]
} }
enum ReportOrdering { enum ReportOrdering {

View File

@ -6,6 +6,7 @@
# } # }
# TODO change this to last message date # TODO change this to last message date
enum _RoomOrdering { enum _RoomOrdering {
lastMessageAt_desc lastMessageAt_desc
createdAt_desc createdAt_desc
@ -19,41 +20,48 @@ type Room {
users: [User]! @relation(name: "CHATS_IN", direction: "IN") users: [User]! @relation(name: "CHATS_IN", direction: "IN")
roomId: String! @cypher(statement: "RETURN this.id") roomId: String! @cypher(statement: "RETURN this.id")
roomName: String! @cypher(statement: "MATCH (this)<-[:CHATS_IN]-(user:User) WHERE NOT user.id = $cypherParams.currentUserId RETURN user.name") roomName: String!
avatar: String @cypher(statement: """ @cypher(
MATCH (this)<-[:CHATS_IN]-(user:User) statement: "MATCH (this)<-[:CHATS_IN]-(user:User) WHERE NOT user.id = $cypherParams.currentUserId RETURN user.name"
WHERE NOT user.id = $cypherParams.currentUserId )
OPTIONAL MATCH (user)-[:AVATAR_IMAGE]->(image:Image) avatar: String
RETURN image.url @cypher(
""") statement: """
MATCH (this)<-[:CHATS_IN]-(user:User)
WHERE NOT user.id = $cypherParams.currentUserId
OPTIONAL MATCH (user)-[:AVATAR_IMAGE]->(image:Image)
RETURN image.url
"""
)
lastMessageAt: String lastMessageAt: String
lastMessage: Message @cypher(statement: """ lastMessage: Message
MATCH (this)<-[:INSIDE]-(message:Message) @cypher(
WITH message ORDER BY message.indexId DESC LIMIT 1 statement: """
RETURN message MATCH (this)<-[:INSIDE]-(message:Message)
""") WITH message ORDER BY message.indexId DESC LIMIT 1
RETURN message
"""
)
unreadCount: Int @cypher(statement: """ unreadCount: Int
MATCH (this)<-[:INSIDE]-(message:Message)<-[:CREATED]-(user:User) @cypher(
WHERE NOT user.id = $cypherParams.currentUserId statement: """
AND NOT message.seen MATCH (this)<-[:INSIDE]-(message:Message)<-[:CREATED]-(user:User)
RETURN count(message) WHERE NOT user.id = $cypherParams.currentUserId
""") AND NOT message.seen
RETURN count(message)
"""
)
} }
type Mutation { type Mutation {
CreateRoom( CreateRoom(userId: ID!): Room
userId: ID!
): Room
} }
type Query { type Query {
Room( Room(id: ID, orderBy: [_RoomOrdering]): [Room]
id: ID
orderBy: [_RoomOrdering]
): [Room]
UnreadRooms: Int UnreadRooms: Int
} }

View File

@ -25,4 +25,3 @@ type Statistics {
usersVerified: Int! usersVerified: Int!
reports: Int! reports: Int!
} }

View File

@ -19,7 +19,8 @@ type Tag {
id: ID! id: ID!
taggedPosts: [Post]! @relation(name: "TAGGED", direction: "IN") taggedPosts: [Post]! @relation(name: "TAGGED", direction: "IN")
taggedCount: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p) RETURN COUNT(DISTINCT p)") taggedCount: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p) RETURN COUNT(DISTINCT p)")
taggedCountUnique: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p)<-[:WROTE]-(u:User) RETURN COUNT(DISTINCT u)") taggedCountUnique: Int!
@cypher(statement: "MATCH (this)<-[:TAGGED]-(p)<-[:WROTE]-(u:User) RETURN COUNT(DISTINCT u)")
deleted: Boolean deleted: Boolean
disabled: Boolean disabled: Boolean
} }
@ -34,11 +35,5 @@ enum _TagOrdering {
} }
type Query { type Query {
Tag( Tag(id: ID, first: Int, offset: Int, orderBy: [_TagOrdering], filter: _TagFilter): [Tag]
id: ID
first: Int
offset: Int
orderBy: [_TagOrdering]
filter: _TagFilter
): [Tag]
} }

View File

@ -38,7 +38,8 @@ type User {
id: ID! id: ID!
actorId: String actorId: String
name: String name: String
email: String! @cypher(statement: "MATCH (this)-[:PRIMARY_EMAIL]->(e:EmailAddress) RETURN e.email") email: String!
@cypher(statement: "MATCH (this)-[:PRIMARY_EMAIL]->(e:EmailAddress) RETURN e.email")
slug: String! slug: String!
avatar: Image @relation(name: "AVATAR_IMAGE", direction: "OUT") avatar: Image @relation(name: "AVATAR_IMAGE", direction: "OUT")
deleted: Boolean deleted: Boolean
@ -64,65 +65,78 @@ type User {
emailNotificationSettings: [EmailNotificationSettings]! @neo4j_ignore emailNotificationSettings: [EmailNotificationSettings]! @neo4j_ignore
locale: String locale: String
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH") friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)") friendsCount: Int!
@cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)")
following: [User]! @relation(name: "FOLLOWS", direction: "OUT") following: [User]! @relation(name: "FOLLOWS", direction: "OUT")
followingCount: Int! @cypher(statement: "MATCH (this)-[:FOLLOWS]->(r:User) RETURN COUNT(DISTINCT r)") followingCount: Int!
@cypher(statement: "MATCH (this)-[:FOLLOWS]->(r:User) RETURN COUNT(DISTINCT r)")
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN") followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
followedByCount: Int! @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(DISTINCT r)") followedByCount: Int!
@cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(DISTINCT r)")
# Is the currently logged in user following that user? "Is the currently logged in user following that user?"
followedByCurrentUser: Boolean! @cypher( followedByCurrentUser: Boolean!
statement: """ @cypher(
MATCH (this)<-[:FOLLOWS]-(u:User { id: $cypherParams.currentUserId}) statement: "MATCH (this)<-[:FOLLOWS]-(u:User { id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1"
RETURN COUNT(u) >= 1 )
"""
)
isBlocked: Boolean! @cypher( isBlocked: Boolean!
statement: """ @cypher(
MATCH (this)<-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId}) statement: """
RETURN COUNT(user) >= 1 MATCH (this)<-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId})
""" RETURN COUNT(user) >= 1
) """
blocked: Boolean! @cypher( )
statement: """ blocked: Boolean!
MATCH (this)-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId}) @cypher(
RETURN COUNT(user) >= 1 statement: """
""" MATCH (this)-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId})
) RETURN COUNT(user) >= 1
"""
)
isMuted: Boolean!
@cypher(
statement: """
MATCH (this)<-[:MUTED]-(user:User { id: $cypherParams.currentUserId})
RETURN COUNT(user) >= 1
"""
)
isMuted: Boolean! @cypher(
statement: """
MATCH (this)<-[:MUTED]-(user:User { id: $cypherParams.currentUserId})
RETURN COUNT(user) >= 1
"""
)
# contributions: [WrittenPost]! # contributions: [WrittenPost]!
# contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]! # contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
# @cypher( # @cypher(
# statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp" # statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp"
# ) # )
contributions: [Post]! @relation(name: "WROTE", direction: "OUT") contributions: [Post]! @relation(name: "WROTE", direction: "OUT")
contributionsCount: Int! @cypher( contributionsCount: Int!
statement: """ @cypher(
MATCH (this)-[:WROTE]->(r:Post) statement: """
WHERE NOT r.deleted = true AND NOT r.disabled = true MATCH (this)-[:WROTE]->(r:Post)
RETURN COUNT(r) WHERE NOT r.deleted = true AND NOT r.disabled = true
""" RETURN COUNT(r)
) """
)
comments: [Comment]! @relation(name: "WROTE", direction: "OUT") comments: [Comment]! @relation(name: "WROTE", direction: "OUT")
commentedCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(:Comment)-[:COMMENTS]->(p:Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))") commentedCount: Int!
@cypher(
statement: "MATCH (this)-[:WROTE]->(:Comment)-[:COMMENTS]->(p:Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))"
)
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT") shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)") shoutedCount: Int!
@cypher(
statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)"
)
categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT") categories: [Category] @relation(name: "CATEGORIZED", direction: "OUT")
# Badges # Badges
badgeVerification: Badge! @neo4j_ignore badgeVerification: Badge! @neo4j_ignore
badgeTrophies: [Badge]! @relation(name: "REWARDED", direction: "IN") badgeTrophies: [Badge]! @relation(name: "REWARDED", direction: "IN")
badgeTrophiesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)") badgeTrophiesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)")
@ -133,22 +147,14 @@ type User {
"personal inviteCodes the user has generated" "personal inviteCodes the user has generated"
inviteCodes: [InviteCode]! @neo4j_ignore inviteCodes: [InviteCode]! @neo4j_ignore
# inviteCodes: [InviteCode]! @relation(name: "GENERATED", direction: "OUT") # inviteCodes: [InviteCode]! @relation(name: "GENERATED", direction: "OUT")
redeemedInviteCode: InviteCode @relation(name: "REDEEMED", direction: "OUT") redeemedInviteCode: InviteCode @relation(name: "REDEEMED", direction: "OUT")
emotions: [EMOTED] emotions: [EMOTED]
activeCategories: [String] @cypher( activeCategories: [String] @neo4j_ignore
statement: """
MATCH (category:Category)
WHERE NOT ((this)-[:NOT_INTERESTED_IN]->(category))
RETURN collect(category.id)
"""
)
myRoleInGroup: GroupMemberRole
} }
input _UserFilter { input _UserFilter {
AND: [_UserFilter!] AND: [_UserFilter!]
OR: [_UserFilter!] OR: [_UserFilter!]
@ -203,7 +209,7 @@ type Query {
filter: _UserFilter filter: _UserFilter
): [User] ): [User]
availableRoles: [UserRole]! availableRoles: [UserRole]!
mutedUsers: [User] mutedUsers: [User]
blockedUsers: [User] blockedUsers: [User]
currentUser: User! currentUser: User!
@ -215,7 +221,7 @@ enum Deletable {
} }
type Mutation { type Mutation {
UpdateUser ( UpdateUser(
id: ID! id: ID!
name: String name: String
email: String email: String
@ -245,14 +251,14 @@ type Mutation {
switchUserRole(role: UserRole!, id: ID!): User switchUserRole(role: UserRole!, id: ID!): User
saveCategorySettings(activeCategories: [String]): Boolean saveCategorySettings(activeCategories: [String]): Boolean
updateOnlineStatus(status: OnlineStatus!): Boolean! updateOnlineStatus(status: OnlineStatus!): Boolean!
requestPasswordReset(email: String!, locale: String!): Boolean! requestPasswordReset(email: String!, locale: String!): Boolean!
resetPassword(email: String!, nonce: String!, newPassword: String!): Boolean! resetPassword(email: String!, nonce: String!, newPassword: String!): Boolean!
changePassword(oldPassword: String!, newPassword: String!): String! changePassword(oldPassword: String!, newPassword: String!): String!
# Get a JWT Token for the given Email and password "Get a JWT Token for the given Email and password"
login(email: String!, password: String!): String! login(email: String!, password: String!): String!
setTrophyBadgeSelected(slot: Int!, badgeId: ID): User setTrophyBadgeSelected(slot: Int!, badgeId: ID): User

View File

@ -4,7 +4,5 @@ type UserData {
} }
type Query { type Query {
userData( userData(id: ID): UserData
id: ID
): UserData
} }

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