diff --git a/go.mod b/go.mod index f6e9da62..12b235a5 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.12.3 github.com/pressly/goose/v3 v3.27.2 + github.com/testcontainers/testcontainers-go v0.43.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.43.0 github.com/vektah/gqlparser/v2 v2.5.35 google.golang.org/grpc v1.80.0 google.golang.org/protobuf v1.36.11 @@ -26,6 +28,56 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect ) +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.7.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/klauspost/compress v1.18.5 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/moby/api v1.55.0 // indirect + github.com/moby/moby/client v0.5.0 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/shirou/gopsutil/v4 v4.26.5 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + require ( github.com/agnivade/levenshtein v1.2.1 // indirect github.com/alexedwards/scs/postgresstore v0.0.0-20251002162104-209de6e426de @@ -52,7 +104,7 @@ require ( golang.org/x/crypto v0.53.0 golang.org/x/mod v0.37.0 // indirect golang.org/x/sync v0.21.0 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/text v0.38.0 golang.org/x/tools v0.46.0 // indirect ) diff --git a/go.sum b/go.sum index 54aeac5e..3979c774 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,13 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/99designs/gqlgen v0.17.93 h1:oqLJS61cSp3YraGIowQPtyrUYfC8LpUFpQTeoFHAVKQ= github.com/99designs/gqlgen v0.17.93/go.mod h1:cXtBb9IeSiGflq69Io7DIAZY4JQJRinpIUgkNm8jEU0= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/alexedwards/scs/postgresstore v0.0.0-20251002162104-209de6e426de h1:LDrMkjj4OCCQsq9SvIPQV1l3leMxqXZTCTxDFwMrqTE= @@ -13,11 +21,25 @@ github.com/beevik/etree v1.5.0 h1:iaQZFSDS+3kYZiGoc9uKeOkUY3nYMXOKLl6KIJxiJWs= github.com/beevik/etree v1.5.0/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA= github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/crewjam/saml v0.5.1 h1:g+mfp0CrLuLRZCK793PgJcZeg5dS/0CDwoeAX2zcwNI= github.com/crewjam/saml v0.5.1/go.mod h1:r0fDkmFe5URDgPrmtH0IYokva6fac3AUdstiPhyEolQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -25,14 +47,27 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= @@ -41,6 +76,7 @@ github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXe github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -55,6 +91,14 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= @@ -64,15 +108,24 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -83,52 +136,98 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= +github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= +github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc= +github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/mochi-mqtt/server/v2 v2.7.9 h1:y0g4vrSLAag7T07l2oCzOa/+nKVLoazKEWAArwqBNYI= github.com/mochi-mqtt/server/v2 v2.7.9/go.mod h1:lZD3j35AVNqJL5cezlnSkuG05c0FCHSsfAKSPBOSbqc= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pressly/goose/v3 v3.27.2 h1:FjKNzcmMdGrQlSIu5alMSmakQtJFBgtw+A0bb1p/LC8= github.com/pressly/goose/v3 v3.27.2/go.mod h1:qWW+/8dkVtJYjJrbIpwD5xxnEJTUKvxkQ9JKQp9LaIM= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys= github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM= +github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE= github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/testcontainers/testcontainers-go v0.43.0 h1:oEQx5MW2DGd9z3AeEQfB2lPM0eLs7ztyaGRu75bFo5A= +github.com/testcontainers/testcontainers-go v0.43.0/go.mod h1:+VxkT2NQnKOZPKi6praMuMKYHYyOGXr0XSBSlSMCzFo= +github.com/testcontainers/testcontainers-go/modules/postgres v0.43.0 h1:ShNOFYAF4lKHvdIG258hi69bSxC88uXnxJkJvNs/IVs= +github.com/testcontainers/testcontainers-go/modules/postgres v0.43.0/go.mod h1:vdq5/RqmGfWeefzyfcVI/pID1rzmc1TDvqXa15bPJks= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/urfave/cli/v3 v3.10.0 h1:0aU8yOObVDMkM13Cj4G+zb4P0PdeJMec65f81Ak1ioM= github.com/urfave/cli/v3 v3.10.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/vektah/gqlparser/v2 v2.5.35 h1:LEr/wXnTKkOqNn+4tNClYclksXN2781VoBFzzFW51Dk= github.com/vektah/gqlparser/v2 v2.5.35/go.mod h1:cAJ9qwVgPaUkWv6Gn8vn0mqOE0Ui5Pn56wNy5396XWo= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -143,17 +242,23 @@ golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg= @@ -164,6 +269,7 @@ google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBN google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -172,6 +278,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA= modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= @@ -180,3 +288,5 @@ modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go index a70bf737..940f8e6b 100644 --- a/internal/auth/middleware.go +++ b/internal/auth/middleware.go @@ -2,6 +2,7 @@ package auth import ( "context" + "errors" "net/http" "github.com/alexedwards/scs/v2" @@ -9,6 +10,18 @@ import ( type UserContextKey struct{} +var ErrNotAuthenticated error = errors.New("not authenticated") + +func UserIDFromContext(ctx context.Context) *int { + userVal := ctx.Value(UserContextKey{}) + if userVal == nil { + return nil + } + userID := userVal.(int) + return &userID + +} + func OptionalAuthMiddleware(next http.Handler, sessionManager *scs.SessionManager) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/database/group_test.go b/internal/database/group_test.go new file mode 100644 index 00000000..e8139912 --- /dev/null +++ b/internal/database/group_test.go @@ -0,0 +1,164 @@ +package database + +import ( + "log/slog" + "testing" +) + +func TestUserGroupVisibilityGood(t *testing.T) { + if testing.Short() { + t.Skip("Skipping db test in short mode.") + } + _, store, data := VerifyTestDb(slog.Default()) + user := data.Users[0] + group := data.BeatlesMusicians + + // brian can see musicians + visible, err := store.Groups.IsGroupVisibleToUser(t.Context(), user.Id, group.Id) + if err != nil { + t.Fatalf("Failed to check group visibility of %+v to see %+v: %v", user, group, err) + } + if visible != true { + t.Fatalf("%v should be able to see %v but couldn't", user, group) + } +} +func TestUserGroupVisibilityBad(t *testing.T) { + if testing.Short() { + t.Skip("Skipping db test in short mode.") + } + _, store, data := VerifyTestDb(slog.Default()) + user := data.Users[1] + group := data.BeatlesManagers + + // musicians can't see up (unless explicitly allowed to) + visible, err := store.Groups.IsGroupVisibleToUser(t.Context(), user.Id, group.Id) + if err != nil { + t.Fatalf("Failed to check group visibility of %+v to see %+v: %v", user, group, err) + } + if visible != false { + t.Fatalf("%v should NOT be able to see %v but could", user, group) + } +} + +func TestAddedUsers(t *testing.T) { + if testing.Short() { + t.Skip("Skipping db test in short mode.") + } + _, store, _ := VerifyTestDb(slog.Default()) + for _, user := range localContext.Users { + user, err := store.Users.GetUserByEmail(t.Context(), user.Email) + if err != nil { + t.Fatalf("Failed to find user with email '%s': %v", user.Email, err) + } + } +} + +func TestGroupMembership(t *testing.T) { + if testing.Short() { + t.Skip("Skipping db test in short mode.") + } + _, store, _ := VerifyTestDb(slog.Default()) + for _, email := range []string{ + "john@beatles.com", + "paul@beatles.com", + "george@beatles.com", + "ringo@beatles.com", + } { + user, err := store.Users.GetUserByEmail(t.Context(), email) + if err != nil { + t.Fatalf("couldn't find user with email '%s': %v", email, err) + } + inGroup, _, err := store.Groups.IsUserInGroup(t.Context(), user.Id, localContext.BeatlesMusicians.Id) + if err != nil { + t.Fatalf("couldn't check user in group for email '%s': %v", email, err) + } + if !inGroup { + t.Fatalf("user with email '%s' should be in group '%s' but wasn't", email, localContext.BeatlesMusicians.Name) + } + } +} + +func TestSubgroupMembership(t *testing.T) { + if testing.Short() { + t.Skip("Skipping db test in short mode.") + } + _, store, _ := VerifyTestDb(slog.Default()) + for _, email := range []string{ + "john@beatles.com", + "paul@beatles.com", + "george@beatles.com", + "ringo@beatles.com", + "brian@example.com", + } { + user, err := store.Users.GetUserByEmail(t.Context(), email) + if err != nil { + t.Fatalf("couldn't find user with email '%s': %v", email, err) + } + inGroup, _, err := store.Groups.IsUserInGroup(t.Context(), user.Id, localContext.Brits.Id) + if err != nil { + t.Fatalf("couldn't check user in group for email '%s': %v", email, err) + } + if !inGroup { + t.Fatalf("user with email '%s' should be in group '%s' via subgroup but wasn't", email, localContext.BeatlesMusicians.Name) + } + } +} + +func TestCanGroupManageAnonymousGroup(t *testing.T) { + if testing.Short() { + t.Skip("Skipping db test in short mode.") + } + _, store, data := VerifyTestDb(slog.Default()) + rootGroupId, err := store.Groups.GetAdminGroupId(t.Context()) + if err != nil { + t.Fatalf("couldn't get root group ID to continue test") + } + tests := []struct { + testName string + // params + managerGroupId int + anonymousGroupId int + want bool + wantErr bool + }{ + { + "beatles managers manage parlophone so can manage staff of parlophone", + data.BeatlesManagers.Id, + data.Parlophone.StaffAgroupId, + true, + false, + }, + { + "beatles musicians DONT manage parlophone so CANT manage staff of parlophone", + data.BeatlesMusicians.Id, + data.Parlophone.StaffAgroupId, + false, + false, + }, + { + "root group can manage any agroup so can manage staff of parlophone", + rootGroupId, + data.Parlophone.StaffAgroupId, + true, + false, + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + got, gotErr := store.Groups.CanGroupManageAnonymousGroup(t.Context(), tt.managerGroupId, tt.anonymousGroupId) + if gotErr != nil { + if !tt.wantErr { + t.Errorf("CanGroupManageAnonymousGroup() failed: %v", gotErr) + } + return + } + if tt.wantErr { + t.Fatal("CanGroupManageAnonymousGroup() succeeded unexpectedly") + } + // TODO: update the condition below to compare got with tt.want. + if got != tt.want { + t.Errorf("CanGroupManageAnonymousGroup() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/database/migrations/20260703201718_initial_schema.sql b/internal/database/migrations/20260703201718_initial_schema.sql index 1cbee8ac..8a455bbe 100644 --- a/internal/database/migrations/20260703201718_initial_schema.sql +++ b/internal/database/migrations/20260703201718_initial_schema.sql @@ -44,15 +44,17 @@ CREATE TABLE images ( CREATE TABLE groups ( id SERIAL PRIMARY KEY, - name TEXT UNIQUE NOT NULL, + name TEXT NOT NULL, manager_id INT REFERENCES groups(id), description TEXT NOT NULL DEFAULT '', UNIQUE (name, manager_id) ); -INSERT INTO groups (name, description) -VALUES ('admin', 'The root of all groups'); - +INSERT INTO groups (id, name, description) +VALUES (0, 'admin', 'The root of all groups'); +alter table groups + add constraint nonnull_manager_except_for_root + check (id = 0 or manager_id is not null); CREATE TABLE group_direct_membership( group_id INT NOT NULL REFERENCES groups(id) ON DELETE CASCADE, @@ -62,8 +64,8 @@ CREATE TABLE group_direct_membership( ); CREATE TABLE group_direct_subgroups ( - group_id INT REFERENCES groups(id), - subgroup_id INT REFERENCES groups(id), + group_id INT REFERENCES groups(id) ON DELETE CASCADE, + subgroup_id INT REFERENCES groups(id) ON DELETE CASCADE, view_permission INT NOT NULL DEFAULT 0 CHECK (view_permission in (0, 1, 2)), PRIMARY KEY (group_id, subgroup_id) ); @@ -142,14 +144,18 @@ create view group_management as ( group by manager_group_id , group_id ); + create view group_membership as ( - select group_id, user_id, gdm.view_permission from group_direct_membership gdm - union - select distinct gs.supergroup_id, gdm.user_id, gs.view_permission - from group_subgroups gs - left join group_direct_membership gdm - on gdm.group_id = gs.subgroup_id - where user_id is not null + select group_id, user_id, MAX(view_permission) as view_permission from ( + select group_id, user_id, gdm.view_permission as view_permission from group_direct_membership gdm + union + select distinct gs.supergroup_id, gdm.user_id, gs.view_permission + from group_subgroups gs + left join group_direct_membership gdm + on gdm.group_id = gs.subgroup_id + where user_id is not null + ) as subquery + group by group_id, user_id ); @@ -164,6 +170,13 @@ CREATE TABLE anonymous_group_subgroups( ); +create view anonymous_group_membership as ( + select distinct ags.anonymous_id as agroup_id , gm.user_id as user_id + from anonymous_group_subgroups ags + left join group_membership gm + on gm.group_id = ags.group_id +); + CREATE TABLE makerspaces ( id SERIAL PRIMARY KEY, @@ -177,10 +190,41 @@ CREATE TABLE makerspaces ( timezone TEXT NOT NULL DEFAULT 'America/New_York', -- agroup of users who can manage this space management_agroup_id INT NOT NULL REFERENCES anonymous_groups(id) ON DELETE CASCADE, - -- agroup of users who can site-set equipment state - can_change_equipment_state_agroup_id INT NOT NULL REFERENCES anonymous_groups(id) ON DELETE CASCADE + -- agroup of users who can site-set equipment state and other such actions + staff_agroup_id INT NOT NULL REFERENCES anonymous_groups(id) ON DELETE CASCADE ); +create view anonymous_group_management_by_group as ( + ( + -- groups in management agroup can manage staff agroup for makerspace + select ags.group_id as manager_group_id, m.staff_agroup_id as managed_agroup_id + from makerspaces m + left join anonymous_group_subgroups ags on m.management_agroup_id = ags.anonymous_id + ) union ( + -- groups in management agroup can manage themselves + select ags.group_id as manager_group_id, m.management_agroup_id as managed_agroup_id + from makerspaces m + left join anonymous_group_subgroups ags on m.management_agroup_id = ags.anonymous_id + ) union ( + -- root group can manage any agroup id + select g.id as manager_group_id, ag.id as managed_agroup_id + from anonymous_groups ag + left join groups g on TRUE + -- only group with no manager is root group + where g.manager_id is null + ) + -- NOTE: add other rules about which groups can manage agroups here and union them in +); + + +create view anonymous_group_management_by_user as ( + select gm.user_id as manager_user_id, agm.managed_agroup_id as managed_agroup_id + from anonymous_group_management_by_group agm + left join group_membership gm on gm.group_id = agm.manager_group_id + +); + + CREATE TABLE restrictions ( id SERIAL PRIMARY KEY, makerspace_id INT NOT NULL REFERENCES makerspaces(id) ON DELETE CASCADE, @@ -222,17 +266,6 @@ CREATE TABLE announcements ( makerspace_id INT REFERENCES makerspaces(id) ON DELETE CASCADE ); -CREATE TABLE managers ( - user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - makerspace_id INT NOT NULL REFERENCES makerspaces(id) ON DELETE CASCADE, - PRIMARY KEY (user_id, makerspace_id) -); - -CREATE TABLE staff ( - user_id INT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - makerspace_id INT NOT NULL REFERENCES makerspaces(id) ON DELETE CASCADE, - PRIMARY KEY (user_id, makerspace_id) -); CREATE TABLE equipment ( id SERIAL PRIMARY KEY, @@ -395,6 +428,13 @@ DROP TABLE IF EXISTS groups; DROP TABLE IF EXISTS group_direct_membership; DROP TABLE IF EXISTS group_direct_subgroups; +DROP VIEW IF EXISTS group_noncombination_management; +DROP VIEW IF EXISTS group_subgroups; +DROP VIEW IF EXISTS group_management; +DROP VIEW IF EXISTS group_membership; +DROP VIEW IF EXISTS anonymous_group_membership; +DROP VIEW IF EXISTS anonymous_group_management_by_group; + DROP TABLE IF EXISTS anonymous_groups; DROP TABLE IF EXISTS anonymous_group_subgroups; diff --git a/internal/database/mock_data.go b/internal/database/mock_data.go new file mode 100644 index 00000000..f4d5b0d3 --- /dev/null +++ b/internal/database/mock_data.go @@ -0,0 +1,272 @@ +package database + +import ( + "context" + "database/sql" + "embed" + "fmt" + "log" + "log/slog" + "make-backend/internal/database/models" + "sync" + + "github.com/pressly/goose/v3" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" +) + +type TestMockData struct { + Users []struct { + Id int + Email string + } + BeatlesMusicians models.Group + OtherMusicians models.Group + ExMusicians models.Group + BeatlesManagers models.Group + Brits models.Group + + Parlophone models.Makerspace +} + +var localContext TestMockData = TestMockData{ + Users: []struct { + Id int + Email string + }{ + {Email: "brian@example.com"}, + {Email: "john@beatles.com"}, + {Email: "paul@beatles.com"}, + {Email: "george@beatles.com"}, + {Email: "ringo@beatles.com"}, + {Email: "pete@beatles.com"}, + }, +} + +func makeTestMakerspace(ctx context.Context, l *slog.Logger, store *Store) bool { + id, err := store.Makerspaces.CreateMakerspace(ctx, "Parlophone", false) + if err != nil { + l.Error("Failed to create makerspace", "err", err) + return false + } + m, err := store.Makerspaces.GetMakerspaceById(ctx, id) + if err != nil { + l.Error("Failed to get created makerspace", "err", err) + return false + } + localContext.Parlophone = *m + err = store.Groups.SetGroupsForAnonymousGroup(ctx, localContext.Parlophone.StaffAgroupId, []int{localContext.BeatlesMusicians.Id}) + if err != nil { + l.Error("Failed to add beatles as staff of parlophone", "err", err) + return false + } + err = store.Groups.SetGroupsForAnonymousGroup(ctx, localContext.Parlophone.ManagementAgroupId, []int{localContext.BeatlesManagers.Id}) + if err != nil { + l.Error("Failed to add beatles managers as staff of parlophone", "err", err) + return false + } + + return true +} +func fillTestUsers(ctx context.Context, l *slog.Logger, store *Store) bool { + for i, u := range localContext.Users { + id, err := store.Users.CreateUser(ctx, u.Email) + if err != nil { + l.Error("failed to add test user", "err", err) + return false + } + localContext.Users[i].Id = id + } + return true +} + +func makeTestGroups(ctx context.Context, l *slog.Logger, store *Store) bool { + managerGroupID, err := store.Groups.GetAdminGroupId(ctx) + if err != nil { + l.Error("Failed to find root group", "err", err) + return false + } + localContext.BeatlesManagers, err = store.Groups.CreateGroup(ctx, models.Group{ + ManagerId: managerGroupID, + Name: "The Beatles Managers", + Description: "", + }) + if err != nil { + l.Error("failed to create manager group", "err", err) + return false + } + localContext.BeatlesMusicians, err = store.Groups.CreateGroup(ctx, models.Group{ + ManagerId: localContext.BeatlesManagers.Id, + Name: "The beatles musicians", + Description: "the members of the beatles", + }) + if err != nil { + l.Error("failed to create musician group", "err", err) + return false + } + localContext.OtherMusicians, err = store.Groups.CreateGroup(ctx, models.Group{ + ManagerId: localContext.BeatlesManagers.Id, + Name: "Other musicians", + Description: "Other members the beatles don't know about", + }) + if err != nil { + l.Error("failed to create other musician group", "err", err) + return false + } + + localContext.ExMusicians, err = store.Groups.CreateGroup(ctx, models.Group{ + ManagerId: localContext.BeatlesManagers.Id, + Name: "The ex musicians for the beatles", + Description: "ex members of the beatles", + }) + if err != nil { + l.Error("failed to create ex musician group", "err", err) + return false + } + + localContext.Brits, err = store.Groups.CreateGroup(ctx, models.Group{ + ManagerId: managerGroupID, + Name: "British People", + Description: "", + }) + if err != nil { + l.Error("failed to create british group", "err", err) + return false + } + + err = store.Groups.AddSubgroupToGroup(ctx, localContext.BeatlesMusicians.Id, localContext.Brits.Id, models.GroupViewPermission_SeeSelf) + if err != nil { + l.Error("Failed to add beatles subgroup to british super group") + return false + } + + err = store.Groups.AddSubgroupToGroup(ctx, localContext.BeatlesManagers.Id, localContext.Brits.Id, models.GroupViewPermission_SeeSelf) + if err != nil { + l.Error("Failed to add beatles managers subgroup to british super group") + return false + } + + return true +} + +func addUsersToTestGroups(ctx context.Context, t *slog.Logger, store *Store) bool { + for _, member := range []struct { + email string + groupId int + perm models.GroupViewPermission + }{ + {"brian@example.com", localContext.BeatlesManagers.Id, models.GroupViewPermission_SeeSelf}, + {"john@beatles.com", localContext.BeatlesMusicians.Id, models.GroupViewPermission_SeeAll}, + {"paul@beatles.com", localContext.BeatlesMusicians.Id, models.GroupViewPermission_SeeAll}, + {"george@beatles.com", localContext.BeatlesMusicians.Id, models.GroupViewPermission_SeeAll}, + {"ringo@beatles.com", localContext.BeatlesMusicians.Id, models.GroupViewPermission_SeeAll}, + {"pete@beatles.com", localContext.ExMusicians.Id, models.GroupViewPermission_SeeNone}, + } { + user, err := store.Users.GetUserByEmail(ctx, member.email) + if err != nil { + t.Error("couldn't find user", "email", member.email, "err", err) + return false + } + err = store.Groups.AddUserToGroup(ctx, user.Id, member.groupId, member.perm) + if err != nil { + t.Error("Failed to add ", "user", user, "group", member.groupId, "err", err) + return false + } + } + + return true + +} + +func fillTestData(ctx context.Context, l *slog.Logger, store *Store) { + fillTestUsers(ctx, l, store) + makeTestGroups(ctx, l, store) + addUsersToTestGroups(ctx, l, store) + makeTestMakerspace(ctx, l, store) + +} + +func setupTestDB(logger *slog.Logger) (*sql.DB, func(), error) { + ctx := context.Background() + + dbName := "testdb" + dbUser := "test_user" + dbPassword := "test_password" + + postgresContainer, err := postgres.Run(ctx, + "postgres:16-alpine", + postgres.WithDatabase(dbName), + postgres.WithUsername(dbUser), + postgres.WithPassword(dbPassword), + postgres.BasicWaitStrategies(), + ) + deferer := func() { + if err := testcontainers.TerminateContainer(postgresContainer); err != nil { + logger.Error("failed to terminate container", "err", err) + } + } + if err != nil { + logger.Error("failed to start container", "err", err) + return nil, func() {}, err + } + + connStr := postgresContainer.MustConnectionString(ctx, "sslmode=disable") + slog.Info("connecting to test postgres", "conn", connStr) + + db, err := sql.Open("postgres", connStr) + if err != nil { + log.Fatal(err) + } + + if err := db.Ping(); err != nil { + return nil, deferer, err + } + + err = migrateTestDB(logger, db) + if err != nil { + return db, deferer, err + } + + store := NewStore(db) + fillTestData(context.TODO(), logger, store) + + return db, deferer, nil + +} + +//go:embed migrations/*.sql +var testEmbedMigrations embed.FS + +func migrateTestDB(logger *slog.Logger, db *sql.DB) error { + // Migrations + logger.Info("Running migrations...") + goose.SetBaseFS(testEmbedMigrations) + if err := goose.SetDialect("postgres"); err != nil { + return fmt.Errorf("failed to set goose dialect: %w", err) + } + + if err := goose.Up(db, "migrations"); err != nil { + return fmt.Errorf("failed to run migrations: %w", err) + } + return nil +} + +var localLock sync.Mutex +var localDB *sql.DB = nil +var localStore *Store = nil +var localCleanup func() = func() {} + +func VerifyTestDb(logger *slog.Logger) (*sql.DB, *Store, *TestMockData) { + localLock.Lock() + defer localLock.Unlock() + if localDB != nil { + return localDB, localStore, &localContext + } + var err error + localDB, localCleanup, err = setupTestDB(logger) + if err != nil { + logger.Error("failed to setup test DB", "err", err) + } + localStore = NewStore(localDB) + return localDB, localStore, &localContext +} diff --git a/internal/database/models/group.go b/internal/database/models/group.go index 2f949cf5..4372efef 100644 --- a/internal/database/models/group.go +++ b/internal/database/models/group.go @@ -1,5 +1,14 @@ package models +import ( + "fmt" + "io" + "strconv" +) + +var ErrNoGroupOrWrongPermissions = fmt.Errorf("group does not exist or user has invalid permissions to query it") +var ErrNoAnonymousGroupOrWrongPermissions = fmt.Errorf("anonymous group does not exist or user has invalid permissions to query it") + // Determines how users see the group theyre a part of type GroupViewPermission int @@ -24,6 +33,10 @@ type Group struct { Description string } +type AnonymousGroup struct { + Id int +} + // Stores a users membership in a group // membership is either direct: A user is manually added to a group with a certain view permission or // indirect: the user is a direct of a subgroup of the super group with view permissions defined at the subgroup relation @@ -46,3 +59,36 @@ type SubgroupLink struct { SubgroupId int ViewPermission GroupViewPermission } + +func (e GroupViewPermission) MarshalGQL(w io.Writer) { + var s string + switch e { + case GroupViewPermission_SeeAll: + s = "SEE_ALL" + case GroupViewPermission_SeeSelf: + s = "SEE_SELF" + case GroupViewPermission_SeeNone: + s = "SEE_NONE" + default: + s = "" + } + w.Write([]byte(strconv.Quote(s))) +} + +func (e *GroupViewPermission) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enum must be a string") + } + switch str { + case "SEE_ALL": + *e = GroupViewPermission_SeeAll + case "SEE_SELF": + *e = GroupViewPermission_SeeSelf + case "SEE_NONE": + *e = GroupViewPermission_SeeNone + default: + return fmt.Errorf("invalid GroupViewPermission value: %s", str) + } + return nil +} diff --git a/internal/database/models/makerspace.go b/internal/database/models/makerspace.go index ea5565af..5f8ef0f1 100644 --- a/internal/database/models/makerspace.go +++ b/internal/database/models/makerspace.go @@ -1,14 +1,16 @@ package models type Makerspace struct { - Id int - Name string - Subtitle string - Description string - DocsUrl string - ImageId *int32 - Hidden bool - Timezone string + Id int + Name string + Subtitle string + Description string + DocsUrl string + ImageId *int32 + Hidden bool + Timezone string + ManagementAgroupId int + StaffAgroupId int // TODO: finer grained permissions } func (m Makerspace) LogEntity() LogEntity { diff --git a/internal/database/models/zone.go b/internal/database/models/zone.go index 370ee8ff..18cd275c 100644 --- a/internal/database/models/zone.go +++ b/internal/database/models/zone.go @@ -1,5 +1,6 @@ package models +// a zone is a thing type Zone struct { Id int MakerspaceId int diff --git a/internal/database/repos/group_repo.go b/internal/database/repos/group_repo.go index dc47d320..bfe4589f 100644 --- a/internal/database/repos/group_repo.go +++ b/internal/database/repos/group_repo.go @@ -3,8 +3,10 @@ package repos import ( "context" "database/sql" + "errors" "fmt" "make-backend/internal/database/models" + "strings" ) type GroupRepository interface { @@ -21,36 +23,283 @@ type GroupRepository interface { GetDirectSubgroups(ctx context.Context, groupId int) ([]models.SubgroupLink, error) AddSubgroupToGroup(ctx context.Context, subgroup, supergroup int, permissions models.GroupViewPermission) error + IsGroupSubgroup(ctx context.Context, subgroup, supergroup int) (bool, models.GroupViewPermission, error) GetGroupMembers(ctx context.Context, groupId int) ([]models.MembershipToGroup, error) GetDirectGroupMembers(ctx context.Context, groupId int) ([]models.MembershipToGroup, error) - IsUserInGroup(ctx context.Context, userId int, groupId int) (bool, error) - IsUserInGroupDirectly(ctx context.Context, userId int, groupId int) (bool, error) + IsUserInGroup(ctx context.Context, userId int, groupId int) (bool, models.GroupViewPermission, error) + + AllGroupsUserIsMemberOf(ctx context.Context, userId int) ([]models.MembershipToUser, error) + IsUserInGroupDirectly(ctx context.Context, userId int, groupId int) (bool, models.GroupViewPermission, error) // add a user to a group AddUserToGroup(ctx context.Context, userId int, groupId int, perms models.GroupViewPermission) error // remove user from a group // return true if removed, false if the user was not removed from that group RemoveUserFromGroup(ctx context.Context, userId int, groupId int) (bool, error) + CanUserManageGroup(ctx context.Context, managerUser, managedGroup int) (bool, error) CanGroupManageGroup(ctx context.Context, managerGroup, managedGroup int) (bool, error) - AllGroupsGroupCanManage(ctx context.Context, userId int) ([]int, error) + AllGroupsGroupCanManage(ctx context.Context, groupId int) ([]int, error) + AllGroupsGroupCanManageDirectly(ctx context.Context, groupId int) ([]int, error) AllGroupsVisibleToGroup(ctx context.Context, groupId int) ([]int, error) + IsGroupVisibleToGroup(ctx context.Context, lookingGroupId int, groupId int) (bool, error) - AllGroupsUserIsMemberOf(ctx context.Context, userId int) ([]models.MembershipToUser, error) AllGroupsUserCanManage(ctx context.Context, userId int) ([]int, error) AllGroupsUserVisibleToUser(ctx context.Context, userId int) ([]int, error) + IsGroupVisibleToUser(ctx context.Context, userId int, groupId int) (bool, error) + + IsUserInAnonymousGroup(ctx context.Context, anonymousGroupId int, userId int) (bool, error) + UsersInAnonymousGroup(ctx context.Context, anonymousGroupId int) ([]models.User, error) + + GroupsInAnonymousGroup(ctx context.Context, anonymousGroupId int) ([]models.Group, error) + SetGroupsForAnonymousGroup(ctx context.Context, anonymousGroupId int, groupIds []int) error + CanGroupManageAnonymousGroup(ctx context.Context, managerGroupId, anonymousGroupId int) (bool, error) + CanUserManageAnonymousGroup(ctx context.Context, managerUserId, anonymousGroupId int) (bool, error) } type GroupRepo struct { DB *sql.DB } +func (g *GroupRepo) CanGroupManageAnonymousGroup(ctx context.Context, managerGroupId, anonymousGroupId int) (bool, error) { + // NOTE don't edit this function to add other rules, instead add rules to the anonymous_group_management_by_group view + var canManage bool + query := "SELECT EXISTS(SELECT 1 FROM anonymous_group_management_by_group WHERE manager_group_id = $1 and managed_agroup_id = $2)" + err := g.DB.QueryRow(query, managerGroupId, anonymousGroupId).Scan(&canManage) + + if err != nil { + return false, err + } + return canManage, nil + +} + +func (g *GroupRepo) CanUserManageAnonymousGroup(ctx context.Context, managerUserId, anonymousGroupId int) (bool, error) { + // NOTE don't edit this function to add other rules, instead add rules to the anonymous_group_management_by_group view + var canManage bool + query := "SELECT EXISTS(SELECT 1 FROM anonymous_group_management_by_user WHERE manager_user_id = $1 and managed_agroup_id = $2)" + err := g.DB.QueryRow(query, managerUserId, anonymousGroupId).Scan(&canManage) + + if err != nil { + return false, err + } + return canManage, nil + +} + +func (g *GroupRepo) UsersInAnonymousGroup(ctx context.Context, anonymousGroupId int) ([]models.User, error) { + query := ` + select + u.id, u.email, u.full_name, u.preferred_name, u.pronouns, u.join_date, u.setup_complete, u.archived, u.notes, u.admin, u.force_archive, u.card_tag + from anonymous_group_membership + left join users u on u.id = user_id + where agroup_id = $1` + + rows, err := g.DB.QueryContext(ctx, query, anonymousGroupId) + if err != nil { + return nil, fmt.Errorf("failed to construct QueryContext for GetGroupMembers: %w", err) + } + + users := []models.User{} + for rows.Next() { + var user models.User + err := rows.Scan( + &user.Id, + &user.Email, + &user.FullName, + &user.PreferredName, + &user.Pronouns, + &user.JoinDate, + &user.SetupComplete, + &user.Archived, + &user.Notes, + &user.Admin, + &user.ForceArchive, + &user.CardTag, + ) + if err != nil { + return nil, fmt.Errorf("failed to scan for user in agroup: %w", err) + } + users = append(users, user) + + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("failed to get group members: %w", err) + } + + return users, nil + +} + +func (g *GroupRepo) SetGroupsForAnonymousGroup(ctx context.Context, anonymousGroupId int, groupIds []int) error { + tx, err := g.DB.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + _, err = tx.ExecContext(ctx, "delete from anonymous_group_subgroups where anonymous_id = $1", anonymousGroupId) + if err != nil { + return fmt.Errorf("failed to delete old subgroups before adding new subgroups for set: %w", err) + } + + counter := 1 + valueSpots := []string{} + values := []any{} + for _, sg := range groupIds { + valueSpots = append(valueSpots, fmt.Sprintf("($%d, $%d)", counter, counter+1)) + values = append(values, anonymousGroupId, sg) + counter += 2 + } + + query := `INSERT INTO anonymous_group_subgroups (anonymous_id, group_id) VALUES ` + strings.Join(valueSpots, ", ") + _, err = tx.ExecContext(ctx, query, values...) + if err != nil { + return fmt.Errorf("failed to add group to anonymous group: %w", err) + } + return tx.Commit() +} + +func (g *GroupRepo) GroupsInAnonymousGroup(ctx context.Context, anonymousGroupId int) ([]models.Group, error) { + query := `SELECT group_id, g.name, g.description, g.manager_id FROM anonymous_group_subgroups left join groups g on g.id = group_id WHERE anonymous_id = $1` + rows, err := g.DB.QueryContext(ctx, query, anonymousGroupId) + if err != nil { + return nil, fmt.Errorf("failed to query groups in anonymous group: %w", err) + } + defer rows.Close() + + var groupIds []models.Group + for rows.Next() { + var g models.Group + if err := rows.Scan(&g.Id, &g.Name, &g.Description, &g.ManagerId); err != nil { + return nil, fmt.Errorf("failed to scan group in anonymous group: %w", err) + } + groupIds = append(groupIds, g) + } + if err := rows.Err(); err != nil { + return nil, err + } + return groupIds, nil +} + +func (g *GroupRepo) IsUserInAnonymousGroup(ctx context.Context, anonymousGroupId int, userId int) (bool, error) { + var inGroup bool + + query := "SELECT EXISTS(SELECT 1 FROM anonymous_group_membership WHERE agroup_id = $1 and user_id = $2)" + err := g.DB.QueryRow(query, anonymousGroupId, userId).Scan(&inGroup) + + if err != nil { + return false, err + } + return inGroup, nil + +} + +func (g *GroupRepo) RemoveGroupFromAnonymousGroup(ctx context.Context, anonymousGroupId int, groupId int) error { + query := `DELETE FROM anonymous_group_subgroups WHERE anonymous_id = $1 AND group_id = $2` + _, err := g.DB.ExecContext(ctx, query, anonymousGroupId, groupId) + if err != nil { + return fmt.Errorf("failed to remove group from anonymous group: %w", err) + } + return nil +} + +var _ GroupRepository = &GroupRepo{} + +func (g *GroupRepo) IsGroupVisibleToUser(ctx context.Context, userId int, groupId int) (bool, error) { + // if can manage -> see all + canManage, err := g.CanUserManageGroup(ctx, userId, groupId) + if err != nil { + return false, fmt.Errorf("failed to check if group is visible to user check 1: %w", err) + } + if canManage { + return true, nil + } + // TODO check if group is shared to group that user is in + + isInGroup, how, err := g.IsUserInGroup(ctx, userId, groupId) + if err != nil { + return false, fmt.Errorf("failed to check if group is visible to user check 2: %w", err) + } + + if isInGroup { + switch how { + case models.GroupViewPermission_SeeAll: + return true, nil + case models.GroupViewPermission_SeeNone: + return false, nil + case models.GroupViewPermission_SeeSelf: + return true, nil + default: + return false, fmt.Errorf("invalid view permission: %v", how) + } + } + return false, nil +} +func (g *GroupRepo) IsGroupVisibleToGroup(ctx context.Context, lookingGroupId int, groupId int) (bool, error) { + // if can manage -> see all + canManage, err := g.CanGroupManageGroup(ctx, lookingGroupId, groupId) + if err != nil { + return false, fmt.Errorf("failed to check if group is visible to group 1: %w", err) + } + if canManage { + return true, nil + } + + isSubgroup, perms, err := g.IsGroupSubgroup(ctx, lookingGroupId, groupId) + if err != nil { + return false, fmt.Errorf("failed to check if group is visible to group 3: %w", err) + } + if isSubgroup { + switch perms { + + case models.GroupViewPermission_SeeAll: + return true, nil + case models.GroupViewPermission_SeeNone: + return false, nil + case models.GroupViewPermission_SeeSelf: + return true, nil + default: + return false, fmt.Errorf("invalid view permission %v", perms) + } + + } + return false, nil + +} + +func (g *GroupRepo) IsGroupSubgroup(ctx context.Context, subgroup int, supergroup int) (bool, models.GroupViewPermission, error) { + var perms models.GroupViewPermission + query := `select view_permission from group_subgroups where supergroup_id = $1 and subgroup_id = $2` + + row := g.DB.QueryRowContext(ctx, query, supergroup, subgroup) + + err := row.Scan(&perms) + if errors.Is(err, sql.ErrNoRows) { + return false, models.GroupViewPermission_SeeNone, nil + } + + if err != nil { + return false, models.GroupViewPermission_SeeNone, err + } + return true, perms, nil + +} + func (g *GroupRepo) AddSubgroupToGroup(ctx context.Context, subgroupID, supergroupID int, permissions models.GroupViewPermission) error { + // if new supergroup is subgroup of new subgroup, would make a loop + wouldCreateCycle, _, err := g.IsGroupSubgroup(ctx, supergroupID, subgroupID) + if err != nil { + return fmt.Errorf("failed to check subgroup for cycle prevention") + } + if wouldCreateCycle { + return errors.New("not adding subgroup to group as it would cause a cycle") + } + query := `INSERT INTO group_direct_subgroups (group_id, subgroup_id, view_permission) VALUES ($1, $2, $3)` - _, err := g.DB.ExecContext(ctx, query, supergroupID, subgroupID, permissions) + _, err = g.DB.ExecContext(ctx, query, supergroupID, subgroupID, permissions) if err != nil { return fmt.Errorf("failed to add user to group: %w", err) } @@ -88,7 +337,6 @@ func (g *GroupRepo) GetDirectSubgroups(ctx context.Context, groupId int) ([]mode } -// GetSubgroups implements [GroupRepository]. func (g *GroupRepo) GetSubgroups(ctx context.Context, groupId int) ([]models.SubgroupLink, error) { query := `select subgroup_id, view_permission from group_subgroups where supergroup_id = $1` @@ -120,7 +368,6 @@ func (g *GroupRepo) GetSubgroups(ctx context.Context, groupId int) ([]models.Sub } -// AllGroupsGroupCanManage implements [GroupRepository]. func (g *GroupRepo) AllGroupsGroupCanManage(ctx context.Context, managerId int) ([]int, error) { query := `select group_id from group_management where manager_group_id = $1` @@ -147,6 +394,33 @@ func (g *GroupRepo) AllGroupsGroupCanManage(ctx context.Context, managerId int) } +// AllGroupsGroupCanManage implements [GroupRepository]. +func (g *GroupRepo) AllGroupsGroupCanManageDirectly(ctx context.Context, managerId int) ([]int, error) { + query := `select id from groups where manager_id = $1` + + rows, err := g.DB.QueryContext(ctx, query, managerId) + if err != nil { + return nil, fmt.Errorf("failed to construct QueryContext: %w", err) + } + defer rows.Close() + + groups := []int{} + for rows.Next() { + var group int + err := rows.Scan(&group) + if err != nil { + return nil, fmt.Errorf("failed to scan for groups group can manage: %w", err) + } + groups = append(groups, group) + + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("failed to get for groups group can manage: %w", err) + } + return groups, nil + +} + func (g *GroupRepo) AllGroupsUserCanManage(ctx context.Context, userId int) ([]int, error) { query := ` select gm.group_id @@ -229,28 +503,55 @@ func (g *GroupRepo) CanGroupManageGroup(ctx context.Context, managerGroup int, m return exists, nil } -func (g *GroupRepo) IsUserInGroup(ctx context.Context, userId int, groupId int) (bool, error) { +func (g *GroupRepo) CanUserManageGroup(ctx context.Context, managerUser int, managedGroup int) (bool, error) { var exists bool - query := "SELECT EXISTS(SELECT 1 FROM group_membership WHERE user_id = $1 and group_id = $2)" + query := ` + SELECT EXISTS(SELECT 1 + from group_management gm + left join group_membership m on m.group_id = gm.manager_group_id + where m.user_id = $1 and gm.group_id = $2) + ` + err := g.DB.QueryRow(query, managerUser, managedGroup).Scan(&exists) - err := g.DB.QueryRow(query, userId, groupId).Scan(&exists) if err != nil { return false, err } return exists, nil } -func (g *GroupRepo) IsUserInGroupDirectly(ctx context.Context, userId int, groupId int) (bool, error) { - var exists bool +func (g *GroupRepo) IsUserInGroup(ctx context.Context, userId int, groupId int) (bool, models.GroupViewPermission, error) { + var perms models.GroupViewPermission - query := "SELECT EXISTS(SELECT 1 FROM group_direct_membership WHERE user_id = $1 and group_id = $2)" + query := "SELECT view_permission FROM group_membership WHERE user_id = $1 and group_id = $2" + + row := g.DB.QueryRow(query, userId, groupId) + + err := row.Scan(&perms) + if errors.Is(err, sql.ErrNoRows) { + return false, models.GroupViewPermission_SeeNone, nil + } - err := g.DB.QueryRow(query, userId, groupId).Scan(&exists) if err != nil { - return false, err + return false, models.GroupViewPermission_SeeNone, err } - return exists, nil + return true, perms, nil +} + +func (g *GroupRepo) IsUserInGroupDirectly(ctx context.Context, userId int, groupId int) (bool, models.GroupViewPermission, error) { + var perms models.GroupViewPermission + + query := "SELECT view_permission FROM group_direct_membership WHERE user_id = $1 and group_id = $2" + + err := g.DB.QueryRow(query, userId, groupId).Scan(&perms) + if errors.Is(err, sql.ErrNoRows) { + return false, models.GroupViewPermission_SeeNone, nil + } + if err != nil { + return false, models.GroupViewPermission_SeeNone, err + } + return true, perms, nil + } func (g *GroupRepo) GetAdminGroupId(ctx context.Context) (int, error) { @@ -301,7 +602,7 @@ func (g *GroupRepo) GetGroupById(ctx context.Context, id int) (*models.Group, er id, manager_id, name, - description, + description FROM groups WHERE id = $1` err := g.DB.QueryRowContext(ctx, query, id).Scan( diff --git a/internal/database/repos/makerspace_repo.go b/internal/database/repos/makerspace_repo.go index 16d485e9..fc0642f3 100644 --- a/internal/database/repos/makerspace_repo.go +++ b/internal/database/repos/makerspace_repo.go @@ -3,7 +3,6 @@ package repos import ( "context" "database/sql" - "fmt" "make-backend/internal/database/models" ) @@ -11,8 +10,7 @@ type MakerspaceRepository interface { GetMakerspaceById(ctx context.Context, id int) (*models.Makerspace, error) CreateMakerspace(ctx context.Context, name string, hidden bool) (int, error) DeleteMakerspace(ctx context.Context, id int) (bool, error) - AddManager(ctx context.Context, makerspace_id int, user_id int) error - AddStaff(ctx context.Context, makerspace_id int, user_id int) error + GetDefaultHours(ctx context.Context, makerspace_id int) ([]*models.DefaultHours, error) } @@ -31,7 +29,9 @@ func (r *MakerspaceRepo) GetMakerspaceById(ctx context.Context, id int) (*models docs_url, image_id, hidden, - timezone + timezone, + management_agroup_id, + staff_agroup_id FROM makerspaces where makerspaces.id = $1 ` @@ -44,6 +44,8 @@ func (r *MakerspaceRepo) GetMakerspaceById(ctx context.Context, id int) (*models &makerspace_result.ImageId, &makerspace_result.Hidden, &makerspace_result.Timezone, + &makerspace_result.ManagementAgroupId, + &makerspace_result.StaffAgroupId, ) if err != nil { @@ -54,49 +56,49 @@ func (r *MakerspaceRepo) GetMakerspaceById(ctx context.Context, id int) (*models } func (r *MakerspaceRepo) CreateMakerspace(ctx context.Context, name string, hidden bool) (int, error) { - var id_result int - - query := `INSERT INTO makerspaces (name, hidden) VALUES ($1, $2) RETURNING id` - - err := r.DB.QueryRowContext(ctx, query, name, hidden).Scan(&id_result) + tx, err := r.DB.BeginTx(ctx, nil) if err != nil { return 0, err } + defer tx.Rollback() - return id_result, nil -} - -func (r *MakerspaceRepo) DeleteMakerspace(ctx context.Context, id int) (bool, error) { - query := `DELETE FROM makerspaces WHERE id = $1` + var mid_result int + var manager_agroup_id_result, staff_agroup_id_result int - _, err := r.DB.ExecContext(ctx, query, id) + query1 := `INSERT INTO anonymous_groups DEFAULT VALUES RETURNING id` + err = tx.QueryRowContext(ctx, query1).Scan(&manager_agroup_id_result) if err != nil { - return false, err + return 0, err } - return true, nil -} + err = tx.QueryRowContext(ctx, query1).Scan(&staff_agroup_id_result) + if err != nil { + return 0, err + } -func (r *MakerspaceRepo) AddManager(ctx context.Context, makerspace_id int, user_id int) error { - query := `INSERT INTO managers (makerspace_id, user_id) VALUES ($1, $2)` + query2 := `INSERT INTO makerspaces (name, hidden, management_agroup_id, staff_agroup_id) VALUES ($1, $2, $3, $4) RETURNING id` - _, err := r.DB.ExecContext(ctx, query, makerspace_id, user_id) + err = tx.QueryRowContext(ctx, query2, name, hidden, manager_agroup_id_result, staff_agroup_id_result).Scan(&mid_result) if err != nil { - return fmt.Errorf("failed to insert manager (makerspace: %d, user: %d): %w", makerspace_id, user_id, err) + return 0, err + } + + if err = tx.Commit(); err != nil { + return 0, err } - return nil + return mid_result, nil } -func (r *MakerspaceRepo) AddStaff(ctx context.Context, makerspace_id int, user_id int) error { - query := `INSERT INTO staff (makerspace_id, user_id) VALUES ($1, $2)` +func (r *MakerspaceRepo) DeleteMakerspace(ctx context.Context, id int) (bool, error) { + query := `DELETE FROM makerspaces WHERE id = $1` - _, err := r.DB.ExecContext(ctx, query, makerspace_id, user_id) + _, err := r.DB.ExecContext(ctx, query, id) if err != nil { - return fmt.Errorf("failed to insert staff (makerspace: %d, user: %d): %w", makerspace_id, user_id, err) + return false, err } - return nil + return true, nil } func (r *MakerspaceRepo) GetManagers(ctx context.Context, makerspace_id int) ([]*models.User, error) { diff --git a/internal/database/repos/user_repo.go b/internal/database/repos/user_repo.go index aa67986a..18da7e08 100644 --- a/internal/database/repos/user_repo.go +++ b/internal/database/repos/user_repo.go @@ -10,11 +10,7 @@ type UserRepository interface { GetUserById(ctx context.Context, id int) (*models.User, error) GetUserByEmail(ctx context.Context, email string) (*models.User, error) CreateUser(ctx context.Context, email string) (int, error) - IsManager(ctx context.Context, id int) (bool, error) - IsStaff(ctx context.Context, id int) (bool, error) IsTrainer(ctx context.Context, id int) (bool, error) - IsManagerFor(ctx context.Context, user_id int, makerspace_id int) (bool, error) - IsStaffFor(ctx context.Context, user_id int, makerspace_id int) (bool, error) IsTrainerFor(ctx context.Context, user_id int, equipment_id int) (bool, error) } @@ -115,32 +111,6 @@ func (r *UserRepo) CreateUser(ctx context.Context, email string) (int, error) { return new_id, nil } -func (r *UserRepo) IsManager(ctx context.Context, id int) (bool, error) { - var isManager bool - - query := `SELECT EXISTS(SELECT 1 FROM managers WHERE user_id = $1)` - - err := r.DB.QueryRowContext(ctx, query, id).Scan(&isManager) - if err != nil { - return false, err - } - - return isManager, nil -} - -func (r *UserRepo) IsStaff(ctx context.Context, id int) (bool, error) { - var isStaff bool - - query := `SELECT EXISTS(SELECT 1 FROM staff WHERE user_id = $1)` - - err := r.DB.QueryRowContext(ctx, query, id).Scan(&isStaff) - if err != nil { - return false, err - } - - return isStaff, nil -} - func (r *UserRepo) IsTrainer(ctx context.Context, id int) (bool, error) { var isTrainer bool @@ -154,32 +124,6 @@ func (r *UserRepo) IsTrainer(ctx context.Context, id int) (bool, error) { return isTrainer, nil } -func (r *UserRepo) IsManagerFor(ctx context.Context, user_id int, makerspace_id int) (bool, error) { - var isManager bool - - query := `SELECT EXISTS(SELECT 1 FROM managers WHERE user_id = $1 AND makerspace_id = $2)` - - err := r.DB.QueryRowContext(ctx, query, user_id, makerspace_id).Scan(&isManager) - if err != nil { - return false, err - } - - return isManager, nil -} - -func (r *UserRepo) IsStaffFor(ctx context.Context, user_id int, makerspace_id int) (bool, error) { - var isStaff bool - - query := `SELECT EXISTS(SELECT 1 FROM staff WHERE user_id = $1 AND makerspace_id = $2)` - - err := r.DB.QueryRowContext(ctx, query, user_id, makerspace_id).Scan(&isStaff) - if err != nil { - return false, err - } - - return isStaff, nil -} - func (r *UserRepo) IsTrainerFor(ctx context.Context, user_id int, equipment_id int) (bool, error) { var isTrainer bool diff --git a/internal/database/setup.go b/internal/database/setup.go index 027b047a..8cbaf753 100644 --- a/internal/database/setup.go +++ b/internal/database/setup.go @@ -19,6 +19,7 @@ func SetupDB() (*sql.DB, error) { if err != nil { return db, err } + return db, nil } diff --git a/internal/database/store.go b/internal/database/store.go index 1741fc27..2affe647 100644 --- a/internal/database/store.go +++ b/internal/database/store.go @@ -1,8 +1,10 @@ package database import ( + "context" "database/sql" "embed" + "log/slog" "make-backend/internal/database/repos" ) @@ -20,7 +22,7 @@ type Store struct { } func NewStore(db *sql.DB) *Store { - return &Store{ + s := &Store{ Users: &repos.UserRepo{DB: db}, Groups: &repos.GroupRepo{DB: db}, Makerspaces: &repos.MakerspaceRepo{DB: db}, @@ -29,4 +31,6 @@ func NewStore(db *sql.DB) *Store { Devices: &repos.DeviceRepo{DB: db}, AuditLogs: &repos.AuditLogRepo{DB: db}, } + fillTestData(context.Background(), slog.Default(), s) + return s } diff --git a/internal/gql/directives/directives.go b/internal/gql/directives/directives.go index 171b48a2..146bddf5 100644 --- a/internal/gql/directives/directives.go +++ b/internal/gql/directives/directives.go @@ -2,20 +2,25 @@ package directives import ( "context" + "errors" "fmt" + "make-backend/internal/auth" "make-backend/internal/database" "make-backend/internal/gql" + "reflect" "github.com/99designs/gqlgen/graphql" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) func isAdmin(store *database.Store, ctx context.Context) (bool, error) { - user_id, ok := ctx.Value("user_id").(int) - if !ok { + user_id := auth.UserIDFromContext(ctx) + if user_id == nil { return false, fmt.Errorf("Access denied") } - user, err := store.Users.GetUserById(ctx, user_id) + user, err := store.Users.GetUserById(ctx, *user_id) if err != nil { return false, err } @@ -23,34 +28,6 @@ func isAdmin(store *database.Store, ctx context.Context) (bool, error) { return user.Admin, nil } -func isManager(store *database.Store, ctx context.Context) (bool, error) { - user_id, ok := ctx.Value("user_id").(int) - if !ok { - return false, fmt.Errorf("Failed to get user_id from context") - } - - manager, err := store.Users.IsManager(ctx, user_id) - if err != nil { - return false, err - } - - return manager, nil -} - -func isStaff(store *database.Store, ctx context.Context) (bool, error) { - user_id, ok := ctx.Value("user_id").(int) - if !ok { - return false, fmt.Errorf("Could not extract user_id from context") - } - - staff, err := store.Users.IsStaff(ctx, user_id) - if err != nil { - return false, err - } - - return staff, nil -} - func isTrainer(store *database.Store, ctx context.Context) (bool, error) { user_id, ok := ctx.Value("user_id").(int) if !ok { @@ -84,52 +61,49 @@ func isSelf(ctx context.Context) (bool, error) { return target_id == user_id, nil } -func isManagerFor(store *database.Store, ctx context.Context) (bool, error) { - fc := graphql.GetFieldContext(ctx) - if fc == nil { - return false, fmt.Errorf("Failed to get field context") - } - - makerspace_id, ok := fc.Args["makerspace_id"].(int) - if !ok { - return false, fmt.Errorf("Failed to get user_id from field context") +func extractMakerspaceIdFromFieldNameInMakerspaceType(obj any, makerspaceIdField string) (int, error) { + // obj represents the parent Go struct (ie. Makerspace) + val := reflect.ValueOf(obj) + if val.Kind() == reflect.Ptr { + val = val.Elem() } - user_id, ok := ctx.Value("user_id").(int) - if !ok { - return false, fmt.Errorf("Failed to get user_id from session context") - } - - managerFor, err := store.Users.IsManagerFor(ctx, user_id, makerspace_id) - if err != nil { - return false, err + // Extract sibling field via reflection or direct type assertion + fieldVal := val.FieldByName(cases.Title(language.AmericanEnglish).String((makerspaceIdField))) + makerspaceId := 0 + if fieldVal.IsValid() { + makerspaceId, _ = fieldVal.Interface().(int) + } else { + return 0, fmt.Errorf("invalid field to extract. name: %v", makerspaceIdField) } + return makerspaceId, nil - return managerFor, nil } -func isStaffFor(store *database.Store, ctx context.Context) (bool, error) { +func extractMakerspaceIdFromArgNameInMakerspaceType(ctx context.Context, makerspaceIdArg string) (int, error) { fc := graphql.GetFieldContext(ctx) - if fc == nil { - return false, fmt.Errorf("Failed to get field context") - } - makerspace_id, ok := fc.Args["makerspace_id"].(int) - if !ok { - return false, fmt.Errorf("Failed to get user_id from field context") + if idVal, ok := fc.Args[makerspaceIdArg]; ok { + id := idVal.(int) + return id, nil } + return 0, fmt.Errorf("couldn't extract id from arg") - user_id, ok := ctx.Value("user_id").(int) - if !ok { - return false, fmt.Errorf("Failed to get user_id from session context") +} + +func extractMakerspaceIdFromArgOrFieldName(ctx context.Context, obj any, makerspaceIdField *string, makerspaceIdArg *string) (int, error) { + if makerspaceIdArg == nil && makerspaceIdField == nil { + return 0, errors.New("specify one of makerspaceIdField makerspaceIdArg") + } + if makerspaceIdArg != nil && makerspaceIdField != nil { + return 0, errors.New("specify one of makerspaceIdField makerspaceIdArg not both") } - staffFor, err := store.Users.IsStaffFor(ctx, user_id, makerspace_id) - if err != nil { - return false, err + if makerspaceIdArg != nil { + return extractMakerspaceIdFromArgNameInMakerspaceType(ctx, *makerspaceIdArg) } + return extractMakerspaceIdFromFieldNameInMakerspaceType(obj, *makerspaceIdField) - return staffFor, nil } func isTrainerFor(store *database.Store, ctx context.Context) (bool, error) { @@ -170,112 +144,75 @@ func SetupDirectives(config *gql.Config, store *database.Store) { } } - config.Directives.IsManager = func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { - manager, err := isManager(store, ctx) + config.Directives.IsSelf = func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { + self, err := isSelf(ctx) if err != nil { return nil, err } - if manager { + if self { return next(ctx) } else { return nil, fmt.Errorf("Unauthorized") } } - - config.Directives.IsStaff = func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { - staff, err := isStaff(store, ctx) - if err != nil { - return nil, err + config.Directives.IsManagerFor = func(ctx context.Context, obj any, next graphql.Resolver, makerspaceIdField *string, makerspaceIdArg *string) (any, error) { + user_id := auth.UserIDFromContext(ctx) + if user_id == nil { + return false, fmt.Errorf("Access denied") } - if staff { - return next(ctx) - } else { - return nil, fmt.Errorf("Unauthorized") - } - } - - config.Directives.IsTrainer = func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { - trainer, err := isTrainer(store, ctx) + makerspaceId, err := extractMakerspaceIdFromArgOrFieldName(ctx, obj, makerspaceIdField, makerspaceIdArg) if err != nil { - return nil, err + return nil, fmt.Errorf("couldn't find makerspace id field: %w", err) } - if trainer { - return next(ctx) - } else { - return nil, fmt.Errorf("Unauthorized") - } - } - - config.Directives.IsSelf = func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { - self, err := isSelf(ctx) + // TODO add direct getter for in manager group + // Or not if permissions go fine grained + m, err := store.Makerspaces.GetMakerspaceById(ctx, makerspaceId) if err != nil { return nil, err } - - if self { - return next(ctx) - } else { - return nil, fmt.Errorf("Unauthorized") - } - } - - config.Directives.IsManagerFor = func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { - managerFor, err := isManagerFor(store, ctx) + isManager, err := store.Groups.IsUserInAnonymousGroup(ctx, m.ManagementAgroupId, *user_id) if err != nil { return nil, err - } - - if managerFor { - return next(ctx) - } else { - return nil, fmt.Errorf("Unauthorized") - } - } - config.Directives.IsStaffFor = func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { - staffFor, err := isStaffFor(store, ctx) - if err != nil { - return nil, err } - if staffFor { + if isManager { return next(ctx) } else { return nil, fmt.Errorf("Unauthorized") } } - config.Directives.IsTrainerFor = func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { - trainerFor, err := isTrainerFor(store, ctx) - if err != nil { - return nil, err + config.Directives.IsStaffFor = func(ctx context.Context, obj any, next graphql.Resolver, makerspaceIdField *string, makerspaceIdArg *string) (any, error) { + user_id := auth.UserIDFromContext(ctx) + if user_id == nil { + return false, fmt.Errorf("Access denied") } - if trainerFor { - return next(ctx) - } else { - return nil, fmt.Errorf("Unauthorized") + makerspaceId, err := extractMakerspaceIdFromArgOrFieldName(ctx, obj, makerspaceIdField, makerspaceIdArg) + if err != nil { + return nil, fmt.Errorf("couldn't find makerspace id field: %w", err) } - } - config.Directives.IsStaffOrSelf = func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { - staff, err := isStaff(store, ctx) + // TODO add direct getter for in manager group + m, err := store.Makerspaces.GetMakerspaceById(ctx, makerspaceId) if err != nil { return nil, err } - - self, err := isSelf(ctx) + isStaff, err := store.Groups.IsUserInAnonymousGroup(ctx, m.StaffAgroupId, *user_id) if err != nil { return nil, err + } - if self || staff { + if isStaff { return next(ctx) } else { return nil, fmt.Errorf("Unauthorized") } } + } diff --git a/internal/gql/generated.go b/internal/gql/generated.go index 28ddc5ad..db50fc99 100644 --- a/internal/gql/generated.go +++ b/internal/gql/generated.go @@ -32,7 +32,10 @@ type Config = graphql.Config[ResolverRoot, DirectiveRoot, ComplexityRoot] type ResolverRoot interface { AccessComponent() AccessComponentResolver + AnonymousGroup() AnonymousGroupResolver + Group() GroupResolver Makerspace() MakerspaceResolver + MembershipToGroup() MembershipToGroupResolver Mutation() MutationResolver OptionBlockOption() OptionBlockOptionResolver Query() QueryResolver @@ -40,15 +43,10 @@ type ResolverRoot interface { } type DirectiveRoot struct { - IsAdmin func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) - IsManager func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) - IsManagerFor func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) - IsSelf func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) - IsStaff func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) - IsStaffFor func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) - IsStaffOrSelf func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) - IsTrainer func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) - IsTrainerFor func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) + IsAdmin func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) + IsManagerFor func(ctx context.Context, obj any, next graphql.Resolver, makerspaceIDField *string, makerspaceIDArg *string) (res any, err error) + IsSelf func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) + IsStaffFor func(ctx context.Context, obj any, next graphql.Resolver, makerspaceIDField *string, makerspaceIDArg *string) (res any, err error) } type ComplexityRoot struct { @@ -96,6 +94,12 @@ type ComplexityRoot struct { Title func(childComplexity int) int } + AnonymousGroup struct { + Id func(childComplexity int) int + Members func(childComplexity int) int + Subgroups func(childComplexity int) int + } + CustomLink struct { LongUrl func(childComplexity int) int ShortUrl func(childComplexity int) int @@ -152,6 +156,17 @@ type ComplexityRoot struct { Name func(childComplexity int) int } + Group struct { + Description func(childComplexity int) int + DirectManagedGroups func(childComplexity int) int + DirectMembers func(childComplexity int) int + DirectSubGroups func(childComplexity int) int + Id func(childComplexity int) int + ManagerId func(childComplexity int) int + Members func(childComplexity int) int + Name func(childComplexity int) int + } + Hold struct { CreateDate func(childComplexity int) int CreatorId func(childComplexity int) int @@ -174,21 +189,33 @@ type ComplexityRoot struct { } Makerspace struct { - Description func(childComplexity int) int - DocsUrl func(childComplexity int) int - Hidden func(childComplexity int) int - Hours func(childComplexity int) int - Id func(childComplexity int) int - ImageId func(childComplexity int) int - Name func(childComplexity int) int - Subtitle func(childComplexity int) int - Timezone func(childComplexity int) int - Zones func(childComplexity int) int + Description func(childComplexity int) int + DocsUrl func(childComplexity int) int + Hidden func(childComplexity int) int + Hours func(childComplexity int) int + Id func(childComplexity int) int + ImageId func(childComplexity int) int + ManagerAnonymousGroup func(childComplexity int) int + Managers func(childComplexity int) int + Name func(childComplexity int) int + Staff func(childComplexity int) int + StaffAnonymousGroup func(childComplexity int) int + Subtitle func(childComplexity int) int + Timezone func(childComplexity int) int + Zones func(childComplexity int) int + } + + MembershipToGroup struct { + User func(childComplexity int) int + UserId func(childComplexity int) int + ViewPermission func(childComplexity int) int } Mutation struct { - CreateMakerspace func(childComplexity int, name string, hidden bool) int - DeleteMakerspace func(childComplexity int, id int) int + CreateMakerspace func(childComplexity int, name string, hidden bool) int + DeleteMakerspace func(childComplexity int, id int) int + SetManagerSubgroups func(childComplexity int, makerspaceID int, subgroups []int) int + SetStaffSubgroups func(childComplexity int, makerspaceID int, subgroups []int) int } OptionBlock struct { @@ -213,13 +240,19 @@ type ComplexityRoot struct { } Query struct { - AccessDevice func(childComplexity int, id int) int - CurrentUser func(childComplexity int) int - Device func(childComplexity int, id int) int - Makerspace func(childComplexity int, id int) int - User func(childComplexity int, id int) int - Zone func(childComplexity int, id int) int - ZonesByMakerspaceID func(childComplexity int, makerspaceID int) int + AccessDevice func(childComplexity int, id int) int + AnonymousGroup func(childComplexity int, id int) int + CanGroupManageGroup func(childComplexity int, managerID int, groupID int) int + CanUserManageGroup func(childComplexity int, managerID int, groupID int) int + CurrentUser func(childComplexity int) int + Device func(childComplexity int, id int) int + Group func(childComplexity int, id int) int + IsUserInGroup func(childComplexity int, userID int, groupID int) int + IsUserInGroupDirectly func(childComplexity int, userID int, groupID int) int + Makerspace func(childComplexity int, id int) int + User func(childComplexity int, id int) int + Zone func(childComplexity int, id int) int + ZonesByMakerspaceID func(childComplexity int, makerspaceID int) int } Reservation struct { @@ -282,7 +315,6 @@ type ComplexityRoot struct { User struct { Admin func(childComplexity int) int Archived func(childComplexity int) int - CardTag func(childComplexity int) int Email func(childComplexity int) int ForceArchive func(childComplexity int) int FullName func(childComplexity int) int @@ -309,13 +341,32 @@ type ComplexityRoot struct { type AccessComponentResolver interface { Type(ctx context.Context, obj *models.AccessComponent) (int, error) } +type AnonymousGroupResolver interface { + Members(ctx context.Context, obj *models.AnonymousGroup) ([]*models.User, error) + Subgroups(ctx context.Context, obj *models.AnonymousGroup) ([]*models.Group, error) +} +type GroupResolver interface { + DirectMembers(ctx context.Context, obj *models.Group) ([]*models.MembershipToGroup, error) + Members(ctx context.Context, obj *models.Group) ([]*models.MembershipToGroup, error) + DirectManagedGroups(ctx context.Context, obj *models.Group) ([]*models.Group, error) + DirectSubGroups(ctx context.Context, obj *models.Group) ([]*models.Group, error) +} type MakerspaceResolver interface { Zones(ctx context.Context, obj *models.Makerspace) ([]*models.Zone, error) Hours(ctx context.Context, obj *models.Makerspace) ([]models.MakerspaceHours, error) + Managers(ctx context.Context, obj *models.Makerspace) ([]*models.User, error) + Staff(ctx context.Context, obj *models.Makerspace) ([]*models.User, error) + ManagerAnonymousGroup(ctx context.Context, obj *models.Makerspace) (*models.AnonymousGroup, error) + StaffAnonymousGroup(ctx context.Context, obj *models.Makerspace) (*models.AnonymousGroup, error) +} +type MembershipToGroupResolver interface { + User(ctx context.Context, obj *models.MembershipToGroup) (*models.User, error) } type MutationResolver interface { CreateMakerspace(ctx context.Context, name string, hidden bool) (int, error) DeleteMakerspace(ctx context.Context, id int) (bool, error) + SetManagerSubgroups(ctx context.Context, makerspaceID int, subgroups []int) (bool, error) + SetStaffSubgroups(ctx context.Context, makerspaceID int, subgroups []int) (bool, error) } type OptionBlockOptionResolver interface { Correct(ctx context.Context, obj *models.OptionBlockOption) (*bool, error) @@ -324,6 +375,12 @@ type QueryResolver interface { Makerspace(ctx context.Context, id int) (*models.Makerspace, error) Device(ctx context.Context, id int) (*models.Device, error) AccessDevice(ctx context.Context, id int) (*models.AccessDevice, error) + Group(ctx context.Context, id int) (*models.Group, error) + AnonymousGroup(ctx context.Context, id int) (*models.AnonymousGroup, error) + IsUserInGroup(ctx context.Context, userID int, groupID int) (bool, error) + IsUserInGroupDirectly(ctx context.Context, userID int, groupID int) (bool, error) + CanGroupManageGroup(ctx context.Context, managerID int, groupID int) (bool, error) + CanUserManageGroup(ctx context.Context, managerID int, groupID int) (bool, error) User(ctx context.Context, id int) (*models.User, error) CurrentUser(ctx context.Context) (*models.User, error) Zone(ctx context.Context, id int) (*models.Zone, error) @@ -513,6 +570,25 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.Announcement.Title(childComplexity), true + case "AnonymousGroup.id": + if e.ComplexityRoot.AnonymousGroup.Id == nil { + break + } + + return e.ComplexityRoot.AnonymousGroup.Id(childComplexity), true + case "AnonymousGroup.members": + if e.ComplexityRoot.AnonymousGroup.Members == nil { + break + } + + return e.ComplexityRoot.AnonymousGroup.Members(childComplexity), true + case "AnonymousGroup.subgroups": + if e.ComplexityRoot.AnonymousGroup.Subgroups == nil { + break + } + + return e.ComplexityRoot.AnonymousGroup.Subgroups(childComplexity), true + case "CustomLink.long_url": if e.ComplexityRoot.CustomLink.LongUrl == nil { break @@ -747,6 +823,55 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.EquipmentInstance.Name(childComplexity), true + case "Group.description": + if e.ComplexityRoot.Group.Description == nil { + break + } + + return e.ComplexityRoot.Group.Description(childComplexity), true + case "Group.directManagedGroups": + if e.ComplexityRoot.Group.DirectManagedGroups == nil { + break + } + + return e.ComplexityRoot.Group.DirectManagedGroups(childComplexity), true + case "Group.directMembers": + if e.ComplexityRoot.Group.DirectMembers == nil { + break + } + + return e.ComplexityRoot.Group.DirectMembers(childComplexity), true + case "Group.directSubGroups": + if e.ComplexityRoot.Group.DirectSubGroups == nil { + break + } + + return e.ComplexityRoot.Group.DirectSubGroups(childComplexity), true + case "Group.id": + if e.ComplexityRoot.Group.Id == nil { + break + } + + return e.ComplexityRoot.Group.Id(childComplexity), true + case "Group.manager_id": + if e.ComplexityRoot.Group.ManagerId == nil { + break + } + + return e.ComplexityRoot.Group.ManagerId(childComplexity), true + case "Group.members": + if e.ComplexityRoot.Group.Members == nil { + break + } + + return e.ComplexityRoot.Group.Members(childComplexity), true + case "Group.name": + if e.ComplexityRoot.Group.Name == nil { + break + } + + return e.ComplexityRoot.Group.Name(childComplexity), true + case "Hold.create_date": if e.ComplexityRoot.Hold.CreateDate == nil { break @@ -858,12 +983,36 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.Makerspace.ImageId(childComplexity), true + case "Makerspace.managerAnonymousGroup": + if e.ComplexityRoot.Makerspace.ManagerAnonymousGroup == nil { + break + } + + return e.ComplexityRoot.Makerspace.ManagerAnonymousGroup(childComplexity), true + case "Makerspace.managers": + if e.ComplexityRoot.Makerspace.Managers == nil { + break + } + + return e.ComplexityRoot.Makerspace.Managers(childComplexity), true case "Makerspace.name": if e.ComplexityRoot.Makerspace.Name == nil { break } return e.ComplexityRoot.Makerspace.Name(childComplexity), true + case "Makerspace.staff": + if e.ComplexityRoot.Makerspace.Staff == nil { + break + } + + return e.ComplexityRoot.Makerspace.Staff(childComplexity), true + case "Makerspace.staffAnonymousGroup": + if e.ComplexityRoot.Makerspace.StaffAnonymousGroup == nil { + break + } + + return e.ComplexityRoot.Makerspace.StaffAnonymousGroup(childComplexity), true case "Makerspace.subtitle": if e.ComplexityRoot.Makerspace.Subtitle == nil { break @@ -883,6 +1032,25 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.Makerspace.Zones(childComplexity), true + case "MembershipToGroup.User": + if e.ComplexityRoot.MembershipToGroup.User == nil { + break + } + + return e.ComplexityRoot.MembershipToGroup.User(childComplexity), true + case "MembershipToGroup.UserId": + if e.ComplexityRoot.MembershipToGroup.UserId == nil { + break + } + + return e.ComplexityRoot.MembershipToGroup.UserId(childComplexity), true + case "MembershipToGroup.ViewPermission": + if e.ComplexityRoot.MembershipToGroup.ViewPermission == nil { + break + } + + return e.ComplexityRoot.MembershipToGroup.ViewPermission(childComplexity), true + case "Mutation.createMakerspace": if e.ComplexityRoot.Mutation.CreateMakerspace == nil { break @@ -905,6 +1073,28 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.Mutation.DeleteMakerspace(childComplexity, args["id"].(int)), true + case "Mutation.setManagerSubgroups": + if e.ComplexityRoot.Mutation.SetManagerSubgroups == nil { + break + } + + args, err := ec.field_Mutation_setManagerSubgroups_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Mutation.SetManagerSubgroups(childComplexity, args["makerspaceId"].(int), args["subgroups"].([]int)), true + case "Mutation.setStaffSubgroups": + if e.ComplexityRoot.Mutation.SetStaffSubgroups == nil { + break + } + + args, err := ec.field_Mutation_setStaffSubgroups_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Mutation.SetStaffSubgroups(childComplexity, args["makerspaceId"].(int), args["subgroups"].([]int)), true case "OptionBlock.affirmation": if e.ComplexityRoot.OptionBlock.Affirmation == nil { @@ -992,6 +1182,39 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.Query.AccessDevice(childComplexity, args["id"].(int)), true + case "Query.anonymousGroup": + if e.ComplexityRoot.Query.AnonymousGroup == nil { + break + } + + args, err := ec.field_Query_anonymousGroup_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Query.AnonymousGroup(childComplexity, args["id"].(int)), true + case "Query.canGroupManageGroup": + if e.ComplexityRoot.Query.CanGroupManageGroup == nil { + break + } + + args, err := ec.field_Query_canGroupManageGroup_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Query.CanGroupManageGroup(childComplexity, args["managerID"].(int), args["groupID"].(int)), true + case "Query.canUserManageGroup": + if e.ComplexityRoot.Query.CanUserManageGroup == nil { + break + } + + args, err := ec.field_Query_canUserManageGroup_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Query.CanUserManageGroup(childComplexity, args["managerID"].(int), args["groupID"].(int)), true case "Query.currentUser": if e.ComplexityRoot.Query.CurrentUser == nil { break @@ -1009,7 +1232,40 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.Query.Device(childComplexity, args["id"].(int)), true + case "Query.group": + if e.ComplexityRoot.Query.Group == nil { + break + } + + args, err := ec.field_Query_group_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Query.Group(childComplexity, args["id"].(int)), true + + case "Query.isUserInGroup": + if e.ComplexityRoot.Query.IsUserInGroup == nil { + break + } + + args, err := ec.field_Query_isUserInGroup_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Query.IsUserInGroup(childComplexity, args["userID"].(int), args["groupID"].(int)), true + case "Query.isUserInGroupDirectly": + if e.ComplexityRoot.Query.IsUserInGroupDirectly == nil { + break + } + + args, err := ec.field_Query_isUserInGroupDirectly_args(ctx, rawArgs) + if err != nil { + return 0, false + } + return e.ComplexityRoot.Query.IsUserInGroupDirectly(childComplexity, args["userID"].(int), args["groupID"].(int)), true case "Query.makerspace": if e.ComplexityRoot.Query.Makerspace == nil { break @@ -1290,12 +1546,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.User.Archived(childComplexity), true - case "User.card_tag": - if e.ComplexityRoot.User.CardTag == nil { - break - } - - return e.ComplexityRoot.User.CardTag(childComplexity), true case "User.email": if e.ComplexityRoot.User.Email == nil { break @@ -1457,7 +1707,7 @@ func newExecutionContext( } } -//go:embed "schema/access_channel.graphqls" "schema/announcement.graphqls" "schema/card.graphqls" "schema/device.graphqls" "schema/equipment.graphqls" "schema/hold.graphqls" "schema/hours.graphqls" "schema/image.graphqls" "schema/link.graphqls" "schema/makerspace.graphqls" "schema/organization.graphqls" "schema/reservation.graphqls" "schema/restriction.graphqls" "schema/training.graphqls" "schema/user.graphqls" "schema/zones.graphqls" +//go:embed "schema/access_channel.graphqls" "schema/announcement.graphqls" "schema/card.graphqls" "schema/device.graphqls" "schema/equipment.graphqls" "schema/group.graphqls" "schema/hold.graphqls" "schema/hours.graphqls" "schema/image.graphqls" "schema/link.graphqls" "schema/makerspace.graphqls" "schema/organization.graphqls" "schema/reservation.graphqls" "schema/restriction.graphqls" "schema/training.graphqls" "schema/user.graphqls" "schema/zones.graphqls" var sourcesFS embed.FS func sourceData(filename string) string { @@ -1474,6 +1724,7 @@ var sources = []*ast.Source{ {Name: "schema/card.graphqls", Input: sourceData("schema/card.graphqls"), BuiltIn: false}, {Name: "schema/device.graphqls", Input: sourceData("schema/device.graphqls"), BuiltIn: false}, {Name: "schema/equipment.graphqls", Input: sourceData("schema/equipment.graphqls"), BuiltIn: false}, + {Name: "schema/group.graphqls", Input: sourceData("schema/group.graphqls"), BuiltIn: false}, {Name: "schema/hold.graphqls", Input: sourceData("schema/hold.graphqls"), BuiltIn: false}, {Name: "schema/hours.graphqls", Input: sourceData("schema/hours.graphqls"), BuiltIn: false}, {Name: "schema/image.graphqls", Input: sourceData("schema/image.graphqls"), BuiltIn: false}, @@ -1550,6 +1801,18 @@ func (ec *executionContext) childFields_AccessDeviceFlags(ctx context.Context, f return nil, fmt.Errorf("no field named %q was found under type AccessDeviceFlags", field.Name) } +func (ec *executionContext) childFields_AnonymousGroup(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_AnonymousGroup_id(ctx, field) + case "members": + return ec.fieldContext_AnonymousGroup_members(ctx, field) + case "subgroups": + return ec.fieldContext_AnonymousGroup_subgroups(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type AnonymousGroup", field.Name) +} + func (ec *executionContext) childFields_Device(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "id": @@ -1574,6 +1837,28 @@ func (ec *executionContext) childFields_Device(ctx context.Context, field graphq return nil, fmt.Errorf("no field named %q was found under type Device", field.Name) } +func (ec *executionContext) childFields_Group(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Group_id(ctx, field) + case "manager_id": + return ec.fieldContext_Group_manager_id(ctx, field) + case "name": + return ec.fieldContext_Group_name(ctx, field) + case "description": + return ec.fieldContext_Group_description(ctx, field) + case "directMembers": + return ec.fieldContext_Group_directMembers(ctx, field) + case "members": + return ec.fieldContext_Group_members(ctx, field) + case "directManagedGroups": + return ec.fieldContext_Group_directManagedGroups(ctx, field) + case "directSubGroups": + return ec.fieldContext_Group_directSubGroups(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Group", field.Name) +} + func (ec *executionContext) childFields_Makerspace(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "id": @@ -1596,10 +1881,30 @@ func (ec *executionContext) childFields_Makerspace(ctx context.Context, field gr return ec.fieldContext_Makerspace_zones(ctx, field) case "hours": return ec.fieldContext_Makerspace_hours(ctx, field) + case "managers": + return ec.fieldContext_Makerspace_managers(ctx, field) + case "staff": + return ec.fieldContext_Makerspace_staff(ctx, field) + case "managerAnonymousGroup": + return ec.fieldContext_Makerspace_managerAnonymousGroup(ctx, field) + case "staffAnonymousGroup": + return ec.fieldContext_Makerspace_staffAnonymousGroup(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Makerspace", field.Name) } +func (ec *executionContext) childFields_MembershipToGroup(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "UserId": + return ec.fieldContext_MembershipToGroup_UserId(ctx, field) + case "User": + return ec.fieldContext_MembershipToGroup_User(ctx, field) + case "ViewPermission": + return ec.fieldContext_MembershipToGroup_ViewPermission(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MembershipToGroup", field.Name) +} + func (ec *executionContext) childFields_OptionBlockOption(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "text": @@ -1634,8 +1939,6 @@ func (ec *executionContext) childFields_User(ctx context.Context, field graphql. return ec.fieldContext_User_admin(ctx, field) case "force_archive": return ec.fieldContext_User_force_archive(ctx, field) - case "card_tag": - return ec.fieldContext_User_card_tag(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) } @@ -1770,6 +2073,50 @@ func (ec *executionContext) childFields___Type(ctx context.Context, field graphq // region ***************************** args.gotpl ***************************** +func (ec *executionContext) dir_isManagerFor_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "makerspaceIdField", + func(ctx context.Context, v any) (*string, error) { + return ec.unmarshalOString2ᚖstring(ctx, v) + }) + if err != nil { + return nil, err + } + args["makerspaceIdField"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "makerspaceIdArg", + func(ctx context.Context, v any) (*string, error) { + return ec.unmarshalOString2ᚖstring(ctx, v) + }) + if err != nil { + return nil, err + } + args["makerspaceIdArg"] = arg1 + return args, nil +} + +func (ec *executionContext) dir_isStaffFor_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "makerspaceIdField", + func(ctx context.Context, v any) (*string, error) { + return ec.unmarshalOString2ᚖstring(ctx, v) + }) + if err != nil { + return nil, err + } + args["makerspaceIdField"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "makerspaceIdArg", + func(ctx context.Context, v any) (*string, error) { + return ec.unmarshalOString2ᚖstring(ctx, v) + }) + if err != nil { + return nil, err + } + args["makerspaceIdArg"] = arg1 + return args, nil +} + func (ec *executionContext) field_Mutation_createMakerspace_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -1806,49 +2153,65 @@ func (ec *executionContext) field_Mutation_deleteMakerspace_args(ctx context.Con return args, nil } -func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Mutation_setManagerSubgroups_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := graphql.ProcessArgField(ctx, rawArgs, "name", - func(ctx context.Context, v any) (string, error) { - return ec.unmarshalNString2string(ctx, v) + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "makerspaceId", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) }) if err != nil { return nil, err } - args["name"] = arg0 + args["makerspaceId"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "subgroups", + func(ctx context.Context, v any) ([]int, error) { + return ec.unmarshalNInt2ᚕintᚄ(ctx, v) + }) + if err != nil { + return nil, err + } + args["subgroups"] = arg1 return args, nil } -func (ec *executionContext) field_Query_accessDevice_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Mutation_setStaffSubgroups_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "makerspaceId", func(ctx context.Context, v any) (int, error) { return ec.unmarshalNID2int(ctx, v) }) if err != nil { return nil, err } - args["id"] = arg0 + args["makerspaceId"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "subgroups", + func(ctx context.Context, v any) ([]int, error) { + return ec.unmarshalNInt2ᚕintᚄ(ctx, v) + }) + if err != nil { + return nil, err + } + args["subgroups"] = arg1 return args, nil } -func (ec *executionContext) field_Query_device_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", - func(ctx context.Context, v any) (int, error) { - return ec.unmarshalNID2int(ctx, v) + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "name", + func(ctx context.Context, v any) (string, error) { + return ec.unmarshalNString2string(ctx, v) }) if err != nil { return nil, err } - args["id"] = arg0 + args["name"] = arg0 return args, nil } -func (ec *executionContext) field_Query_makerspace_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Query_accessDevice_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", @@ -1862,7 +2225,7 @@ func (ec *executionContext) field_Query_makerspace_args(ctx context.Context, raw return args, nil } -func (ec *executionContext) field_Query_user_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Query_anonymousGroup_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", @@ -1876,88 +2239,232 @@ func (ec *executionContext) field_Query_user_args(ctx context.Context, rawArgs m return args, nil } -func (ec *executionContext) field_Query_zone_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Query_canGroupManageGroup_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "managerID", func(ctx context.Context, v any) (int, error) { return ec.unmarshalNID2int(ctx, v) }) if err != nil { return nil, err } - args["id"] = arg0 + args["managerID"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "groupID", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) + }) + if err != nil { + return nil, err + } + args["groupID"] = arg1 return args, nil } -func (ec *executionContext) field_Query_zonesByMakerspaceId_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Query_canUserManageGroup_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := graphql.ProcessArgField(ctx, rawArgs, "makerspace_id", + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "managerID", func(ctx context.Context, v any) (int, error) { return ec.unmarshalNID2int(ctx, v) }) if err != nil { return nil, err } - args["makerspace_id"] = arg0 + args["managerID"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "groupID", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) + }) + if err != nil { + return nil, err + } + args["groupID"] = arg1 return args, nil } -func (ec *executionContext) field___Directive_args_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Query_device_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := graphql.ProcessArgField(ctx, rawArgs, "includeDeprecated", - func(ctx context.Context, v any) (*bool, error) { - return ec.unmarshalOBoolean2ᚖbool(ctx, v) + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) }) if err != nil { return nil, err } - args["includeDeprecated"] = arg0 + args["id"] = arg0 return args, nil } -func (ec *executionContext) field___Field_args_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Query_group_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := graphql.ProcessArgField(ctx, rawArgs, "includeDeprecated", - func(ctx context.Context, v any) (*bool, error) { - return ec.unmarshalOBoolean2ᚖbool(ctx, v) + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) }) if err != nil { return nil, err } - args["includeDeprecated"] = arg0 + args["id"] = arg0 return args, nil } -func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Query_isUserInGroupDirectly_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := graphql.ProcessArgField(ctx, rawArgs, "includeDeprecated", - func(ctx context.Context, v any) (bool, error) { - return ec.unmarshalOBoolean2bool(ctx, v) + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "userID", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) }) if err != nil { return nil, err } - args["includeDeprecated"] = arg0 + args["userID"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "groupID", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) + }) + if err != nil { + return nil, err + } + args["groupID"] = arg1 return args, nil } -func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +func (ec *executionContext) field_Query_isUserInGroup_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := graphql.ProcessArgField(ctx, rawArgs, "includeDeprecated", - func(ctx context.Context, v any) (bool, error) { - return ec.unmarshalOBoolean2bool(ctx, v) + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "userID", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) }) if err != nil { return nil, err } - args["includeDeprecated"] = arg0 - return args, nil + args["userID"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "groupID", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) + }) + if err != nil { + return nil, err + } + args["groupID"] = arg1 + return args, nil +} + +func (ec *executionContext) field_Query_makerspace_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) + }) + if err != nil { + return nil, err + } + args["id"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query_user_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) + }) + if err != nil { + return nil, err + } + args["id"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query_zone_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "id", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) + }) + if err != nil { + return nil, err + } + args["id"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query_zonesByMakerspaceId_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "makerspace_id", + func(ctx context.Context, v any) (int, error) { + return ec.unmarshalNID2int(ctx, v) + }) + if err != nil { + return nil, err + } + args["makerspace_id"] = arg0 + return args, nil +} + +func (ec *executionContext) field___Directive_args_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "includeDeprecated", + func(ctx context.Context, v any) (*bool, error) { + return ec.unmarshalOBoolean2ᚖbool(ctx, v) + }) + if err != nil { + return nil, err + } + args["includeDeprecated"] = arg0 + return args, nil +} + +func (ec *executionContext) field___Field_args_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "includeDeprecated", + func(ctx context.Context, v any) (*bool, error) { + return ec.unmarshalOBoolean2ᚖbool(ctx, v) + }) + if err != nil { + return nil, err + } + args["includeDeprecated"] = arg0 + return args, nil +} + +func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "includeDeprecated", + func(ctx context.Context, v any) (bool, error) { + return ec.unmarshalOBoolean2bool(ctx, v) + }) + if err != nil { + return nil, err + } + args["includeDeprecated"] = arg0 + return args, nil +} + +func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "includeDeprecated", + func(ctx context.Context, v any) (bool, error) { + return ec.unmarshalOBoolean2bool(ctx, v) + }) + if err != nil { + return nil, err + } + args["includeDeprecated"] = arg0 + return args, nil } // endregion ***************************** args.gotpl ***************************** @@ -2616,6 +3123,93 @@ func (ec *executionContext) fieldContext_Announcement_makerspace_id(_ context.Co return graphql.NewScalarFieldContext("Announcement", field, false, false, errors.New("field of type ID does not have child fields")) } +func (ec *executionContext) _AnonymousGroup_id(ctx context.Context, field graphql.CollectedField, obj *models.AnonymousGroup) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_AnonymousGroup_id(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Id, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNID2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_AnonymousGroup_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("AnonymousGroup", field, false, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _AnonymousGroup_members(ctx context.Context, field graphql.CollectedField, obj *models.AnonymousGroup) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_AnonymousGroup_members(ctx, field) + }, + func(ctx context.Context) (any, error) { + return ec.Resolvers.AnonymousGroup().Members(ctx, obj) + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v []*models.User) graphql.Marshaler { + return ec.marshalNUser2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐUserᚄ(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_AnonymousGroup_members(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AnonymousGroup", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_User(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _AnonymousGroup_subgroups(ctx context.Context, field graphql.CollectedField, obj *models.AnonymousGroup) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_AnonymousGroup_subgroups(ctx, field) + }, + func(ctx context.Context) (any, error) { + return ec.Resolvers.AnonymousGroup().Subgroups(ctx, obj) + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v []*models.Group) graphql.Marshaler { + return ec.marshalNGroup2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroupᚄ(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_AnonymousGroup_subgroups(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "AnonymousGroup", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_Group(ctx, field) + }, + } + return fc, nil +} + func (ec *executionContext) _CustomLink_short_url(ctx context.Context, field graphql.CollectedField, obj *models.CustomLink) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -3503,13 +4097,13 @@ func (ec *executionContext) fieldContext_EquipmentInstance_access_channel_id(_ c return graphql.NewScalarFieldContext("EquipmentInstance", field, false, false, errors.New("field of type ID does not have child fields")) } -func (ec *executionContext) _Hold_id(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { +func (ec *executionContext) _Group_id(ctx context.Context, field graphql.CollectedField, obj *models.Group) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Hold_id(ctx, field) + return ec.fieldContext_Group_id(ctx, field) }, func(ctx context.Context) (any, error) { return obj.Id, nil @@ -3522,20 +4116,20 @@ func (ec *executionContext) _Hold_id(ctx context.Context, field graphql.Collecte true, ) } -func (ec *executionContext) fieldContext_Hold_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type ID does not have child fields")) +func (ec *executionContext) fieldContext_Group_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Group", field, false, false, errors.New("field of type ID does not have child fields")) } -func (ec *executionContext) _Hold_creator_id(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { +func (ec *executionContext) _Group_manager_id(ctx context.Context, field graphql.CollectedField, obj *models.Group) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Hold_creator_id(ctx, field) + return ec.fieldContext_Group_manager_id(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.CreatorId, nil + return obj.ManagerId, nil }, nil, func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { @@ -3545,122 +4139,342 @@ func (ec *executionContext) _Hold_creator_id(ctx context.Context, field graphql. true, ) } -func (ec *executionContext) fieldContext_Hold_creator_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type ID does not have child fields")) +func (ec *executionContext) fieldContext_Group_manager_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Group", field, false, false, errors.New("field of type ID does not have child fields")) } -func (ec *executionContext) _Hold_remover_id(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { +func (ec *executionContext) _Group_name(ctx context.Context, field graphql.CollectedField, obj *models.Group) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Hold_remover_id(ctx, field) + return ec.fieldContext_Group_name(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.RemoverId, nil + return obj.Name, nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v *int) graphql.Marshaler { - return ec.marshalOID2ᚖint(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) }, true, - false, + true, ) } -func (ec *executionContext) fieldContext_Hold_remover_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type ID does not have child fields")) +func (ec *executionContext) fieldContext_Group_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Group", field, false, false, errors.New("field of type String does not have child fields")) } -func (ec *executionContext) _Hold_target_id(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { +func (ec *executionContext) _Group_description(ctx context.Context, field graphql.CollectedField, obj *models.Group) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Hold_target_id(ctx, field) + return ec.fieldContext_Group_description(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.TargetId, nil + return obj.Description, nil }, nil, - func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { - return ec.marshalNID2int(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_Hold_target_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type ID does not have child fields")) +func (ec *executionContext) fieldContext_Group_description(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Group", field, false, false, errors.New("field of type String does not have child fields")) } -func (ec *executionContext) _Hold_reason(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { +func (ec *executionContext) _Group_directMembers(ctx context.Context, field graphql.CollectedField, obj *models.Group) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Hold_reason(ctx, field) + return ec.fieldContext_Group_directMembers(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Reason, nil + return ec.Resolvers.Group().DirectMembers(ctx, obj) }, nil, - func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { - return ec.marshalNString2string(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v []*models.MembershipToGroup) graphql.Marshaler { + return ec.marshalNMembershipToGroup2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐMembershipToGroupᚄ(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_Hold_reason(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type String does not have child fields")) +func (ec *executionContext) fieldContext_Group_directMembers(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Group", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_MembershipToGroup(ctx, field) + }, + } + return fc, nil } -func (ec *executionContext) _Hold_create_date(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { +func (ec *executionContext) _Group_members(ctx context.Context, field graphql.CollectedField, obj *models.Group) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Hold_create_date(ctx, field) + return ec.fieldContext_Group_members(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.CreateDate, nil + return ec.Resolvers.Group().Members(ctx, obj) }, nil, - func(ctx context.Context, selections ast.SelectionSet, v time.Time) graphql.Marshaler { - return ec.marshalNTime2timeᚐTime(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v []*models.MembershipToGroup) graphql.Marshaler { + return ec.marshalNMembershipToGroup2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐMembershipToGroupᚄ(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_Hold_create_date(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type Time does not have child fields")) +func (ec *executionContext) fieldContext_Group_members(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Group", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_MembershipToGroup(ctx, field) + }, + } + return fc, nil } -func (ec *executionContext) _Hold_remove_date(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { +func (ec *executionContext) _Group_directManagedGroups(ctx context.Context, field graphql.CollectedField, obj *models.Group) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Hold_remove_date(ctx, field) + return ec.fieldContext_Group_directManagedGroups(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.RemoveDate, nil + return ec.Resolvers.Group().DirectManagedGroups(ctx, obj) }, nil, - func(ctx context.Context, selections ast.SelectionSet, v *time.Time) graphql.Marshaler { - return ec.marshalOTime2ᚖtimeᚐTime(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v []*models.Group) graphql.Marshaler { + return ec.marshalNGroup2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroupᚄ(ctx, selections, v) }, true, - false, + true, ) } -func (ec *executionContext) fieldContext_Hold_remove_date(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Group_directManagedGroups(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Group", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_Group(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _Group_directSubGroups(ctx context.Context, field graphql.CollectedField, obj *models.Group) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Group_directSubGroups(ctx, field) + }, + func(ctx context.Context) (any, error) { + return ec.Resolvers.Group().DirectSubGroups(ctx, obj) + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v []*models.Group) graphql.Marshaler { + return ec.marshalNGroup2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroupᚄ(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Group_directSubGroups(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Group", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_Group(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _Hold_id(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Hold_id(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Id, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNID2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Hold_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _Hold_creator_id(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Hold_creator_id(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.CreatorId, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNID2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Hold_creator_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _Hold_remover_id(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Hold_remover_id(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.RemoverId, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *int) graphql.Marshaler { + return ec.marshalOID2ᚖint(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_Hold_remover_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _Hold_target_id(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Hold_target_id(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.TargetId, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNID2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Hold_target_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _Hold_reason(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Hold_reason(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Reason, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Hold_reason(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _Hold_create_date(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Hold_create_date(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.CreateDate, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v time.Time) graphql.Marshaler { + return ec.marshalNTime2timeᚐTime(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Hold_create_date(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type Time does not have child fields")) +} + +func (ec *executionContext) _Hold_remove_date(ctx context.Context, field graphql.CollectedField, obj *models.Hold) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Hold_remove_date(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.RemoveDate, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *time.Time) graphql.Marshaler { + return ec.marshalOTime2ᚖtimeᚐTime(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_Hold_remove_date(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { return graphql.NewScalarFieldContext("Hold", field, false, false, errors.New("field of type Time does not have child fields")) } @@ -4018,87 +4832,391 @@ func (ec *executionContext) fieldContext_Makerspace_hours(_ context.Context, fie return graphql.NewScalarFieldContext("Makerspace", field, true, true, errors.New("field of type MakerspaceHours does not have child fields")) } -func (ec *executionContext) _Mutation_createMakerspace(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Makerspace_managers(ctx context.Context, field graphql.CollectedField, obj *models.Makerspace) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Mutation_createMakerspace(ctx, field) + return ec.fieldContext_Makerspace_managers(ctx, field) }, func(ctx context.Context) (any, error) { - fc := graphql.GetFieldContext(ctx) - return ec.Resolvers.Mutation().CreateMakerspace(ctx, fc.Args["name"].(string), fc.Args["hidden"].(bool)) + return ec.Resolvers.Makerspace().Managers(ctx, obj) }, - nil, - func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { - return ec.marshalNID2int(ctx, selections, v) + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + makerspaceIDField, err := ec.unmarshalOString2ᚖstring(ctx, "id") + if err != nil { + var zeroVal []*models.User + return zeroVal, err + } + if ec.Directives.IsManagerFor == nil { + var zeroVal []*models.User + return zeroVal, errors.New("directive isManagerFor is not implemented") + } + return ec.Directives.IsManagerFor(ctx, obj, directive0, makerspaceIDField, nil) + } + + next = directive1 + return next + }, + func(ctx context.Context, selections ast.SelectionSet, v []*models.User) graphql.Marshaler { + return ec.marshalNUser2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐUserᚄ(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_Mutation_createMakerspace(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Makerspace_managers(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "Mutation", + Object: "Makerspace", Field: field, IsMethod: true, IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type ID does not have child fields") + return ec.childFields_User(ctx, field) }, } - defer func() { - if r := recover(); r != nil { - err = ec.Recover(ctx, r) - ec.Error(ctx, err) - } - }() - ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Mutation_createMakerspace_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } return fc, nil } -func (ec *executionContext) _Mutation_deleteMakerspace(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Makerspace_staff(ctx context.Context, field graphql.CollectedField, obj *models.Makerspace) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Mutation_deleteMakerspace(ctx, field) + return ec.fieldContext_Makerspace_staff(ctx, field) }, func(ctx context.Context) (any, error) { - fc := graphql.GetFieldContext(ctx) - return ec.Resolvers.Mutation().DeleteMakerspace(ctx, fc.Args["id"].(int)) + return ec.Resolvers.Makerspace().Staff(ctx, obj) }, - nil, - func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { - return ec.marshalNBoolean2bool(ctx, selections, v) + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + makerspaceIDField, err := ec.unmarshalOString2ᚖstring(ctx, "id") + if err != nil { + var zeroVal []*models.User + return zeroVal, err + } + if ec.Directives.IsStaffFor == nil { + var zeroVal []*models.User + return zeroVal, errors.New("directive isStaffFor is not implemented") + } + return ec.Directives.IsStaffFor(ctx, obj, directive0, makerspaceIDField, nil) + } + + next = directive1 + return next + }, + func(ctx context.Context, selections ast.SelectionSet, v []*models.User) graphql.Marshaler { + return ec.marshalNUser2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐUserᚄ(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_Mutation_deleteMakerspace(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Makerspace_staff(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "Mutation", + Object: "Makerspace", Field: field, IsMethod: true, IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type Boolean does not have child fields") + return ec.childFields_User(ctx, field) }, } - defer func() { - if r := recover(); r != nil { - err = ec.Recover(ctx, r) - ec.Error(ctx, err) - } - }() - ctx = graphql.WithFieldContext(ctx, fc) + return fc, nil +} + +func (ec *executionContext) _Makerspace_managerAnonymousGroup(ctx context.Context, field graphql.CollectedField, obj *models.Makerspace) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Makerspace_managerAnonymousGroup(ctx, field) + }, + func(ctx context.Context) (any, error) { + return ec.Resolvers.Makerspace().ManagerAnonymousGroup(ctx, obj) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + makerspaceIDField, err := ec.unmarshalOString2ᚖstring(ctx, "id") + if err != nil { + var zeroVal *models.AnonymousGroup + return zeroVal, err + } + if ec.Directives.IsManagerFor == nil { + var zeroVal *models.AnonymousGroup + return zeroVal, errors.New("directive isManagerFor is not implemented") + } + return ec.Directives.IsManagerFor(ctx, obj, directive0, makerspaceIDField, nil) + } + + next = directive1 + return next + }, + func(ctx context.Context, selections ast.SelectionSet, v *models.AnonymousGroup) graphql.Marshaler { + return ec.marshalOAnonymousGroup2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐAnonymousGroup(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_Makerspace_managerAnonymousGroup(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Makerspace", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_AnonymousGroup(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _Makerspace_staffAnonymousGroup(ctx context.Context, field graphql.CollectedField, obj *models.Makerspace) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Makerspace_staffAnonymousGroup(ctx, field) + }, + func(ctx context.Context) (any, error) { + return ec.Resolvers.Makerspace().StaffAnonymousGroup(ctx, obj) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + makerspaceIDField, err := ec.unmarshalOString2ᚖstring(ctx, "id") + if err != nil { + var zeroVal *models.AnonymousGroup + return zeroVal, err + } + if ec.Directives.IsManagerFor == nil { + var zeroVal *models.AnonymousGroup + return zeroVal, errors.New("directive isManagerFor is not implemented") + } + return ec.Directives.IsManagerFor(ctx, obj, directive0, makerspaceIDField, nil) + } + + next = directive1 + return next + }, + func(ctx context.Context, selections ast.SelectionSet, v *models.AnonymousGroup) graphql.Marshaler { + return ec.marshalOAnonymousGroup2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐAnonymousGroup(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_Makerspace_staffAnonymousGroup(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Makerspace", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_AnonymousGroup(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _MembershipToGroup_UserId(ctx context.Context, field graphql.CollectedField, obj *models.MembershipToGroup) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_MembershipToGroup_UserId(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.UserId, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNID2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_MembershipToGroup_UserId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("MembershipToGroup", field, false, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _MembershipToGroup_User(ctx context.Context, field graphql.CollectedField, obj *models.MembershipToGroup) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_MembershipToGroup_User(ctx, field) + }, + func(ctx context.Context) (any, error) { + return ec.Resolvers.MembershipToGroup().User(ctx, obj) + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *models.User) graphql.Marshaler { + return ec.marshalNUser2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐUser(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_MembershipToGroup_User(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MembershipToGroup", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_User(ctx, field) + }, + } + return fc, nil +} + +func (ec *executionContext) _MembershipToGroup_ViewPermission(ctx context.Context, field graphql.CollectedField, obj *models.MembershipToGroup) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_MembershipToGroup_ViewPermission(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.ViewPermission, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v models.GroupViewPermission) graphql.Marshaler { + return ec.marshalNGroupViewPermission2makeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroupViewPermission(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_MembershipToGroup_ViewPermission(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("MembershipToGroup", field, false, false, errors.New("field of type GroupViewPermission does not have child fields")) +} + +func (ec *executionContext) _Mutation_createMakerspace(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Mutation_createMakerspace(ctx, field) + }, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Mutation().CreateMakerspace(ctx, fc.Args["name"].(string), fc.Args["hidden"].(bool)) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + if ec.Directives.IsAdmin == nil { + var zeroVal int + return zeroVal, errors.New("directive isAdmin is not implemented") + } + return ec.Directives.IsAdmin(ctx, nil, directive0) + } + + next = directive1 + return next + }, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNID2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Mutation_createMakerspace(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_createMakerspace_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_deleteMakerspace(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Mutation_deleteMakerspace(ctx, field) + }, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Mutation().DeleteMakerspace(ctx, fc.Args["id"].(int)) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + if ec.Directives.IsAdmin == nil { + var zeroVal bool + return zeroVal, errors.New("directive isAdmin is not implemented") + } + return ec.Directives.IsAdmin(ctx, nil, directive0) + } + + next = directive1 + return next + }, + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Mutation_deleteMakerspace(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) if fc.Args, err = ec.field_Mutation_deleteMakerspace_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err @@ -4106,6 +5224,130 @@ func (ec *executionContext) fieldContext_Mutation_deleteMakerspace(ctx context.C return fc, nil } +func (ec *executionContext) _Mutation_setManagerSubgroups(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Mutation_setManagerSubgroups(ctx, field) + }, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Mutation().SetManagerSubgroups(ctx, fc.Args["makerspaceId"].(int), fc.Args["subgroups"].([]int)) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + makerspaceIDArg, err := ec.unmarshalOString2ᚖstring(ctx, "makerspaceId") + if err != nil { + var zeroVal bool + return zeroVal, err + } + if ec.Directives.IsManagerFor == nil { + var zeroVal bool + return zeroVal, errors.New("directive isManagerFor is not implemented") + } + return ec.Directives.IsManagerFor(ctx, nil, directive0, nil, makerspaceIDArg) + } + + next = directive1 + return next + }, + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Mutation_setManagerSubgroups(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_setManagerSubgroups_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_setStaffSubgroups(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Mutation_setStaffSubgroups(ctx, field) + }, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Mutation().SetStaffSubgroups(ctx, fc.Args["makerspaceId"].(int), fc.Args["subgroups"].([]int)) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + makerspaceIDArg, err := ec.unmarshalOString2ᚖstring(ctx, "makerspaceId") + if err != nil { + var zeroVal bool + return zeroVal, err + } + if ec.Directives.IsManagerFor == nil { + var zeroVal bool + return zeroVal, errors.New("directive isManagerFor is not implemented") + } + return ec.Directives.IsManagerFor(ctx, nil, directive0, nil, makerspaceIDArg) + } + + next = directive1 + return next + }, + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Mutation_setStaffSubgroups(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_setStaffSubgroups_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _OptionBlock_block_id(ctx context.Context, field graphql.CollectedField, obj *models.OptionBlock) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -4299,126 +5541,390 @@ func (ec *executionContext) fieldContext_OptionBlockOption_correct(_ context.Con return graphql.NewScalarFieldContext("OptionBlockOption", field, true, true, errors.New("field of type Boolean does not have child fields")) } -func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *models.Organization) (ret graphql.Marshaler) { +func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *models.Organization) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Organization_id(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Id, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { + return ec.marshalNID2int(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Organization_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Organization", field, false, false, errors.New("field of type ID does not have child fields")) +} + +func (ec *executionContext) _Organization_display_name(ctx context.Context, field graphql.CollectedField, obj *models.Organization) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Organization_display_name(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.DisplayName, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Organization_display_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Organization", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _Organization_email(ctx context.Context, field graphql.CollectedField, obj *models.Organization) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Organization_email(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Email, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Organization_email(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Organization", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _Organization_notes(ctx context.Context, field graphql.CollectedField, obj *models.Organization) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Organization_notes(ctx, field) + }, + func(ctx context.Context) (any, error) { + return obj.Notes, nil + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { + return ec.marshalNString2string(ctx, selections, v) + }, + true, + true, + ) +} +func (ec *executionContext) fieldContext_Organization_notes(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + return graphql.NewScalarFieldContext("Organization", field, false, false, errors.New("field of type String does not have child fields")) +} + +func (ec *executionContext) _Query_makerspace(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Query_makerspace(ctx, field) + }, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Query().Makerspace(ctx, fc.Args["id"].(int)) + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *models.Makerspace) graphql.Marshaler { + return ec.marshalOMakerspace2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐMakerspace(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_Query_makerspace(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_Makerspace(ctx, field) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_makerspace_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query_device(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.fieldContext_Query_device(ctx, field) + }, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Query().Device(ctx, fc.Args["id"].(int)) + }, + nil, + func(ctx context.Context, selections ast.SelectionSet, v *models.Device) graphql.Marshaler { + return ec.marshalODevice2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐDevice(ctx, selections, v) + }, + true, + false, + ) +} +func (ec *executionContext) fieldContext_Query_device(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_Device(ctx, field) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_device_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Query_accessDevice(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Organization_id(ctx, field) + return ec.fieldContext_Query_accessDevice(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Id, nil + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Query().AccessDevice(ctx, fc.Args["id"].(int)) }, nil, - func(ctx context.Context, selections ast.SelectionSet, v int) graphql.Marshaler { - return ec.marshalNID2int(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v *models.AccessDevice) graphql.Marshaler { + return ec.marshalOAccessDevice2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐAccessDevice(ctx, selections, v) }, true, - true, + false, ) } -func (ec *executionContext) fieldContext_Organization_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("Organization", field, false, false, errors.New("field of type ID does not have child fields")) +func (ec *executionContext) fieldContext_Query_accessDevice(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_AccessDevice(ctx, field) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_accessDevice_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil } -func (ec *executionContext) _Organization_display_name(ctx context.Context, field graphql.CollectedField, obj *models.Organization) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_group(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Organization_display_name(ctx, field) + return ec.fieldContext_Query_group(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.DisplayName, nil + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Query().Group(ctx, fc.Args["id"].(int)) }, nil, - func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { - return ec.marshalNString2string(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v *models.Group) graphql.Marshaler { + return ec.marshalNGroup2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroup(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_Organization_display_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("Organization", field, false, false, errors.New("field of type String does not have child fields")) +func (ec *executionContext) fieldContext_Query_group(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_Group(ctx, field) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_group_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil } -func (ec *executionContext) _Organization_email(ctx context.Context, field graphql.CollectedField, obj *models.Organization) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_anonymousGroup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Organization_email(ctx, field) + return ec.fieldContext_Query_anonymousGroup(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Email, nil + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Query().AnonymousGroup(ctx, fc.Args["id"].(int)) }, nil, - func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { - return ec.marshalNString2string(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v *models.AnonymousGroup) graphql.Marshaler { + return ec.marshalNAnonymousGroup2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐAnonymousGroup(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_Organization_email(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("Organization", field, false, false, errors.New("field of type String does not have child fields")) +func (ec *executionContext) fieldContext_Query_anonymousGroup(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return ec.childFields_AnonymousGroup(ctx, field) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_anonymousGroup_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil } -func (ec *executionContext) _Organization_notes(ctx context.Context, field graphql.CollectedField, obj *models.Organization) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_isUserInGroup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Organization_notes(ctx, field) + return ec.fieldContext_Query_isUserInGroup(ctx, field) }, func(ctx context.Context) (any, error) { - return obj.Notes, nil + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Query().IsUserInGroup(ctx, fc.Args["userID"].(int), fc.Args["groupID"].(int)) }, nil, - func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { - return ec.marshalNString2string(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) }, true, true, ) } -func (ec *executionContext) fieldContext_Organization_notes(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("Organization", field, false, false, errors.New("field of type String does not have child fields")) +func (ec *executionContext) fieldContext_Query_isUserInGroup(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_isUserInGroup_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil } -func (ec *executionContext) _Query_makerspace(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_isUserInGroupDirectly(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Query_makerspace(ctx, field) + return ec.fieldContext_Query_isUserInGroupDirectly(ctx, field) }, func(ctx context.Context) (any, error) { fc := graphql.GetFieldContext(ctx) - return ec.Resolvers.Query().Makerspace(ctx, fc.Args["id"].(int)) + return ec.Resolvers.Query().IsUserInGroupDirectly(ctx, fc.Args["userID"].(int), fc.Args["groupID"].(int)) }, nil, - func(ctx context.Context, selections ast.SelectionSet, v *models.Makerspace) graphql.Marshaler { - return ec.marshalOMakerspace2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐMakerspace(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) }, true, - false, + true, ) } -func (ec *executionContext) fieldContext_Query_makerspace(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Query_isUserInGroupDirectly(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Query", Field: field, IsMethod: true, IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.childFields_Makerspace(ctx, field) + return nil, errors.New("field of type Boolean does not have child fields") }, } defer func() { @@ -4428,41 +5934,41 @@ func (ec *executionContext) fieldContext_Query_makerspace(ctx context.Context, f } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_makerspace_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Query_isUserInGroupDirectly_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } return fc, nil } -func (ec *executionContext) _Query_device(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_canGroupManageGroup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Query_device(ctx, field) + return ec.fieldContext_Query_canGroupManageGroup(ctx, field) }, func(ctx context.Context) (any, error) { fc := graphql.GetFieldContext(ctx) - return ec.Resolvers.Query().Device(ctx, fc.Args["id"].(int)) + return ec.Resolvers.Query().CanGroupManageGroup(ctx, fc.Args["managerID"].(int), fc.Args["groupID"].(int)) }, nil, - func(ctx context.Context, selections ast.SelectionSet, v *models.Device) graphql.Marshaler { - return ec.marshalODevice2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐDevice(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) }, true, - false, + true, ) } -func (ec *executionContext) fieldContext_Query_device(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Query_canGroupManageGroup(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Query", Field: field, IsMethod: true, IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.childFields_Device(ctx, field) + return nil, errors.New("field of type Boolean does not have child fields") }, } defer func() { @@ -4472,41 +5978,41 @@ func (ec *executionContext) fieldContext_Query_device(ctx context.Context, field } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_device_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Query_canGroupManageGroup_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } return fc, nil } -func (ec *executionContext) _Query_accessDevice(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_canUserManageGroup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_Query_accessDevice(ctx, field) + return ec.fieldContext_Query_canUserManageGroup(ctx, field) }, func(ctx context.Context) (any, error) { fc := graphql.GetFieldContext(ctx) - return ec.Resolvers.Query().AccessDevice(ctx, fc.Args["id"].(int)) + return ec.Resolvers.Query().CanUserManageGroup(ctx, fc.Args["managerID"].(int), fc.Args["groupID"].(int)) }, nil, - func(ctx context.Context, selections ast.SelectionSet, v *models.AccessDevice) graphql.Marshaler { - return ec.marshalOAccessDevice2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐAccessDevice(ctx, selections, v) + func(ctx context.Context, selections ast.SelectionSet, v bool) graphql.Marshaler { + return ec.marshalNBoolean2bool(ctx, selections, v) }, true, - false, + true, ) } -func (ec *executionContext) fieldContext_Query_accessDevice(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Query_canUserManageGroup(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Query", Field: field, IsMethod: true, IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.childFields_AccessDevice(ctx, field) + return nil, errors.New("field of type Boolean does not have child fields") }, } defer func() { @@ -4516,7 +6022,7 @@ func (ec *executionContext) fieldContext_Query_accessDevice(ctx context.Context, } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Query_accessDevice_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Query_canUserManageGroup_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } @@ -4535,7 +6041,20 @@ func (ec *executionContext) _Query_user(ctx context.Context, field graphql.Colle fc := graphql.GetFieldContext(ctx) return ec.Resolvers.Query().User(ctx, fc.Args["id"].(int)) }, - nil, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + if ec.Directives.IsSelf == nil { + var zeroVal *models.User + return zeroVal, errors.New("directive isSelf is not implemented") + } + return ec.Directives.IsSelf(ctx, nil, directive0) + } + + next = directive1 + return next + }, func(ctx context.Context, selections ast.SelectionSet, v *models.User) graphql.Marshaler { return ec.marshalNUser2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐUser(ctx, selections, v) }, @@ -5844,42 +7363,6 @@ func (ec *executionContext) fieldContext_User_force_archive(_ context.Context, f return graphql.NewScalarFieldContext("User", field, false, false, errors.New("field of type Boolean does not have child fields")) } -func (ec *executionContext) _User_card_tag(ctx context.Context, field graphql.CollectedField, obj *models.User) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return ec.fieldContext_User_card_tag(ctx, field) - }, - func(ctx context.Context) (any, error) { - return obj.CardTag, nil - }, - func(ctx context.Context, next graphql.Resolver) graphql.Resolver { - directive0 := next - - directive1 := func(ctx context.Context) (any, error) { - if ec.Directives.IsStaff == nil { - var zeroVal string - return zeroVal, errors.New("directive isStaff is not implemented") - } - return ec.Directives.IsStaff(ctx, obj, directive0) - } - - next = directive1 - return next - }, - func(ctx context.Context, selections ast.SelectionSet, v string) graphql.Marshaler { - return ec.marshalNString2string(ctx, selections, v) - }, - true, - true, - ) -} -func (ec *executionContext) fieldContext_User_card_tag(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - return graphql.NewScalarFieldContext("User", field, false, false, errors.New("field of type String does not have child fields")) -} - func (ec *executionContext) _Zone_id(ctx context.Context, field graphql.CollectedField, obj *models.Zone) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -7376,14 +8859,67 @@ func (ec *executionContext) _AccessDeviceFlags(ctx context.Context, sel ast.Sele if out.Values[i] == graphql.Null { out.Invalids++ } - case "restart_when_unused": - out.Values[i] = ec._AccessDeviceFlags_restart_when_unused(ctx, field, obj) + case "restart_when_unused": + out.Values[i] = ec._AccessDeviceFlags_restart_when_unused(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "welcoming": + out.Values[i] = ec._AccessDeviceFlags_welcoming(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferLabelToView), math.MaxInt32))) + + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Defers: deferLabelToView, + Path: graphql.GetPath(ctx), + FieldSet: deferredFieldSet, + Context: ctx, + }) + + return out +} + +var announcementImplementors = []string{"Announcement"} + +func (ec *executionContext) _Announcement(ctx context.Context, sel ast.SelectionSet, obj *models.Announcement) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, announcementImplementors) + + out := graphql.NewFieldSet(fields) + deferredFieldSet := graphql.NewFieldSet(nil) + deferLabelToView := make(map[string]*graphql.FieldSetView) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Announcement") + case "id": + out.Values[i] = ec._Announcement_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "title": + out.Values[i] = ec._Announcement_title(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "body": + out.Values[i] = ec._Announcement_body(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } - case "welcoming": - out.Values[i] = ec._AccessDeviceFlags_welcoming(ctx, field, obj) - if out.Values[i] == graphql.Null { + case "makerspace_id": + out.Values[i] = ec._Announcement_makerspace_id(ctx, field, obj) + if out.Values[i] == graphql.RequiredNull { out.Invalids++ } default: @@ -7407,10 +8943,10 @@ func (ec *executionContext) _AccessDeviceFlags(ctx context.Context, sel ast.Sele return out } -var announcementImplementors = []string{"Announcement"} +var anonymousGroupImplementors = []string{"AnonymousGroup"} -func (ec *executionContext) _Announcement(ctx context.Context, sel ast.SelectionSet, obj *models.Announcement) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, announcementImplementors) +func (ec *executionContext) _AnonymousGroup(ctx context.Context, sel ast.SelectionSet, obj *models.AnonymousGroup) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, anonymousGroupImplementors) out := graphql.NewFieldSet(fields) deferredFieldSet := graphql.NewFieldSet(nil) @@ -7418,27 +8954,88 @@ func (ec *executionContext) _Announcement(ctx context.Context, sel ast.Selection for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("Announcement") + out.Values[i] = graphql.MarshalString("AnonymousGroup") case "id": - out.Values[i] = ec._Announcement_id(ctx, field, obj) + out.Values[i] = ec._AnonymousGroup_id(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } - case "title": - out.Values[i] = ec._Announcement_title(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + case "members": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._AnonymousGroup_members(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res } - case "body": - out.Values[i] = ec._Announcement_body(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } - case "makerspace_id": - out.Values[i] = ec._Announcement_makerspace_id(ctx, field, obj) - if out.Values[i] == graphql.RequiredNull { - out.Invalids++ + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "subgroups": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._AnonymousGroup_subgroups(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -7817,16 +9414,221 @@ func (ec *executionContext) _EquipmentInstance(ctx context.Context, sel ast.Sele if out.Values[i] == graphql.Null { out.Invalids++ } - case "name": - out.Values[i] = ec._EquipmentInstance_name(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ + case "name": + out.Values[i] = ec._EquipmentInstance_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "access_channel_id": + out.Values[i] = ec._EquipmentInstance_access_channel_id(ctx, field, obj) + if out.Values[i] == graphql.RequiredNull { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferLabelToView), math.MaxInt32))) + + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Defers: deferLabelToView, + Path: graphql.GetPath(ctx), + FieldSet: deferredFieldSet, + Context: ctx, + }) + + return out +} + +var groupImplementors = []string{"Group"} + +func (ec *executionContext) _Group(ctx context.Context, sel ast.SelectionSet, obj *models.Group) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, groupImplementors) + + out := graphql.NewFieldSet(fields) + deferredFieldSet := graphql.NewFieldSet(nil) + deferLabelToView := make(map[string]*graphql.FieldSetView) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Group") + case "id": + out.Values[i] = ec._Group_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "manager_id": + out.Values[i] = ec._Group_manager_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "name": + out.Values[i] = ec._Group_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "description": + out.Values[i] = ec._Group_description(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "directMembers": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Group_directMembers(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "members": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Group_members(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "directManagedGroups": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Group_directManagedGroups(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "directSubGroups": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Group_directSubGroups(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res } - case "access_channel_id": - out.Values[i] = ec._EquipmentInstance_access_channel_id(ctx, field, obj) - if out.Values[i] == graphql.RequiredNull { - out.Invalids++ + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -8091,13 +9893,241 @@ func (ec *executionContext) _Makerspace(ctx context.Context, sel ast.SelectionSe view.AddIndices(fieldIndex) } - // don't run the out.Concurrently() call below - out.Values[i] = graphql.Null - continue - } + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "hours": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Makerspace_hours(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "managers": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Makerspace_managers(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "staff": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Makerspace_staff(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "managerAnonymousGroup": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Makerspace_managerAnonymousGroup(ctx, field, obj) + if res == graphql.RequiredNull { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "staffAnonymousGroup": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Makerspace_staffAnonymousGroup(ctx, field, obj) + if res == graphql.RequiredNull { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.IsDeferred() { + deferredFieldSet.AddField(field) + fieldIndex := len(deferredFieldSet.Values) - 1 + deferredFieldSet.Concurrently(fieldIndex, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, deferredFieldSet) + }) + + for _, deferrable := range field.Deferrables { + view, ok := deferLabelToView[deferrable.Label] + if !ok { + view = deferredFieldSet.NewView() + deferLabelToView[deferrable.Label] = view + } + view.AddIndices(fieldIndex) + } + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(min(len(deferLabelToView), math.MaxInt32))) + + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Defers: deferLabelToView, + Path: graphql.GetPath(ctx), + FieldSet: deferredFieldSet, + Context: ctx, + }) + + return out +} + +var membershipToGroupImplementors = []string{"MembershipToGroup"} + +func (ec *executionContext) _MembershipToGroup(ctx context.Context, sel ast.SelectionSet, obj *models.MembershipToGroup) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, membershipToGroupImplementors) - out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) - case "hours": + out := graphql.NewFieldSet(fields) + deferredFieldSet := graphql.NewFieldSet(nil) + deferLabelToView := make(map[string]*graphql.FieldSetView) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("MembershipToGroup") + case "UserId": + out.Values[i] = ec._MembershipToGroup_UserId(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "User": field := field innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { @@ -8106,7 +10136,7 @@ func (ec *executionContext) _Makerspace(ctx context.Context, sel ast.SelectionSe ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Makerspace_hours(ctx, field, obj) + res = ec._MembershipToGroup_User(ctx, field, obj) if res == graphql.Null { atomic.AddUint32(&fs.Invalids, 1) } @@ -8135,6 +10165,11 @@ func (ec *executionContext) _Makerspace(ctx context.Context, sel ast.SelectionSe } out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "ViewPermission": + out.Values[i] = ec._MembershipToGroup_ViewPermission(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -8190,6 +10225,20 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "setManagerSubgroups": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_setManagerSubgroups(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "setStaffSubgroups": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_setStaffSubgroups(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -8488,6 +10537,138 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "group": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_group(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "anonymousGroup": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_anonymousGroup(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "isUserInGroup": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_isUserInGroup(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "isUserInGroupDirectly": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_isUserInGroupDirectly(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "canGroupManageGroup": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_canGroupManageGroup(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "canUserManageGroup": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_canUserManageGroup(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "user": field := field @@ -9123,11 +11304,6 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.RequiredNull { out.Invalids++ } - case "card_tag": - out.Values[i] = ec._User_card_tag(ctx, field, obj) - if out.Values[i] == graphql.Null { - out.Invalids++ - } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -9618,6 +11794,20 @@ func (ec *executionContext) marshalNAccessDeviceFlags2makeᚑbackendᚋinternal return ec._AccessDeviceFlags(ctx, sel, &v) } +func (ec *executionContext) marshalNAnonymousGroup2makeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐAnonymousGroup(ctx context.Context, sel ast.SelectionSet, v models.AnonymousGroup) graphql.Marshaler { + return ec._AnonymousGroup(ctx, sel, &v) +} + +func (ec *executionContext) marshalNAnonymousGroup2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐAnonymousGroup(ctx context.Context, sel ast.SelectionSet, v *models.AnonymousGroup) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._AnonymousGroup(ctx, sel, v) +} + func (ec *executionContext) unmarshalNBoolean2bool(ctx context.Context, v any) (bool, error) { res, err := graphql.UnmarshalBoolean(v) return res, graphql.ErrorOnPath(ctx, err) @@ -9654,6 +11844,46 @@ func (ec *executionContext) marshalNDevice2makeᚑbackendᚋinternalᚋdatabase return ec._Device(ctx, sel, &v) } +func (ec *executionContext) marshalNGroup2makeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroup(ctx context.Context, sel ast.SelectionSet, v models.Group) graphql.Marshaler { + return ec._Group(ctx, sel, &v) +} + +func (ec *executionContext) marshalNGroup2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroupᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.Group) graphql.Marshaler { + ret := graphql.MarshalSliceConcurrently(ctx, len(v), 0, false, func(ctx context.Context, i int) graphql.Marshaler { + fc := graphql.GetFieldContext(ctx) + fc.Result = &v[i] + return ec.marshalNGroup2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroup(ctx, sel, v[i]) + }) + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNGroup2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroup(ctx context.Context, sel ast.SelectionSet, v *models.Group) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._Group(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNGroupViewPermission2makeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroupViewPermission(ctx context.Context, v any) (models.GroupViewPermission, error) { + var res models.GroupViewPermission + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNGroupViewPermission2makeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐGroupViewPermission(ctx context.Context, sel ast.SelectionSet, v models.GroupViewPermission) graphql.Marshaler { + return v +} + func (ec *executionContext) unmarshalNID2int(ctx context.Context, v any) (int, error) { res, err := graphql.UnmarshalInt(v) return res, graphql.ErrorOnPath(ctx, err) @@ -9686,6 +11916,36 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti return res } +func (ec *executionContext) unmarshalNInt2ᚕintᚄ(ctx context.Context, v any) ([]int, error) { + var vSlice []any + vSlice = graphql.CoerceList(v) + var err error + res := make([]int, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNInt2int(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalNInt2ᚕintᚄ(ctx context.Context, sel ast.SelectionSet, v []int) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNInt2int(ctx, sel, v[i]) + } + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) marshalNMakerspaceHours2makeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐMakerspaceHours(ctx context.Context, sel ast.SelectionSet, v models.MakerspaceHours) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -9712,6 +11972,32 @@ func (ec *executionContext) marshalNMakerspaceHours2ᚕmakeᚑbackendᚋinternal return ret } +func (ec *executionContext) marshalNMembershipToGroup2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐMembershipToGroupᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.MembershipToGroup) graphql.Marshaler { + ret := graphql.MarshalSliceConcurrently(ctx, len(v), 0, false, func(ctx context.Context, i int) graphql.Marshaler { + fc := graphql.GetFieldContext(ctx) + fc.Result = &v[i] + return ec.marshalNMembershipToGroup2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐMembershipToGroup(ctx, sel, v[i]) + }) + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNMembershipToGroup2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐMembershipToGroup(ctx context.Context, sel ast.SelectionSet, v *models.MembershipToGroup) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._MembershipToGroup(ctx, sel, v) +} + func (ec *executionContext) marshalNOptionBlockOption2makeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐOptionBlockOption(ctx context.Context, sel ast.SelectionSet, v models.OptionBlockOption) graphql.Marshaler { return ec._OptionBlockOption(ctx, sel, &v) } @@ -9794,6 +12080,22 @@ func (ec *executionContext) marshalNUser2makeᚑbackendᚋinternalᚋdatabaseᚋ return ec._User(ctx, sel, &v) } +func (ec *executionContext) marshalNUser2ᚕᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐUserᚄ(ctx context.Context, sel ast.SelectionSet, v []*models.User) graphql.Marshaler { + ret := graphql.MarshalSliceConcurrently(ctx, len(v), 0, false, func(ctx context.Context, i int) graphql.Marshaler { + fc := graphql.GetFieldContext(ctx) + fc.Result = &v[i] + return ec.marshalNUser2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐUser(ctx, sel, v[i]) + }) + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) marshalNUser2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐUser(ctx context.Context, sel ast.SelectionSet, v *models.User) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -10008,6 +12310,13 @@ func (ec *executionContext) marshalOAccessDevice2ᚖmakeᚑbackendᚋinternalᚋ return ec._AccessDevice(ctx, sel, v) } +func (ec *executionContext) marshalOAnonymousGroup2ᚖmakeᚑbackendᚋinternalᚋdatabaseᚋmodelsᚐAnonymousGroup(ctx context.Context, sel ast.SelectionSet, v *models.AnonymousGroup) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._AnonymousGroup(ctx, sel, v) +} + func (ec *executionContext) unmarshalOBoolean2bool(ctx context.Context, v any) (bool, error) { res, err := graphql.UnmarshalBoolean(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/internal/gql/resolvers/group.resolvers.go b/internal/gql/resolvers/group.resolvers.go new file mode 100644 index 00000000..e4e3d8ce --- /dev/null +++ b/internal/gql/resolvers/group.resolvers.go @@ -0,0 +1,210 @@ +package resolvers + +// This file will be automatically regenerated based on the schema, any resolver +// implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.93 + +import ( + "context" + "fmt" + "make-backend/internal/auth" + "make-backend/internal/database/models" + "make-backend/internal/gql" +) + +// Members is the resolver for the members field. +func (r *anonymousGroupResolver) Members(ctx context.Context, obj *models.AnonymousGroup) ([]*models.User, error) { + members, err := r.Store.Groups.UsersInAnonymousGroup(ctx, obj.Id) + if err != nil { + + return nil, err + } + return SliceToPtrSlice(members), nil +} + +// Subgroups is the resolver for the subgroups field. +func (r *anonymousGroupResolver) Subgroups(ctx context.Context, obj *models.AnonymousGroup) ([]*models.Group, error) { + subgroups, err := r.Store.Groups.GroupsInAnonymousGroup(ctx, obj.Id) + if err != nil { + return nil, err + } + return SliceToPtrSlice(subgroups), nil +} + +// DirectMembers is the resolver for the directMembers field. +func (r *groupResolver) DirectMembers(ctx context.Context, obj *models.Group) ([]*models.MembershipToGroup, error) { + panic(fmt.Errorf("not implemented: DirectMembers - directMembers")) +} + +// Members is the resolver for the members field. +func (r *groupResolver) Members(ctx context.Context, obj *models.Group) ([]*models.MembershipToGroup, error) { + how, err := CanUserFromContextSeeGroup(r.Store, ctx, obj.Id) + if err != nil { + return nil, err + } + + userId := auth.UserIDFromContext(ctx) + if how == models.GroupViewPermission_SeeSelf { + m := models.MembershipToGroup{ + UserId: *userId, + ViewPermission: models.GroupViewPermission_SeeSelf, + } + ms := []*models.MembershipToGroup{ + &m, + } + return ms, nil + } + + // Permission SeeAll or manager level + + members, err := r.Store.Groups.GetGroupMembers(ctx, obj.Id) + if err != nil { + return nil, err + } + return SliceToPtrSlice(members), nil +} + +// DirectManagedGroups is the resolver for the directManagedGroups field. +func (r *groupResolver) DirectManagedGroups(ctx context.Context, obj *models.Group) ([]*models.Group, error) { + groupIds, err := r.Store.Groups.AllGroupsGroupCanManageDirectly(ctx, obj.Id) + if err != nil { + return nil, err + } + + groups := make([]*models.Group, len(groupIds)) + for i, id := range groupIds { + g, err := r.Store.Groups.GetGroupById(ctx, id) + if err != nil { + return nil, err + } + groups[i] = g + + } + return groups, nil +} + +// DirectSubGroups is the resolver for the directSubGroups field. +func (r *groupResolver) DirectSubGroups(ctx context.Context, obj *models.Group) ([]*models.Group, error) { + panic(fmt.Errorf("not implemented: DirectSubGroups - directSubGroups")) +} + +// User is the resolver for the User field. +func (r *membershipToGroupResolver) User(ctx context.Context, obj *models.MembershipToGroup) (*models.User, error) { + // TODO figure out what permissions for this are and check them + return r.Store.Users.GetUserById(ctx, obj.UserId) +} + +// Group is the resolver for the group field. +func (r *queryResolver) Group(ctx context.Context, id int) (*models.Group, error) { + how, err := CanUserFromContextSeeGroup(r.Store, ctx, id) + if err != nil { + return nil, err + } + if how == models.GroupViewPermission_SeeNone { + return nil, models.ErrNoGroupOrWrongPermissions + } + + group, err := r.Store.Groups.GetGroupById(ctx, id) + if err != nil { + return nil, err + } + return group, nil +} + +// AnonymousGroup is the resolver for the anonymousGroup field. +func (r *queryResolver) AnonymousGroup(ctx context.Context, id int) (*models.AnonymousGroup, error) { + ag := models.AnonymousGroup{ + Id: id, + } + return &ag, nil +} + +// IsUserInGroup is the resolver for the isUserInGroup field. +func (r *queryResolver) IsUserInGroup(ctx context.Context, userID int, groupID int) (bool, error) { + _, err := CanUserFromContextSeeGroup(r.Store, ctx, groupID) + if err != nil { + return false, err + } + + in, _, err := r.Store.Groups.IsUserInGroup(ctx, userID, groupID) + return in, err +} + +// IsUserInGroupDirectly is the resolver for the isUserInGroupDirectly field. +func (r *queryResolver) IsUserInGroupDirectly(ctx context.Context, userID int, groupID int) (bool, error) { + askingUserID := auth.UserIDFromContext(ctx) + if askingUserID == nil { + return false, auth.ErrNotAuthenticated + } + + visibleToAsker, err := r.Store.Groups.IsGroupVisibleToUser(ctx, *askingUserID, groupID) + if err != nil { + return false, err + } + if !visibleToAsker { + return false, models.ErrNoGroupOrWrongPermissions + } + + in, _, err := r.Store.Groups.IsUserInGroupDirectly(ctx, userID, groupID) + return in, err +} + +// CanGroupManageGroup is the resolver for the canGroupManageGroup field. +func (r *queryResolver) CanGroupManageGroup(ctx context.Context, managerID int, groupID int) (bool, error) { + askingUserID := auth.UserIDFromContext(ctx) + if askingUserID == nil { + return false, auth.ErrNotAuthenticated + } + + managerVisibleToAsker, err := r.Store.Groups.IsGroupVisibleToUser(ctx, *askingUserID, managerID) + if err != nil { + return false, err + } + if !managerVisibleToAsker { + return false, fmt.Errorf("manager group does not exist or user has invalid permissions to query it") + } + managedVisibleToAsker, err := r.Store.Groups.IsGroupVisibleToUser(ctx, *askingUserID, groupID) + if err != nil { + return false, err + } + if !managedVisibleToAsker { + return false, fmt.Errorf("managed group does not exist or user has invalid permissions to query it") + } + + managedVisibleToManager, err := r.Store.Groups.IsGroupVisibleToGroup(ctx, managerID, groupID) + if err != nil { + return false, err + } + if !managedVisibleToManager { + return false, fmt.Errorf("managed group is not visible to manager group") + } + return r.Store.Groups.CanGroupManageGroup(ctx, managerID, groupID) +} + +// CanUserManageGroup is the resolver for the canUserManageGroup field. +func (r *queryResolver) CanUserManageGroup(ctx context.Context, managerID int, groupID int) (bool, error) { + _, err := CanUserFromContextSeeGroup(r.Store, ctx, groupID) + if err != nil { + return false, err + } + + return r.Store.Groups.CanUserManageGroup(ctx, managerID, groupID) +} + +// AnonymousGroup returns gql.AnonymousGroupResolver implementation. +func (r *Resolver) AnonymousGroup() gql.AnonymousGroupResolver { return &anonymousGroupResolver{r} } + +// Group returns gql.GroupResolver implementation. +func (r *Resolver) Group() gql.GroupResolver { return &groupResolver{r} } + +// MembershipToGroup returns gql.MembershipToGroupResolver implementation. +func (r *Resolver) MembershipToGroup() gql.MembershipToGroupResolver { + return &membershipToGroupResolver{r} +} + +type ( + anonymousGroupResolver struct{ *Resolver } + groupResolver struct{ *Resolver } + membershipToGroupResolver struct{ *Resolver } +) diff --git a/internal/gql/resolvers/group_resolver_test.go b/internal/gql/resolvers/group_resolver_test.go new file mode 100644 index 00000000..a13da07a --- /dev/null +++ b/internal/gql/resolvers/group_resolver_test.go @@ -0,0 +1,425 @@ +package resolvers + +import ( + "context" + "log/slog" + "make-backend/internal/auth" + "make-backend/internal/database" + "slices" + "testing" +) + +func helpersForResolverTest(t *testing.T) (*database.Store, *database.TestMockData, *Resolver) { + if testing.Short() { + t.Skip("Skipping db/resolver test in short mode.") + } + _, store, mock := database.VerifyTestDb(slog.Default()) + return store, mock, &Resolver{Store: store} +} + +func ContextWithUser(parent context.Context, userId int) context.Context { + return context.WithValue(parent, auth.UserContextKey{}, userId) +} + +// If you query a group for a member and you don't have visibility to that group, you should get a group not found error +func TestUserGroupVisibilityByMemberQueryGood(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + + whenUserIsBrian := ContextWithUser(t.Context(), data.Users[0].Id) + userId := 400 + + _, brianErr := resolver.Query().IsUserInGroup(whenUserIsBrian, userId, data.OtherMusicians.Id) + if brianErr != nil { + t.Errorf("failed to check membership when brian should have visibility: %v", brianErr) + } +} +func TestUserGroupVisibilityByMemberQueryBad(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + + whenUserIsJohn := ContextWithUser(t.Context(), data.Users[1].Id) + userId := 400 + + _, JohnErr := resolver.Query().IsUserInGroup(whenUserIsJohn, userId, data.OtherMusicians.Id) + if JohnErr == nil { + t.Errorf("john checking membership should return a group not found error since he has no visibility to the other musicians but got null") + } +} + +func TestUserManagingGroup(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + + trials := []struct { + askerUser int + user int + group int + + shouldError bool + shouldManage bool + }{ + {data.Users[0].Id, data.Users[0].Id, data.BeatlesMusicians.Id, false, true}, + {data.Users[1].Id, data.Users[1].Id, data.BeatlesMusicians.Id, false, false}, + {data.Users[1].Id, data.Users[1].Id, data.OtherMusicians.Id, true, false}, + } + + for _, trial := range trials { + ctx := ContextWithUser(t.Context(), trial.askerUser) + + can, err := resolver.Query().CanUserManageGroup(ctx, trial.user, trial.group) + if err != nil && !trial.shouldError { + t.Fatalf("got error when user %v checks if user %v can manage group %v: %v", trial.askerUser, trial.user, trial.group, err) + } else if err == nil && trial.shouldError { + t.Fatalf("should get error when user %v checks if user %v can manage group %v we can't see", trial.askerUser, trial.user, trial.group) + continue + } + if trial.shouldManage && can != trial.shouldManage { + t.Fatalf("user %v should be able to manage %v but wasn't able to for some reason", trial.user, trial.group) + } else if !trial.shouldManage && can != trial.shouldManage { + t.Fatalf("user %v should NOT be able to manage %v but wasn't able to for some reason", trial.user, trial.group) + } + } +} + +func TestGroupManagingGroup(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + + trials := []struct { + desc string + askerUser int + manager int + group int + + shouldError bool + shouldManage bool + }{ + {"managers managing beatles", data.Users[0].Id, data.BeatlesManagers.Id, data.BeatlesMusicians.Id, false, true}, + {"subgroup managing supergroup owner", data.Users[0].Id, data.BeatlesMusicians.Id, data.Brits.Id, false, false}, + {"beatles managing managers", data.Users[1].Id, data.BeatlesMusicians.Id, data.BeatlesManagers.Id, true, false}, + {"subgroup managing supergroup member", data.Users[1].Id, data.BeatlesMusicians.Id, data.Brits.Id, false, false}, + {"sibling group managing each other", data.Users[0].Id, data.BeatlesMusicians.Id, data.OtherMusicians.Id, true, false}, + {"manager group not visible to user", data.Users[1].Id, data.BeatlesManagers.Id, data.OtherMusicians.Id, true, false}, + {"managed group not visible to user", data.Users[1].Id, data.BeatlesMusicians.Id, data.OtherMusicians.Id, true, false}, + // TODO explicitly shared groups + } + + for _, trial := range trials { + t.Run(trial.desc, func(t *testing.T) { + + ctx := ContextWithUser(t.Context(), trial.askerUser) + + can, err := resolver.Query().CanGroupManageGroup(ctx, trial.manager, trial.group) + if err != nil && !trial.shouldError { + t.Fatalf("got error when %v checks if %v can manage group %v: %v", trial.askerUser, trial.manager, trial.group, err) + } else if err == nil && trial.shouldError { + t.Fatalf("should get error when %v checks if %v can manage group %v we can't see", trial.askerUser, trial.manager, trial.group) + return + } + if trial.shouldManage && can != trial.shouldManage { + t.Fatalf("%v should be able to manage %v but wasn't able to for some reason", trial.manager, trial.group) + } else if !trial.shouldManage && can != trial.shouldManage { + t.Fatalf("%v should NOT be able to manage %v but wasn't able to for some reason", trial.manager, trial.group) + } + }) + } +} + +func TestGroupMembership(t *testing.T) { + + _, data, resolver := helpersForResolverTest(t) + ctx := ContextWithUser(t.Context(), data.Users[0].Id) + + testCases := []struct { + desc string + user int + group int + shouldBeMember bool + shouldBeDirect bool + }{ + {"brian as direct member of managers", data.Users[0].Id, data.BeatlesManagers.Id, true, true}, + {"brian as NOT member of beatles musicians", data.Users[0].Id, data.BeatlesMusicians.Id, false, false}, + {"john as direct member of beatles musicians", data.Users[1].Id, data.BeatlesMusicians.Id, true, true}, + {"john as indirect member of brits", data.Users[1].Id, data.Brits.Id, true, false}, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + inGroup, err1 := resolver.Query().IsUserInGroup(ctx, tC.user, tC.group) + inGroupDirectly, err2 := resolver.Query().IsUserInGroupDirectly(ctx, tC.user, tC.group) + if err1 != nil { + t.Fatalf("failed to check if user %v in group %v: %v", tC.user, tC.group, err1) + } + if err2 != nil { + t.Fatalf("failed to check if user %v directly in group %v: %v", tC.user, tC.group, err2) + } + if inGroup != tC.shouldBeMember { + t.Fatalf("user %v should have membership:%v to group %v but was %v", tC.user, tC.shouldBeMember, tC.group, inGroup) + } + if inGroupDirectly != tC.shouldBeDirect { + t.Fatalf("user %v should have direct membership:%v to group %v but was %v", tC.user, tC.shouldBeDirect, tC.group, inGroupDirectly) + } + }) + } +} + +func TestFindingGroupsById(t *testing.T) { + + _, data, resolver := helpersForResolverTest(t) + testCases := []struct { + desc string + askingUser int + group int + exists bool + shouldError bool + }{ + { + desc: "brian getting beatles manager group", + askingUser: data.Users[0].Id, + group: data.BeatlesManagers.Id, + exists: true, + shouldError: false, + }, + { + desc: "brian getting beatles musician group", + askingUser: data.Users[0].Id, + group: data.BeatlesMusicians.Id, + exists: true, + shouldError: false, + }, + { + desc: "john getting beatles musician group", + askingUser: data.Users[1].Id, + group: data.BeatlesMusicians.Id, + exists: true, + shouldError: false, + }, + + { + desc: "john getting beatles manager group", + askingUser: data.Users[1].Id, + group: data.BeatlesManagers.Id, + exists: true, + shouldError: true, + }, { + desc: "john getting nonexistent group", + askingUser: data.Users[1].Id, + group: -1, + exists: false, + shouldError: true, + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + ctx := ContextWithUser(t.Context(), tC.askingUser) + g, err := resolver.Query().Group(ctx, tC.group) + if !tC.shouldError && err != nil { + t.Fatalf("should not error when user %v gets group %v but got %v", tC.askingUser, tC.group, err) + return + } + if !tC.exists && (err == nil || g != nil) { + t.Fatalf("group %v didn't exist but was somehow successful in fetching. Got %+v", tC.group, g) + return + } + + if tC.shouldError && err == nil { + t.Fatalf("should error when user %v gets group %v but didn't", tC.askingUser, tC.group) + return + } + + if !tC.shouldError { + if tC.exists && g == nil { + t.Fatalf("group %v should exist but could not find it: err = %v", tC.group, err) + return + } + if tC.group != g.Id { + t.Fatalf("asked for group %v but got non-matching %+v", tC.group, g) + return + } + } + }) + } +} + +func areGroupsEqual(wantedIds, gotIds []int) bool { + slices.Sort(wantedIds) + slices.Sort(gotIds) + return slices.Equal(wantedIds, gotIds) +} + +func TestMembersOfGroupAsManager(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + askerId := data.Users[0].Id + group := data.BeatlesMusicians + + ctx := ContextWithUser(t.Context(), askerId) + members, err := resolver.Group().Members(ctx, &group) + if err != nil { + t.Fatalf("failed to get members of group: %v", err) + } + memberIds := []int{} + for _, m := range members { + memberIds = append(memberIds, m.UserId) + } + wantedIds := []int{} + + for _, user := range data.Users[1:5] { + wantedIds = append(wantedIds, user.Id) + } + + if !areGroupsEqual(wantedIds, memberIds) { + t.Fatalf("groups were not equal. Wanted %v. got %v", wantedIds, memberIds) + } + +} + +func TestMembersOfGroupAsSeeSelf(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + group := data.BeatlesManagers + askerId := data.Users[0].Id + + ctx := ContextWithUser(t.Context(), askerId) + members, err := resolver.Group().Members(ctx, &group) + if err != nil { + t.Fatalf("failed to get members of group: %v", err) + } + memberIds := []int{} + for _, m := range members { + memberIds = append(memberIds, m.UserId) + } + wantedIds := []int{askerId} + + if !areGroupsEqual(wantedIds, memberIds) { + t.Fatalf("groups were not equal. Wanted %v. got %v", wantedIds, memberIds) + } +} + +func TestMembersOfGroupAsSeeAll(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + group := data.BeatlesMusicians + askerId := data.Users[1].Id + + ctx := ContextWithUser(t.Context(), askerId) + members, err := resolver.Group().Members(ctx, &group) + if err != nil { + t.Fatalf("failed to get members of group: %v", err) + } + memberIds := []int{} + for _, m := range members { + memberIds = append(memberIds, m.UserId) + } + wantedIds := []int{} + for _, user := range data.Users[1:5] { + wantedIds = append(wantedIds, user.Id) + } + + if !areGroupsEqual(wantedIds, memberIds) { + t.Fatalf("groups were not equal. Wanted %v. got %v", wantedIds, memberIds) + } +} + +func TestMembersOfGroupAsSeeNone(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + group := data.BeatlesMusicians + askerId := data.Users[5].Id + + ctx := ContextWithUser(t.Context(), askerId) + members, err := resolver.Group().Members(ctx, &group) + if err == nil { + t.Fatalf("see none member should not be able to see group at all: saw %v", members) + } +} +func TestMembersOfGroupAsNotSignedIn(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + group := data.BeatlesMusicians + + members, err := resolver.Group().Members(t.Context(), &group) + if err == nil { + t.Fatalf("non signed in user should not be able to see group at all: saw %v", members) + } +} + +func TestDirectManagedGroups(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + + // Brian (Users[0]) is the asker, who belongs to BeatlesManagers. + // BeatlesManagers (which is managed by admin/root group) directly manages: + // - BeatlesMusicians + // - OtherMusicians + // - ExMusicians + ctx := ContextWithUser(t.Context(), data.Users[0].Id) + + managedGroups, err := resolver.Group().DirectManagedGroups(ctx, &data.BeatlesManagers) + if err != nil { + t.Fatalf("failed to get direct managed groups: %v", err) + } + + expectedIds := []int{ + data.BeatlesMusicians.Id, + data.OtherMusicians.Id, + data.ExMusicians.Id, + } + + gotIds := []int{} + for _, g := range managedGroups { + gotIds = append(gotIds, g.Id) + } + + if !areGroupsEqual(expectedIds, gotIds) { + t.Fatalf("direct managed groups were not equal. Expected %v, got %v", expectedIds, gotIds) + } +} + +func TestAuthenticationRequired(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + ctx := t.Context() // context with no user + + tests := []struct { + name string + run func() error + }{ + { + "Group.Members", func() error { + _, err := resolver.Group().Members(ctx, &data.BeatlesMusicians) + return err + }, + }, + { + "Query.Group", func() error { + _, err := resolver.Query().Group(ctx, data.BeatlesMusicians.Id) + return err + }, + }, + { + name: "Query.IsUserInGroup", run: func() error { + _, err := resolver.Query().IsUserInGroup(ctx, data.Users[0].Id, data.BeatlesMusicians.Id) + return err + }, + }, + { + "Query.IsUserInGroupDirectly", func() error { + _, err := resolver.Query().IsUserInGroupDirectly(ctx, data.Users[0].Id, data.BeatlesMusicians.Id) + return err + }, + }, + { + "Query.CanGroupManageGroup", func() error { + _, err := resolver.Query().CanGroupManageGroup(ctx, data.BeatlesManagers.Id, data.BeatlesMusicians.Id) + return err + }, + }, + { + "Query.CanUserManageGroup", func() error { + _, err := resolver.Query().CanUserManageGroup(ctx, data.Users[0].Id, data.BeatlesMusicians.Id) + return err + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.run() + if err == nil { + t.Error("expected error for unauthenticated user, but got nil") + } else if err != auth.ErrNotAuthenticated { + t.Errorf("expected non authenticated error %v, but got %v", auth.ErrNotAuthenticated, err) + } + }) + } +} diff --git a/internal/gql/resolvers/helpers.go b/internal/gql/resolvers/helpers.go new file mode 100644 index 00000000..96f400d5 --- /dev/null +++ b/internal/gql/resolvers/helpers.go @@ -0,0 +1,86 @@ +package resolvers + +import ( + "context" + "fmt" + "make-backend/internal/auth" + "make-backend/internal/database" + "make-backend/internal/database/models" +) + +func SliceToPtrSlice[E any](s []E) []*E { + pointers := make([]*E, 0, len(s)) + for i := range s { + pointers = append(pointers, &s[i]) + } + return pointers +} + +func FullUserFromContext(store *database.Store, ctx context.Context) *models.User { + id := auth.UserIDFromContext(ctx) + if id == nil { + return nil + } + u, err := store.Users.GetUserById(ctx, *id) + if err != nil { + return nil + } + return u +} + +// helper to check if the use stored in context can manage anonymous group +// returns err if not, nil if can +func CanUserFromContextManageAnonymousGroup(store *database.Store, ctx context.Context, agroupID int) error { + userId := auth.UserIDFromContext(ctx) + if userId == nil { + return auth.ErrNotAuthenticated + } + + canManage, err := store.Groups.CanUserManageAnonymousGroup(ctx, *userId, agroupID) + if err != nil { + return err + } + if !canManage { + return models.ErrNoAnonymousGroupOrWrongPermissions + } + return nil +} + +func CanUserFromContextManageGroup(store *database.Store, ctx context.Context, groupID int) (bool, error) { + userId := auth.UserIDFromContext(ctx) + if userId == nil { + return false, auth.ErrNotAuthenticated + } + return store.Groups.CanUserManageGroup(ctx, *userId, groupID) +} + +func CanUserFromContextSeeGroup(store *database.Store, ctx context.Context, groupID int) (models.GroupViewPermission, error) { + userId := auth.UserIDFromContext(ctx) + if userId == nil { + return models.GroupViewPermission_SeeNone, auth.ErrNotAuthenticated + } + + visibleByManager, err := store.Groups.CanUserManageGroup(ctx, *userId, groupID) + if err != nil { + return models.GroupViewPermission_SeeNone, fmt.Errorf("error checking how user can view group: %w", err) + } + if visibleByManager { + return models.GroupViewPermission_SeeAll, nil + } + + visibleByMember, how, err := store.Groups.IsUserInGroup(ctx, *userId, groupID) + if err != nil { + return models.GroupViewPermission_SeeNone, fmt.Errorf("error checking how user can view group: %w", err) + } + + if !visibleByMember { + return models.GroupViewPermission_SeeNone, models.ErrNoGroupOrWrongPermissions + } + + if how == models.GroupViewPermission_SeeNone { + return models.GroupViewPermission_SeeNone, models.ErrNoGroupOrWrongPermissions + } + + return how, nil + +} diff --git a/internal/gql/resolvers/makerspace.resolvers.go b/internal/gql/resolvers/makerspace.resolvers.go index dba21aaf..11f3bace 100644 --- a/internal/gql/resolvers/makerspace.resolvers.go +++ b/internal/gql/resolvers/makerspace.resolvers.go @@ -7,6 +7,8 @@ package resolvers import ( "context" + "fmt" + "make-backend/internal/auth" "make-backend/internal/database/models" "make-backend/internal/gql" ) @@ -36,12 +38,50 @@ func (r *makerspaceResolver) Hours(ctx context.Context, obj *models.Makerspace) return hours, nil } +// Managers is the resolver for the managers field. +func (r *makerspaceResolver) Managers(ctx context.Context, obj *models.Makerspace) ([]*models.User, error) { + us, err := r.Store.Groups.UsersInAnonymousGroup(ctx, obj.ManagementAgroupId) + if err != nil { + return nil, err + } + + return SliceToPtrSlice(us), nil +} + +// Staff is the resolver for the staff field. +func (r *makerspaceResolver) Staff(ctx context.Context, obj *models.Makerspace) ([]*models.User, error) { + us, err := r.Store.Groups.UsersInAnonymousGroup(ctx, obj.StaffAgroupId) + if err != nil { + return nil, err + } + return SliceToPtrSlice(us), nil +} + +// ManagerAnonymousGroup is the resolver for the managerAnonymousGroup field. +func (r *makerspaceResolver) ManagerAnonymousGroup(ctx context.Context, obj *models.Makerspace) (*models.AnonymousGroup, error) { + panic(fmt.Errorf("not implemented: ManagerAnonymousGroup - managerAnonymousGroup")) +} + +// StaffAnonymousGroup is the resolver for the staffAnonymousGroup field. +func (r *makerspaceResolver) StaffAnonymousGroup(ctx context.Context, obj *models.Makerspace) (*models.AnonymousGroup, error) { + panic(fmt.Errorf("not implemented: StaffAnonymousGroup - staffAnonymousGroup")) +} + // CreateMakerspace is the resolver for the createMakerspace field. func (r *mutationResolver) CreateMakerspace(ctx context.Context, name string, hidden bool) (int, error) { + user := FullUserFromContext(r.Store, ctx) + if user != nil { + return 0, auth.ErrNotAuthenticated + } id, err := r.Store.Makerspaces.CreateMakerspace(ctx, name, hidden) if err != nil { return 0, err } + makerspace, err := r.Store.Makerspaces.GetMakerspaceById(ctx, id) + if err != nil { + return id, fmt.Errorf("failed to retrieve new makerspace") + } + r.Logger.AuditLog.CreateUnassociated("builtin.makerspace.create", "{user} created {makerspace)", user.LogEntity(), makerspace.LogEntity()) return id, nil } @@ -56,6 +96,34 @@ func (r *mutationResolver) DeleteMakerspace(ctx context.Context, id int) (bool, return success, err } +// SetManagerSubgroups is the resolver for the setManagerSubgroups field. +func (r *mutationResolver) SetManagerSubgroups(ctx context.Context, makerspaceID int, subgroups []int) (bool, error) { + makerspace, err := r.Store.Makerspaces.GetMakerspaceById(ctx, makerspaceID) + if err != nil { + return false, err + } + + err = r.Store.Groups.SetGroupsForAnonymousGroup(ctx, makerspace.ManagementAgroupId, subgroups) + if err != nil { + return false, err + } + return true, nil +} + +// SetStaffSubgroups is the resolver for the setStaffSubgroups field. +func (r *mutationResolver) SetStaffSubgroups(ctx context.Context, makerspaceID int, subgroups []int) (bool, error) { + makerspace, err := r.Store.Makerspaces.GetMakerspaceById(ctx, makerspaceID) + if err != nil { + return false, err + } + + err = r.Store.Groups.SetGroupsForAnonymousGroup(ctx, makerspace.StaffAgroupId, subgroups) + if err != nil { + return false, err + } + return true, nil +} + // Makerspace is the resolver for the makerspace field. func (r *queryResolver) Makerspace(ctx context.Context, id int) (*models.Makerspace, error) { makerspace, err := r.Store.Makerspaces.GetMakerspaceById(ctx, id) diff --git a/internal/gql/resolvers/makerspace_resolver_test.go b/internal/gql/resolvers/makerspace_resolver_test.go new file mode 100644 index 00000000..f32e6498 --- /dev/null +++ b/internal/gql/resolvers/makerspace_resolver_test.go @@ -0,0 +1,132 @@ +package resolvers + +import ( + "testing" +) + +func TestMakerspaceStaffAuthed(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + ctx := ContextWithUser(t.Context(), data.Users[0].Id) + wantedStaff := []int{data.Users[1].Id, data.Users[2].Id, data.Users[3].Id, data.Users[4].Id} + gotStaff, err := resolver.Makerspace().Staff(ctx, &data.Parlophone) + if err != nil { + t.Fatalf("failed to get staff of makerspace %v", err) + } + sIds := []int{} + for _, user := range gotStaff { + sIds = append(sIds, user.Id) + } + if !areGroupsEqual(wantedStaff, sIds) { + t.Fatalf("staff group for makerspace not equal. Wanted %v but got %v", wantedStaff, sIds) + } +} +func TestMakerspaceManagersAuthed(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + wantedManagers := []int{data.Users[0].Id} + gotManagers, err := resolver.Makerspace().Managers(t.Context(), &data.Parlophone) + if err != nil { + t.Fatalf("failed to get managers of makerspace %v", err) + } + mIds := []int{} + for _, user := range gotManagers { + mIds = append(mIds, user.Id) + } + if !areGroupsEqual(wantedManagers, mIds) { + t.Fatalf("manager group for makerspace not equal. Wanted %v but got %v", wantedManagers, mIds) + } +} + +func TestMakerspaceStaffSubgroups(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + ctx := ContextWithUser(t.Context(), data.Users[0].Id) + + wantedSG := []int{data.BeatlesMusicians.Id} + gotSGs, err := resolver.Makerspace().StaffSubgroups(ctx, &data.Parlophone) + if err != nil { + t.Fatalf("failed to get staff subgroups of makerspace %v", err) + } + SGIds := []int{} + for _, user := range gotSGs { + SGIds = append(SGIds, user.Id) + } + if !areGroupsEqual(wantedSG, SGIds) { + t.Fatalf("staff subgroups for makerspace not equal. Wanted %v but got %v", wantedSG, SGIds) + } +} + +func TestMakerspaceManagerSubgroupsAuthed(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + ctx := ContextWithUser(t.Context(), data.Users[0].Id) + + wantedSG := []int{data.BeatlesManagers.Id} + gotSGs, err := resolver.Makerspace().ManagerSubgroups(ctx, &data.Parlophone) + if err != nil { + t.Fatalf("failed to get manager subgroups of makerspace: %v", err) + } + SGIds := []int{} + for _, user := range gotSGs { + SGIds = append(SGIds, user.Id) + } + if !areGroupsEqual(wantedSG, SGIds) { + t.Fatalf("manager subgroups for makerspace not equal. Wanted %v but got %v", wantedSG, SGIds) + } +} + +func TestAddToStaffAgroupWithoutAuth(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + groupAlreadyThere := data.BeatlesMusicians.Id + groupToAdd := data.ExMusicians.Id + wantedSG := []int{groupAlreadyThere, groupToAdd} + + good, err := resolver.Mutation().SetAnonymousGroupSubgroups(t.Context(), data.Parlophone.StaffAgroupId, wantedSG) + if good || err == nil { + t.Fatalf("succeeded in adding subgroups to anonymous group when unauthenticated") + } + +} + +func TestAddAndRemoveFromStaffAgroup(t *testing.T) { + _, data, resolver := helpersForResolverTest(t) + groupAlreadyThere := data.BeatlesMusicians.Id + groupToAdd := data.ExMusicians.Id + midWantedSG := []int{groupAlreadyThere, groupToAdd} + endWantedSG := []int{groupAlreadyThere} + + ctx := ContextWithUser(t.Context(), data.Users[0].Id) + + good, err := resolver.Mutation().SetAnonymousGroupSubgroups(ctx, data.Parlophone.StaffAgroupId, midWantedSG) + if !good || err != nil { + t.Fatalf("failed to set agroup subgroups to %v: good: %v, err: %v", midWantedSG, good, err) + } + + // add new group + gotSGsMid, err := resolver.Makerspace().StaffSubgroups(ctx, &data.Parlophone) + if err != nil { + t.Fatalf("failed to get staff subgroups of makerspace after addition: %v", err) + } + midSGIds := []int{} + for _, user := range gotSGsMid { + midSGIds = append(midSGIds, user.Id) + } + if !areGroupsEqual(midWantedSG, midSGIds) { + t.Fatalf("failed to add subgroup to agroup. Wanted %v but got %v", midWantedSG, midSGIds) + } + + // remove new group + good, err = resolver.Mutation().SetAnonymousGroupSubgroups(ctx, data.Parlophone.StaffAgroupId, endWantedSG) + if !good || err != nil { + t.Fatalf("failed to set agroup subgroups to %v: good: %v, err: %v", endWantedSG, good, err) + } + + gotSGsEnd, err := resolver.Makerspace().StaffSubgroups(ctx, &data.Parlophone) + if err != nil { + t.Fatalf("failed to get staff subgroups of makerspace after removal: %v", err) + } + endSGIds := []int{} + for _, user := range gotSGsEnd { + endSGIds = append(endSGIds, user.Id) + } + if !areGroupsEqual(endWantedSG, endSGIds) { + t.Fatalf("failed to remove subgroup from agroup. Wanted %v but got %v", endWantedSG, endSGIds) + } +} diff --git a/internal/gql/schema/group.graphqls b/internal/gql/schema/group.graphqls new file mode 100644 index 00000000..930a0761 --- /dev/null +++ b/internal/gql/schema/group.graphqls @@ -0,0 +1,49 @@ + + + + + +enum GroupViewPermission { + SEE_NONE + SEE_SELF + SEE_ALL +} + + +type MembershipToGroup { + UserId: ID! + User: User! + ViewPermission: GroupViewPermission! +} + +type Group { + id: ID! + manager_id: ID! + name: String! + description: String! + + directMembers: [MembershipToGroup!]! + members: [MembershipToGroup!]! + + directManagedGroups: [Group!]! + directSubGroups: [Group!]! +} + +type AnonymousGroup{ + id: ID! + members: [User!]! + subgroups: [Group!]! +} + +extend type Query { + + group(id: ID!): Group! + anonymousGroup(id: ID!): AnonymousGroup! + + isUserInGroup(userID: ID!, groupID: ID!): Boolean! + + isUserInGroupDirectly(userID: ID!, groupID: ID!): Boolean! + canGroupManageGroup(managerID: ID!, groupID: ID!): Boolean! + canUserManageGroup(managerID: ID!, groupID: ID!): Boolean! +} + diff --git a/internal/gql/schema/makerspace.graphqls b/internal/gql/schema/makerspace.graphqls index e7d37c2f..402ed70b 100644 --- a/internal/gql/schema/makerspace.graphqls +++ b/internal/gql/schema/makerspace.graphqls @@ -1,3 +1,5 @@ +directive @isManagerFor(makerspaceIdField: String, makerspaceIdArg: String) on FIELD_DEFINITION +directive @isStaffFor(makerspaceIdField: String, makerspaceIdArg: String) on FIELD_DEFINITION union MakerspaceHours = DefaultHours | SpecialHours @@ -12,6 +14,12 @@ type Makerspace { timezone: String! zones: [Zone!]! hours: [MakerspaceHours!]! + + managers: [User!]! @isManagerFor(makerspaceIdField: "id") + staff: [User!]! @isStaffFor(makerspaceIdField: "id") + + managerAnonymousGroup: AnonymousGroup @isManagerFor(makerspaceIdField: "id") + staffAnonymousGroup: AnonymousGroup @isManagerFor(makerspaceIdField: "id") } type Query { @@ -19,6 +27,9 @@ type Query { } type Mutation { - createMakerspace(name: String!, hidden: Boolean!): ID! - deleteMakerspace(id: ID!): Boolean! -} \ No newline at end of file + createMakerspace(name: String!, hidden: Boolean!): ID! @isAdmin + deleteMakerspace(id: ID!): Boolean! @isAdmin + + setManagerSubgroups(makerspaceId: ID!, subgroups: [Int!]!): Boolean! @isManagerFor(makerspaceIdArg: "makerspaceId") + setStaffSubgroups(makerspaceId: ID!, subgroups: [Int!]!): Boolean! @isManagerFor(makerspaceIdArg: "makerspaceId") +} diff --git a/internal/gql/schema/user.graphqls b/internal/gql/schema/user.graphqls index 2982fdb5..d9c2889c 100644 --- a/internal/gql/schema/user.graphqls +++ b/internal/gql/schema/user.graphqls @@ -1,16 +1,8 @@ scalar Time directive @isAdmin on FIELD_DEFINITION -directive @isManager on FIELD_DEFINITION -directive @isStaff on FIELD_DEFINITION -directive @isTrainer on FIELD_DEFINITION -# These directives REQUIRE the queries to have specific parameters in order to function properly -directive @isManagerFor on FIELD_DEFINITION # REQUIRES a makerspace_id parameter -directive @isStaffFor on FIELD_DEFINITION # REQUIRES a makerspace_id parameter -directive @isTrainerFor on FIELD_DEFINITION # REQUIRES an equipment_id parameter directive @isSelf on FIELD_DEFINITION # REQUIRES a target_id parameter -directive @isStaffOrSelf on FIELD_DEFINITION # REQUIRES a target_id parameter type User { id: ID! @@ -24,10 +16,9 @@ type User { notes: String! admin: Boolean! force_archive: Boolean - card_tag: String! @isStaff } extend type Query { - user(id: ID!): User! + user(id: ID!): User! @isSelf # isStaffOrSelf currentUser: User! } \ No newline at end of file diff --git a/internal/logging/audit_log.go b/internal/logging/audit_log.go index c992dde7..d6af2d4c 100644 --- a/internal/logging/audit_log.go +++ b/internal/logging/audit_log.go @@ -78,7 +78,7 @@ func dataForEntities(fmtString string, entities []models.LogEntity) map[string]a locations := auditLogRegex.FindAllString(fmtString, -1) for i, entityType := range locations { - if i > len(entities) { + if i >= len(entities) { slog.Warn("Invalid audit log format string - too many specifiers without entities", "num_specifiers", len(locations), "num_entities", len(entities)) break } @@ -101,11 +101,8 @@ func dataForEntities(fmtString string, entities []models.LogEntity) map[string]a func CreatePlainString(fmtString string, entities ...models.LogEntity) string { index := 0 replacer := func(s string) string { - if len(s) > 2 { - s = s[1 : len(s)-1] - } var entity models.LogEntity - if index > len(entities) { + if index >= len(entities) { entity = models.LogEntity{Id: -1, Label: "unknown"} } else { entity = entities[index] @@ -124,7 +121,7 @@ func CreateFormatString(fmtString string, entities ...models.LogEntity) string { s = s[1 : len(s)-1] } var entity models.LogEntity - if index > len(entities) { + if index >= len(entities) { entity = models.LogEntity{Id: -1, Label: "unknown"} } else { entity = entities[index] diff --git a/internal/logging/audit_log_test.go b/internal/logging/audit_log_test.go index 1fa5343b..58f2a01d 100644 --- a/internal/logging/audit_log_test.go +++ b/internal/logging/audit_log_test.go @@ -80,3 +80,26 @@ func TestJsonDataGenerationMultipleSame(t *testing.T) { } } + +func TestMoreSpacesThanEntitiesFormat(t *testing.T) { + // should log a warning but still work and not crash + str := CreateFormatString("{user} elevated {user}", user1.LogEntity()) + ref := "{user:0:John Doe} elevated {user:-1:unknown}" + if str != ref { + t.Errorf("reference and created string don't agree. Wanted `%s` got `%s`", str, ref) + } +} + +func TestMoreSpacesThanEntitiesPlain(t *testing.T) { + // should log a warning but still work and not crash + str := CreatePlainString("{user} elevated {user}", user1.LogEntity()) + ref := "John Doe elevated unknown" + if str != ref { + t.Errorf("reference and created string don't agree. Wanted `%s` got `%s`", str, ref) + } +} + +func TestMoreSpacesThanEntitiesData(t *testing.T) { + // should log a warning but still work and not crash + _ = dataForEntities("{user} elevated {user}", []models.LogEntity{user1.LogEntity()}) +} diff --git a/internal/plugins/plugin_allowlist.go b/internal/plugins/plugin_allowlist.go index 6d74701a..7fd5971c 100644 --- a/internal/plugins/plugin_allowlist.go +++ b/internal/plugins/plugin_allowlist.go @@ -20,20 +20,6 @@ var wanted_plugins = []common.PluginDescription{ PluginType: common.PluginType_Notification, Options: map[string]string{}, }, - // { - // Name: "notification.core.linux_email", - // Version: 1, - // MagicValue: "904d74d0-24e7-49b8-a4f6-0914aa4edde8", - // Url: "", - // PluginType: PluginType_Notification, - // }, - // { - // Name: "auth.rit.shibboleth_sso", - // Version: 1, - // MagicValue: "904d74d0-24e7-49b8-a4f6-0914aa4edde8", - // Url: "", - // PluginType: PluginType_Notification, - // }, { Name: "auth.core.saml", Version: 1,