Consolidate frontend REST calls with GraphQL
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 1s
Some checks failed
Deploy with Docker Compose / deploy (push) Failing after 1s
Replace 5 separate REST calls on home page load with a single GraphQL query. Add homeData store that fetches posts, favorites, activities, spotify, and auth in one request. Convert all admin mutations and auth flows to use GraphQL. Add album images to Spotify GraphQL schema. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,24 @@
|
||||
module adam-french.co.uk/backend
|
||||
|
||||
go 1.24.0
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.17.88
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/vektah/gqlparser/v2 v2.5.32
|
||||
github.com/zmb3/spotify/v2 v2.4.3
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0 // indirect
|
||||
github.com/agnivade/levenshtein v1.2.1 // indirect
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
@@ -22,10 +27,11 @@ require (
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
@@ -41,7 +47,7 @@ require (
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
|
||||
github.com/sosodev/duration v1.4.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
@@ -50,12 +56,11 @@ require (
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
@@ -31,10 +31,22 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/99designs/gqlgen v0.17.88 h1:neMQDgehMwT1vYIOx/w5ZYPUU/iMNAJzRO44I5Intoc=
|
||||
github.com/99designs/gqlgen v0.17.88/go.mod h1:qeqYFEgOeSKqWedOjogPizimp2iu4E23bdPvl4jTYic=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||
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/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
@@ -50,6 +62,10 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -71,10 +87,12 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
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-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -102,10 +120,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@@ -129,12 +144,16 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
@@ -178,6 +197,10 @@ github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQ
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -203,6 +226,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc=
|
||||
github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -226,8 +251,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -260,8 +285,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -294,14 +319,13 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 h1:Ati8dO7+U7mxpkPSxBZQEvzHVUYB/MqCklCN8ig5w/o=
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
@@ -315,8 +339,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -351,8 +375,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -368,8 +392,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -415,8 +439,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -443,7 +467,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@@ -499,12 +522,14 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
59
backend/gqlgen.yml
Normal file
59
backend/gqlgen.yml
Normal file
@@ -0,0 +1,59 @@
|
||||
schema:
|
||||
- graph/schema/*.graphql
|
||||
|
||||
exec:
|
||||
filename: graph/generated.go
|
||||
package: graph
|
||||
|
||||
model:
|
||||
filename: graph/model/models_gen.go
|
||||
package: model
|
||||
|
||||
resolver:
|
||||
layout: follow-schema
|
||||
dir: graph
|
||||
package: graph
|
||||
filename_template: "{name}.resolvers.go"
|
||||
|
||||
models:
|
||||
ID:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.IntID
|
||||
User:
|
||||
model:
|
||||
- adam-french.co.uk/backend/models.User
|
||||
fields:
|
||||
password:
|
||||
resolver: false
|
||||
deletedAt:
|
||||
resolver: false
|
||||
Post:
|
||||
model:
|
||||
- adam-french.co.uk/backend/models.Post
|
||||
fields:
|
||||
deletedAt:
|
||||
resolver: false
|
||||
Activity:
|
||||
model:
|
||||
- adam-french.co.uk/backend/models.Activity
|
||||
fields:
|
||||
deletedAt:
|
||||
resolver: false
|
||||
Favorite:
|
||||
model:
|
||||
- adam-french.co.uk/backend/models.Favorite
|
||||
fields:
|
||||
deletedAt:
|
||||
resolver: false
|
||||
Rowing:
|
||||
model:
|
||||
- adam-french.co.uk/backend/models.Rowing
|
||||
fields:
|
||||
deletedAt:
|
||||
resolver: false
|
||||
Message:
|
||||
model:
|
||||
- adam-french.co.uk/backend/models.Message
|
||||
fields:
|
||||
deletedAt:
|
||||
resolver: false
|
||||
22
backend/graph/activity.resolvers.go
Normal file
22
backend/graph/activity.resolvers.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package graph
|
||||
|
||||
// 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.88
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"adam-french.co.uk/backend/models"
|
||||
)
|
||||
|
||||
// ID is the resolver for the id field.
|
||||
func (r *activityResolver) ID(ctx context.Context, obj *models.Activity) (int, error) {
|
||||
return int(obj.ID), nil
|
||||
}
|
||||
|
||||
// Activity returns ActivityResolver implementation.
|
||||
func (r *Resolver) Activity() ActivityResolver { return &activityResolver{r} }
|
||||
|
||||
type activityResolver struct{ *Resolver }
|
||||
52
backend/graph/context.go
Normal file
52
backend/graph/context.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
userClaimsKey contextKey = "userClaims"
|
||||
ginContextKey contextKey = "ginContext"
|
||||
)
|
||||
|
||||
func UserClaimsFromCtx(ctx context.Context) *jwt.MapClaims {
|
||||
claims, ok := ctx.Value(userClaimsKey).(*jwt.MapClaims)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return claims
|
||||
}
|
||||
|
||||
func UserIDFromCtx(ctx context.Context) (uint, bool) {
|
||||
claims := UserClaimsFromCtx(ctx)
|
||||
if claims == nil {
|
||||
return 0, false
|
||||
}
|
||||
idF, ok := (*claims)["id"].(float64)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return uint(idF), true
|
||||
}
|
||||
|
||||
func IsAdminFromCtx(ctx context.Context) bool {
|
||||
claims := UserClaimsFromCtx(ctx)
|
||||
if claims == nil {
|
||||
return false
|
||||
}
|
||||
admin, ok := (*claims)["admin"].(bool)
|
||||
return ok && admin
|
||||
}
|
||||
|
||||
func GinContextFromCtx(ctx context.Context) *gin.Context {
|
||||
gc, ok := ctx.Value(ginContextKey).(*gin.Context)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return gc
|
||||
}
|
||||
22
backend/graph/favorite.resolvers.go
Normal file
22
backend/graph/favorite.resolvers.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package graph
|
||||
|
||||
// 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.88
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"adam-french.co.uk/backend/models"
|
||||
)
|
||||
|
||||
// ID is the resolver for the id field.
|
||||
func (r *favoriteResolver) ID(ctx context.Context, obj *models.Favorite) (int, error) {
|
||||
return int(obj.ID), nil
|
||||
}
|
||||
|
||||
// Favorite returns FavoriteResolver implementation.
|
||||
func (r *Resolver) Favorite() FavoriteResolver { return &favoriteResolver{r} }
|
||||
|
||||
type favoriteResolver struct{ *Resolver }
|
||||
7656
backend/graph/generated.go
Normal file
7656
backend/graph/generated.go
Normal file
File diff suppressed because it is too large
Load Diff
27
backend/graph/message.resolvers.go
Normal file
27
backend/graph/message.resolvers.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package graph
|
||||
|
||||
// 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.88
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"adam-french.co.uk/backend/models"
|
||||
)
|
||||
|
||||
// ID is the resolver for the id field.
|
||||
func (r *messageResolver) ID(ctx context.Context, obj *models.Message) (int, error) {
|
||||
return int(obj.ID), nil
|
||||
}
|
||||
|
||||
// AuthorID is the resolver for the authorId field.
|
||||
func (r *messageResolver) AuthorID(ctx context.Context, obj *models.Message) (int, error) {
|
||||
return int(obj.AuthorID), nil
|
||||
}
|
||||
|
||||
// Message returns MessageResolver implementation.
|
||||
func (r *Resolver) Message() MessageResolver { return &messageResolver{r} }
|
||||
|
||||
type messageResolver struct{ *Resolver }
|
||||
25
backend/graph/middleware.go
Normal file
25
backend/graph/middleware.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"adam-french.co.uk/backend/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AuthContextMiddleware(auth *services.Auth) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx := context.WithValue(c.Request.Context(), ginContextKey, c)
|
||||
|
||||
accessToken, err := c.Cookie("access_token")
|
||||
if err == nil {
|
||||
claims, err := auth.VerifyJWT(accessToken)
|
||||
if err == nil {
|
||||
ctx = context.WithValue(ctx, userClaimsKey, claims)
|
||||
}
|
||||
}
|
||||
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
80
backend/graph/model/models_gen.go
Normal file
80
backend/graph/model/models_gen.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"adam-french.co.uk/backend/models"
|
||||
)
|
||||
|
||||
type AuthPayload struct {
|
||||
User *models.User `json:"user"`
|
||||
}
|
||||
|
||||
type CreateActivityInput struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Link *string `json:"link,omitempty"`
|
||||
}
|
||||
|
||||
type CreateFavoriteInput struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Link *string `json:"link,omitempty"`
|
||||
}
|
||||
|
||||
type CreatePostInput struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type CreateUserInput struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type LoginInput struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type Mutation struct {
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
}
|
||||
|
||||
type SpotifyAlbum struct {
|
||||
Name string `json:"name"`
|
||||
Images []*SpotifyImage `json:"images"`
|
||||
}
|
||||
|
||||
type SpotifyArtist struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type SpotifyImage struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type SpotifyPlaying struct {
|
||||
Playing bool `json:"playing"`
|
||||
Track *SpotifyTrack `json:"track,omitempty"`
|
||||
}
|
||||
|
||||
type SpotifyRecentItem struct {
|
||||
Track *SpotifyTrack `json:"track"`
|
||||
PlayedAt time.Time `json:"playedAt"`
|
||||
}
|
||||
|
||||
type SpotifyTrack struct {
|
||||
Name string `json:"name"`
|
||||
Artists []*SpotifyArtist `json:"artists"`
|
||||
Album *SpotifyAlbum `json:"album"`
|
||||
}
|
||||
|
||||
type UpdatePostInput struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
22
backend/graph/post.resolvers.go
Normal file
22
backend/graph/post.resolvers.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package graph
|
||||
|
||||
// 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.88
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"adam-french.co.uk/backend/models"
|
||||
)
|
||||
|
||||
// ID is the resolver for the id field.
|
||||
func (r *postResolver) ID(ctx context.Context, obj *models.Post) (int, error) {
|
||||
return int(obj.ID), nil
|
||||
}
|
||||
|
||||
// Post returns PostResolver implementation.
|
||||
func (r *Resolver) Post() PostResolver { return &postResolver{r} }
|
||||
|
||||
type postResolver struct{ *Resolver }
|
||||
12
backend/graph/resolver.go
Normal file
12
backend/graph/resolver.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package graph
|
||||
|
||||
import "adam-french.co.uk/backend/handlers"
|
||||
|
||||
// This file will not be regenerated automatically.
|
||||
//
|
||||
// It serves as dependency injection for your app, add any dependencies you require
|
||||
// here.
|
||||
|
||||
type Resolver struct {
|
||||
Store *handlers.Store
|
||||
}
|
||||
32
backend/graph/rowing.resolvers.go
Normal file
32
backend/graph/rowing.resolvers.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package graph
|
||||
|
||||
// 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.88
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"adam-french.co.uk/backend/models"
|
||||
)
|
||||
|
||||
// ID is the resolver for the id field.
|
||||
func (r *rowingResolver) ID(ctx context.Context, obj *models.Rowing) (int, error) {
|
||||
return int(obj.ID), nil
|
||||
}
|
||||
|
||||
// Time is the resolver for the time field.
|
||||
func (r *rowingResolver) Time(ctx context.Context, obj *models.Rowing) (int, error) {
|
||||
return int(obj.Time), nil
|
||||
}
|
||||
|
||||
// Distance is the resolver for the distance field.
|
||||
func (r *rowingResolver) Distance(ctx context.Context, obj *models.Rowing) (int, error) {
|
||||
return int(obj.Distance), nil
|
||||
}
|
||||
|
||||
// Rowing returns RowingResolver implementation.
|
||||
func (r *Resolver) Rowing() RowingResolver { return &rowingResolver{r} }
|
||||
|
||||
type rowingResolver struct{ *Resolver }
|
||||
456
backend/graph/schema.resolvers.go
Normal file
456
backend/graph/schema.resolvers.go
Normal file
@@ -0,0 +1,456 @@
|
||||
package graph
|
||||
|
||||
// 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.88
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"adam-french.co.uk/backend/graph/model"
|
||||
"adam-french.co.uk/backend/models"
|
||||
spotify "github.com/zmb3/spotify/v2"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Login is the resolver for the login field.
|
||||
func (r *mutationResolver) Login(ctx context.Context, input model.LoginInput) (*model.AuthPayload, error) {
|
||||
gc := GinContextFromCtx(ctx)
|
||||
if gc == nil {
|
||||
return nil, fmt.Errorf("could not get gin context")
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := r.Store.DB.Where("username = ?", input.Username).First(&user).Error; err != nil {
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword(user.Password, []byte(input.Password)); err != nil {
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
|
||||
tokens, err := r.Store.Auth.GenerateJWT(&user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate tokens")
|
||||
}
|
||||
|
||||
gc.SetSameSite(http.SameSiteLaxMode)
|
||||
gc.SetCookie(
|
||||
"access_token",
|
||||
tokens.AccessToken,
|
||||
int(r.Store.Auth.Config.AccessTokenLifetime.Seconds()),
|
||||
r.Store.Auth.Config.Endpoint,
|
||||
r.Store.Auth.Config.Domain,
|
||||
true, true,
|
||||
)
|
||||
gc.SetCookie(
|
||||
"refresh_token",
|
||||
tokens.RefreshToken,
|
||||
int(r.Store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
||||
r.Store.Auth.Config.Endpoint,
|
||||
r.Store.Auth.Config.Domain,
|
||||
true, true,
|
||||
)
|
||||
|
||||
return &model.AuthPayload{User: &user}, nil
|
||||
}
|
||||
|
||||
// Logout is the resolver for the logout field.
|
||||
func (r *mutationResolver) Logout(ctx context.Context) (bool, error) {
|
||||
gc := GinContextFromCtx(ctx)
|
||||
if gc == nil {
|
||||
return false, fmt.Errorf("could not get gin context")
|
||||
}
|
||||
|
||||
gc.SetSameSite(http.SameSiteLaxMode)
|
||||
gc.SetCookie("access_token", "", -1, "", "", true, true)
|
||||
gc.SetCookie("refresh_token", "", -1, "", "", true, true)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RefreshToken is the resolver for the refreshToken field.
|
||||
func (r *mutationResolver) RefreshToken(ctx context.Context) (*model.AuthPayload, error) {
|
||||
gc := GinContextFromCtx(ctx)
|
||||
if gc == nil {
|
||||
return nil, fmt.Errorf("could not get gin context")
|
||||
}
|
||||
|
||||
refreshToken, err := gc.Cookie("refresh_token")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
claims, err := r.Store.Auth.VerifyJWT(refreshToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
userIDF, ok := (*claims)["id"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid token claims")
|
||||
}
|
||||
|
||||
var user models.User
|
||||
user.ID = uint(userIDF)
|
||||
if err := r.Store.DB.First(&user).Error; err != nil {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
|
||||
tokens, err := r.Store.Auth.GenerateJWT(&user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate tokens")
|
||||
}
|
||||
|
||||
gc.SetSameSite(http.SameSiteLaxMode)
|
||||
gc.SetCookie(
|
||||
"access_token",
|
||||
tokens.AccessToken,
|
||||
int(r.Store.Auth.Config.AccessTokenLifetime.Seconds()),
|
||||
r.Store.Auth.Config.Endpoint,
|
||||
r.Store.Auth.Config.Domain,
|
||||
true, true,
|
||||
)
|
||||
gc.SetCookie(
|
||||
"refresh_token",
|
||||
tokens.RefreshToken,
|
||||
int(r.Store.Auth.Config.RefreshTokenLifetime.Seconds()),
|
||||
r.Store.Auth.Config.Endpoint,
|
||||
r.Store.Auth.Config.Domain,
|
||||
true, true,
|
||||
)
|
||||
|
||||
return &model.AuthPayload{User: &user}, nil
|
||||
}
|
||||
|
||||
// CreatePost is the resolver for the createPost field.
|
||||
func (r *mutationResolver) CreatePost(ctx context.Context, input model.CreatePostInput) (*models.Post, error) {
|
||||
if !IsAdminFromCtx(ctx) {
|
||||
return nil, fmt.Errorf("admin access required")
|
||||
}
|
||||
|
||||
userID, ok := UserIDFromCtx(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
post := models.Post{Title: input.Title, Content: input.Content, AuthorID: userID}
|
||||
if err := r.Store.DB.Create(&post).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// UpdatePost is the resolver for the updatePost field.
|
||||
func (r *mutationResolver) UpdatePost(ctx context.Context, id int, input model.UpdatePostInput) (*models.Post, error) {
|
||||
if !IsAdminFromCtx(ctx) {
|
||||
return nil, fmt.Errorf("admin access required")
|
||||
}
|
||||
|
||||
userID, ok := UserIDFromCtx(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
var post models.Post
|
||||
if err := r.Store.DB.First(&post, id).Error; err != nil {
|
||||
return nil, fmt.Errorf("post not found")
|
||||
}
|
||||
|
||||
if post.AuthorID != userID {
|
||||
return nil, fmt.Errorf("you can only update your own posts")
|
||||
}
|
||||
|
||||
post.Title = input.Title
|
||||
post.Content = input.Content
|
||||
if err := r.Store.DB.Save(&post).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// DeletePost is the resolver for the deletePost field.
|
||||
func (r *mutationResolver) DeletePost(ctx context.Context, id int) (*models.Post, error) {
|
||||
if !IsAdminFromCtx(ctx) {
|
||||
return nil, fmt.Errorf("admin access required")
|
||||
}
|
||||
|
||||
userID, ok := UserIDFromCtx(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
var post models.Post
|
||||
if err := r.Store.DB.First(&post, id).Error; err != nil {
|
||||
return nil, fmt.Errorf("post not found")
|
||||
}
|
||||
|
||||
if post.AuthorID != userID {
|
||||
return nil, fmt.Errorf("you can only delete your own posts")
|
||||
}
|
||||
|
||||
r.Store.DB.Delete(&post)
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// CreateUser is the resolver for the createUser field.
|
||||
func (r *mutationResolver) CreateUser(ctx context.Context, input model.CreateUserInput) (*models.User, error) {
|
||||
if !IsAdminFromCtx(ctx) {
|
||||
return nil, fmt.Errorf("admin access required")
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := models.User{Username: input.Username, Password: hashedPassword}
|
||||
if err := r.Store.DB.Create(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// DeleteUser is the resolver for the deleteUser field.
|
||||
func (r *mutationResolver) DeleteUser(ctx context.Context, id int) (*models.User, error) {
|
||||
if !IsAdminFromCtx(ctx) {
|
||||
return nil, fmt.Errorf("admin access required")
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := r.Store.DB.First(&user, id).Error; err != nil {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
|
||||
if err := r.Store.DB.Delete(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// SetUserAdmin is the resolver for the setUserAdmin field.
|
||||
func (r *mutationResolver) SetUserAdmin(ctx context.Context, id int, admin bool) (*models.User, error) {
|
||||
if !IsAdminFromCtx(ctx) {
|
||||
return nil, fmt.Errorf("admin access required")
|
||||
}
|
||||
|
||||
callerID, ok := UserIDFromCtx(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
if uint(id) == callerID {
|
||||
return nil, fmt.Errorf("cannot change your own admin status")
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := r.Store.DB.First(&user, id).Error; err != nil {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
|
||||
user.Admin = admin
|
||||
if err := r.Store.DB.Save(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// CreateFavorite is the resolver for the createFavorite field.
|
||||
func (r *mutationResolver) CreateFavorite(ctx context.Context, input model.CreateFavoriteInput) (*models.Favorite, error) {
|
||||
if !IsAdminFromCtx(ctx) {
|
||||
return nil, fmt.Errorf("admin access required")
|
||||
}
|
||||
|
||||
favorite := models.Favorite{Type: input.Type, Name: input.Name, Link: input.Link}
|
||||
if err := r.Store.DB.Create(&favorite).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &favorite, nil
|
||||
}
|
||||
|
||||
// CreateActivity is the resolver for the createActivity field.
|
||||
func (r *mutationResolver) CreateActivity(ctx context.Context, input model.CreateActivityInput) (*models.Activity, error) {
|
||||
if !IsAdminFromCtx(ctx) {
|
||||
return nil, fmt.Errorf("admin access required")
|
||||
}
|
||||
|
||||
activity := models.Activity{Type: input.Type, Name: input.Name, Link: input.Link}
|
||||
if err := r.Store.DB.Create(&activity).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &activity, nil
|
||||
}
|
||||
|
||||
// Users is the resolver for the users field.
|
||||
func (r *queryResolver) Users(ctx context.Context) ([]*models.User, error) {
|
||||
var users []models.User
|
||||
if err := r.Store.DB.Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*models.User, len(users))
|
||||
for i := range users {
|
||||
result[i] = &users[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// User is the resolver for the user field.
|
||||
func (r *queryResolver) User(ctx context.Context, id int) (*models.User, error) {
|
||||
var user models.User
|
||||
if err := r.Store.DB.First(&user, id).Error; err != nil {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Posts is the resolver for the posts field.
|
||||
func (r *queryResolver) Posts(ctx context.Context) ([]*models.Post, error) {
|
||||
var posts []models.Post
|
||||
if err := r.Store.DB.Preload("Author").Order("created_at DESC").Find(&posts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*models.Post, len(posts))
|
||||
for i := range posts {
|
||||
result[i] = &posts[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Post is the resolver for the post field.
|
||||
func (r *queryResolver) Post(ctx context.Context, id int) (*models.Post, error) {
|
||||
var post models.Post
|
||||
if err := r.Store.DB.Preload("Author").First(&post, id).Error; err != nil {
|
||||
return nil, fmt.Errorf("post not found")
|
||||
}
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// Activities is the resolver for the activities field.
|
||||
func (r *queryResolver) Activities(ctx context.Context) ([]*models.Activity, error) {
|
||||
var activities []models.Activity
|
||||
if err := r.Store.DB.Order("created_at DESC").Find(&activities).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*models.Activity, len(activities))
|
||||
for i := range activities {
|
||||
result[i] = &activities[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Favorites is the resolver for the favorites field.
|
||||
func (r *queryResolver) Favorites(ctx context.Context) ([]*models.Favorite, error) {
|
||||
var favorites []models.Favorite
|
||||
if err := r.Store.DB.Order("created_at DESC").Find(&favorites).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*models.Favorite, len(favorites))
|
||||
for i := range favorites {
|
||||
result[i] = &favorites[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// RowingSessions is the resolver for the rowingSessions field.
|
||||
func (r *queryResolver) RowingSessions(ctx context.Context) ([]*models.Rowing, error) {
|
||||
var rows []models.Rowing
|
||||
if err := r.Store.DB.Order("created_at DESC").Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*models.Rowing, len(rows))
|
||||
for i := range rows {
|
||||
result[i] = &rows[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Messages is the resolver for the messages field.
|
||||
func (r *queryResolver) Messages(ctx context.Context) ([]*models.Message, error) {
|
||||
var messages []models.Message
|
||||
if err := r.Store.DB.Order("created_at DESC").Find(&messages).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*models.Message, len(messages))
|
||||
for i := range messages {
|
||||
result[i] = &messages[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SpotifyListening is the resolver for the spotifyListening field.
|
||||
func (r *queryResolver) SpotifyListening(ctx context.Context) (*model.SpotifyPlaying, error) {
|
||||
if r.Store.SpotifyClient == nil {
|
||||
return nil, fmt.Errorf("Spotify not authenticated")
|
||||
}
|
||||
|
||||
playing, err := r.Store.SpotifyClient.PlayerCurrentlyPlaying(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &model.SpotifyPlaying{Playing: playing.Playing}
|
||||
if playing.Item != nil {
|
||||
result.Track = mapSpotifyTrack(playing.Item)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SpotifyRecent is the resolver for the spotifyRecent field.
|
||||
func (r *queryResolver) SpotifyRecent(ctx context.Context) ([]*model.SpotifyRecentItem, error) {
|
||||
if r.Store.SpotifyClient == nil {
|
||||
return nil, fmt.Errorf("Spotify not authenticated")
|
||||
}
|
||||
|
||||
if r.Store.RecentSongsFresh() {
|
||||
return mapRecentItems(*r.Store.RecentSongs), nil
|
||||
}
|
||||
|
||||
opts := spotify.RecentlyPlayedOptions{Limit: 3}
|
||||
played, err := r.Store.SpotifyClient.PlayerRecentlyPlayedOpt(ctx, &opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.Store.RecentSongs = &played
|
||||
r.Store.RecentSongsFetchedAt = time.Now()
|
||||
|
||||
return mapRecentItems(played), nil
|
||||
}
|
||||
|
||||
// Me is the resolver for the me field.
|
||||
func (r *queryResolver) Me(ctx context.Context) (*models.User, error) {
|
||||
userID, ok := UserIDFromCtx(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
var user models.User
|
||||
user.ID = userID
|
||||
if err := r.Store.DB.First(&user).Error; err != nil {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Mutation returns MutationResolver implementation.
|
||||
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
|
||||
|
||||
// Query returns QueryResolver implementation.
|
||||
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
|
||||
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
|
||||
14
backend/graph/schema/activity.graphql
Normal file
14
backend/graph/schema/activity.graphql
Normal file
@@ -0,0 +1,14 @@
|
||||
type Activity {
|
||||
id: ID!
|
||||
createdAt: Time!
|
||||
updatedAt: Time!
|
||||
type: String!
|
||||
name: String!
|
||||
link: String
|
||||
}
|
||||
|
||||
input CreateActivityInput {
|
||||
type: String!
|
||||
name: String!
|
||||
link: String
|
||||
}
|
||||
8
backend/graph/schema/auth.graphql
Normal file
8
backend/graph/schema/auth.graphql
Normal file
@@ -0,0 +1,8 @@
|
||||
input LoginInput {
|
||||
username: String!
|
||||
password: String!
|
||||
}
|
||||
|
||||
type AuthPayload {
|
||||
user: User!
|
||||
}
|
||||
14
backend/graph/schema/favorite.graphql
Normal file
14
backend/graph/schema/favorite.graphql
Normal file
@@ -0,0 +1,14 @@
|
||||
type Favorite {
|
||||
id: ID!
|
||||
createdAt: Time!
|
||||
updatedAt: Time!
|
||||
type: String!
|
||||
name: String!
|
||||
link: String
|
||||
}
|
||||
|
||||
input CreateFavoriteInput {
|
||||
type: String!
|
||||
name: String!
|
||||
link: String
|
||||
}
|
||||
7
backend/graph/schema/message.graphql
Normal file
7
backend/graph/schema/message.graphql
Normal file
@@ -0,0 +1,7 @@
|
||||
type Message {
|
||||
id: ID!
|
||||
content: String!
|
||||
authorId: Int!
|
||||
fileUrl: String
|
||||
createdAt: Time!
|
||||
}
|
||||
18
backend/graph/schema/post.graphql
Normal file
18
backend/graph/schema/post.graphql
Normal file
@@ -0,0 +1,18 @@
|
||||
type Post {
|
||||
id: ID!
|
||||
createdAt: Time!
|
||||
updatedAt: Time!
|
||||
title: String!
|
||||
author: User
|
||||
content: String!
|
||||
}
|
||||
|
||||
input CreatePostInput {
|
||||
title: String!
|
||||
content: String!
|
||||
}
|
||||
|
||||
input UpdatePostInput {
|
||||
title: String!
|
||||
content: String!
|
||||
}
|
||||
9
backend/graph/schema/rowing.graphql
Normal file
9
backend/graph/schema/rowing.graphql
Normal file
@@ -0,0 +1,9 @@
|
||||
type Rowing {
|
||||
id: ID!
|
||||
createdAt: Time!
|
||||
date: Time!
|
||||
time: Int!
|
||||
distance: Int!
|
||||
timePer500m: Float!
|
||||
calories: Float!
|
||||
}
|
||||
29
backend/graph/schema/schema.graphql
Normal file
29
backend/graph/schema/schema.graphql
Normal file
@@ -0,0 +1,29 @@
|
||||
scalar Time
|
||||
|
||||
type Query {
|
||||
users: [User!]!
|
||||
user(id: ID!): User
|
||||
posts: [Post!]!
|
||||
post(id: ID!): Post
|
||||
activities: [Activity!]!
|
||||
favorites: [Favorite!]!
|
||||
rowingSessions: [Rowing!]!
|
||||
messages: [Message!]!
|
||||
spotifyListening: SpotifyPlaying
|
||||
spotifyRecent: [SpotifyRecentItem!]!
|
||||
me: User
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
login(input: LoginInput!): AuthPayload!
|
||||
logout: Boolean!
|
||||
refreshToken: AuthPayload!
|
||||
createPost(input: CreatePostInput!): Post!
|
||||
updatePost(id: ID!, input: UpdatePostInput!): Post!
|
||||
deletePost(id: ID!): Post!
|
||||
createUser(input: CreateUserInput!): User!
|
||||
deleteUser(id: ID!): User!
|
||||
setUserAdmin(id: ID!, admin: Boolean!): User!
|
||||
createFavorite(input: CreateFavoriteInput!): Favorite!
|
||||
createActivity(input: CreateActivityInput!): Activity!
|
||||
}
|
||||
28
backend/graph/schema/spotify.graphql
Normal file
28
backend/graph/schema/spotify.graphql
Normal file
@@ -0,0 +1,28 @@
|
||||
type SpotifyArtist {
|
||||
name: String!
|
||||
}
|
||||
|
||||
type SpotifyImage {
|
||||
url: String!
|
||||
}
|
||||
|
||||
type SpotifyAlbum {
|
||||
name: String!
|
||||
images: [SpotifyImage!]!
|
||||
}
|
||||
|
||||
type SpotifyTrack {
|
||||
name: String!
|
||||
artists: [SpotifyArtist!]!
|
||||
album: SpotifyAlbum!
|
||||
}
|
||||
|
||||
type SpotifyPlaying {
|
||||
playing: Boolean!
|
||||
track: SpotifyTrack
|
||||
}
|
||||
|
||||
type SpotifyRecentItem {
|
||||
track: SpotifyTrack!
|
||||
playedAt: Time!
|
||||
}
|
||||
12
backend/graph/schema/user.graphql
Normal file
12
backend/graph/schema/user.graphql
Normal file
@@ -0,0 +1,12 @@
|
||||
type User {
|
||||
id: ID!
|
||||
createdAt: Time!
|
||||
updatedAt: Time!
|
||||
username: String!
|
||||
admin: Boolean!
|
||||
}
|
||||
|
||||
input CreateUserInput {
|
||||
username: String!
|
||||
password: String!
|
||||
}
|
||||
51
backend/graph/spotify_helpers.go
Normal file
51
backend/graph/spotify_helpers.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"adam-french.co.uk/backend/graph/model"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
func mapSpotifyImages(images []spotify.Image) []*model.SpotifyImage {
|
||||
result := make([]*model.SpotifyImage, len(images))
|
||||
for i, img := range images {
|
||||
result[i] = &model.SpotifyImage{URL: img.URL}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func mapSpotifyTrack(track *spotify.FullTrack) *model.SpotifyTrack {
|
||||
artists := make([]*model.SpotifyArtist, len(track.Artists))
|
||||
for i, a := range track.Artists {
|
||||
artists[i] = &model.SpotifyArtist{Name: a.Name}
|
||||
}
|
||||
return &model.SpotifyTrack{
|
||||
Name: track.Name,
|
||||
Artists: artists,
|
||||
Album: &model.SpotifyAlbum{
|
||||
Name: track.Album.Name,
|
||||
Images: mapSpotifyImages(track.Album.Images),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func mapRecentItems(items []spotify.RecentlyPlayedItem) []*model.SpotifyRecentItem {
|
||||
result := make([]*model.SpotifyRecentItem, len(items))
|
||||
for i, item := range items {
|
||||
artists := make([]*model.SpotifyArtist, len(item.Track.Artists))
|
||||
for j, a := range item.Track.Artists {
|
||||
artists[j] = &model.SpotifyArtist{Name: a.Name}
|
||||
}
|
||||
result[i] = &model.SpotifyRecentItem{
|
||||
PlayedAt: item.PlayedAt,
|
||||
Track: &model.SpotifyTrack{
|
||||
Name: item.Track.Name,
|
||||
Artists: artists,
|
||||
Album: &model.SpotifyAlbum{
|
||||
Name: item.Track.Album.Name,
|
||||
Images: mapSpotifyImages(item.Track.Album.Images),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
22
backend/graph/user.resolvers.go
Normal file
22
backend/graph/user.resolvers.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package graph
|
||||
|
||||
// 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.88
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"adam-french.co.uk/backend/models"
|
||||
)
|
||||
|
||||
// ID is the resolver for the id field.
|
||||
func (r *userResolver) ID(ctx context.Context, obj *models.User) (int, error) {
|
||||
return int(obj.ID), nil
|
||||
}
|
||||
|
||||
// User returns UserResolver implementation.
|
||||
func (r *Resolver) User() UserResolver { return &userResolver{r} }
|
||||
|
||||
type userResolver struct{ *Resolver }
|
||||
@@ -7,8 +7,11 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql/handler"
|
||||
"github.com/99designs/gqlgen/graphql/playground"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"adam-french.co.uk/backend/graph"
|
||||
"adam-french.co.uk/backend/handlers"
|
||||
"adam-french.co.uk/backend/services"
|
||||
)
|
||||
@@ -119,6 +122,17 @@ func main() {
|
||||
// NOTES
|
||||
r.GET("/notes/*path", store.GetNoteFile)
|
||||
|
||||
// GRAPHQL
|
||||
gqlSrv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{
|
||||
Resolvers: &graph.Resolver{Store: &store},
|
||||
}))
|
||||
r.POST("/graphql", graph.AuthContextMiddleware(auth), func(c *gin.Context) {
|
||||
gqlSrv.ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
r.GET("/graphql", func(c *gin.Context) {
|
||||
playground.Handler("GraphQL Playground", "/graphql").ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
|
||||
// HELLO WORLD
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"message": "Hello World"})
|
||||
|
||||
Reference in New Issue
Block a user