mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 05:22:24 -05:00 
			
		
		
		
	Inbox post (#22)
Inbox POST from federated servers now working for statuses and follow requests.
    Follow request client API added.
    Start work on federating outgoing messages.
    Other fixes and changes/tidying up.
	
	
This commit is contained in:
		
					parent
					
						
							
								742f985d5b
							
						
					
				
			
			
				commit
				
					
						cc48294c31
					
				
			
		
					 58 changed files with 2248 additions and 366 deletions
				
			
		
							
								
								
									
										50
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										50
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -5,31 +5,53 @@ go 1.16 | |||
| require ( | ||||
| 	github.com/buckket/go-blurhash v1.1.0 | ||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect | ||||
| 	github.com/dsoprea/go-exif v0.0.0-20210428042052-dca55bf8ca15 // indirect | ||||
| 	github.com/dsoprea/go-exif/v2 v2.0.0-20210428042052-dca55bf8ca15 // indirect | ||||
| 	github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413 // indirect | ||||
| 	github.com/dsoprea/go-jpeg-image-structure v0.0.0-20210505113650-8010c634293c // indirect | ||||
| 	github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect | ||||
| 	github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d // indirect | ||||
| 	github.com/dsoprea/go-png-image-structure v0.0.0-20210428043356-45b892641b59 // indirect | ||||
| 	github.com/dsoprea/go-utility v0.0.0-20200717064901-2fccff4aa15e // indirect | ||||
| 	github.com/gin-contrib/cors v1.3.1 | ||||
| 	github.com/gin-contrib/sessions v0.0.3 | ||||
| 	github.com/gin-gonic/gin v1.6.3 | ||||
| 	github.com/go-fed/activity v1.0.0 | ||||
| 	github.com/go-fed/httpsig v0.1.1-0.20190914113940-c2de3672e5b5 | ||||
| 	github.com/gin-gonic/gin v1.7.1 | ||||
| 	github.com/go-errors/errors v1.2.0 // indirect | ||||
| 	github.com/go-fed/activity v1.0.1-0.20210426194615-e0de0863dcc1 | ||||
| 	github.com/go-fed/httpsig v1.1.0 | ||||
| 	github.com/go-pg/pg/extra/pgdebug v0.2.0 | ||||
| 	github.com/go-pg/pg/v10 v10.8.0 | ||||
| 	github.com/golang/mock v1.4.4 // indirect | ||||
| 	github.com/go-pg/pg/v10 v10.9.1 | ||||
| 	github.com/go-playground/validator/v10 v10.6.0 // indirect | ||||
| 	github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect | ||||
| 	github.com/golang/mock v1.5.0 // indirect | ||||
| 	github.com/google/uuid v1.2.0 | ||||
| 	github.com/gorilla/sessions v1.2.1 // indirect | ||||
| 	github.com/h2non/filetype v1.1.1 | ||||
| 	github.com/json-iterator/go v1.1.11 // indirect | ||||
| 	github.com/leodido/go-urn v1.2.1 // indirect | ||||
| 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||
| 	github.com/modern-go/reflect2 v1.0.1 // indirect | ||||
| 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 | ||||
| 	github.com/onsi/ginkgo v1.15.0 // indirect | ||||
| 	github.com/onsi/gomega v1.10.5 // indirect | ||||
| 	github.com/sirupsen/logrus v1.8.0 | ||||
| 	github.com/onsi/gomega v1.12.0 // indirect | ||||
| 	github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect | ||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||
| 	github.com/sirupsen/logrus v1.8.1 | ||||
| 	github.com/stretchr/objx v0.3.0 // indirect | ||||
| 	github.com/stretchr/testify v1.7.0 | ||||
| 	github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203 | ||||
| 	github.com/superseriousbusiness/oauth2/v4 v4.2.1-0.20210327102222-902aba1ef45f | ||||
| 	github.com/tidwall/btree v0.4.2 // indirect | ||||
| 	github.com/tidwall/buntdb v1.2.0 // indirect | ||||
| 	github.com/tidwall/pretty v1.1.0 // indirect | ||||
| 	github.com/tidwall/btree v0.5.0 // indirect | ||||
| 	github.com/tidwall/buntdb v1.2.3 // indirect | ||||
| 	github.com/ugorji/go v1.2.5 // indirect | ||||
| 	github.com/urfave/cli/v2 v2.3.0 | ||||
| 	github.com/vmihailenco/msgpack/v5 v5.3.1 // indirect | ||||
| 	github.com/wagslane/go-password-validator v0.3.0 | ||||
| 	golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b | ||||
| 	golang.org/x/text v0.3.3 | ||||
| 	go.opentelemetry.io/otel v0.20.0 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf | ||||
| 	golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect | ||||
| 	golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect | ||||
| 	golang.org/x/text v0.3.6 | ||||
| 	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect | ||||
| 	gopkg.in/yaml.v2 v2.3.0 | ||||
| 	gopkg.in/yaml.v2 v2.4.0 | ||||
| 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect | ||||
| ) | ||||
|  |  | |||
							
								
								
									
										186
									
								
								go.sum
									
										
									
									
									
								
							
							
						
						
									
										186
									
								
								go.sum
									
										
									
									
									
								
							|  | @ -20,24 +20,33 @@ 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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | ||||
| github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||||
| github.com/dsoprea/go-exif v0.0.0-20210131231135-d154f10435cc h1:AuzYp98IFVOi0NU/WcZyGDQ6vAh/zkCjxGD3kt8aLzA= | ||||
| github.com/dsoprea/go-exif v0.0.0-20210131231135-d154f10435cc/go.mod h1:lOaOt7+UEppOgyvRy749v3do836U/hw0YVJNjoyPaEs= | ||||
| github.com/dsoprea/go-exif v0.0.0-20210428042052-dca55bf8ca15 h1:uqmD+m+8q7afXhhtABSab5ZMWpy0L+Vi7p/SDDNIMbs= | ||||
| github.com/dsoprea/go-exif v0.0.0-20210428042052-dca55bf8ca15/go.mod h1:lOaOt7+UEppOgyvRy749v3do836U/hw0YVJNjoyPaEs= | ||||
| github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= | ||||
| github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4 h1:Mg7pY7kxDQD2Bkvr1N+XW4BESSIQ7tTTR7Vv+Gi2CsM= | ||||
| github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4/go.mod h1:9EXlPeHfblFFnwu5UOqmP2eoZfJyAZ2Ri/Vki33ajO0= | ||||
| github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb h1:gwjJjUr6FY7zAWVEueFPrcRHhd9+IK81TcItbqw2du4= | ||||
| github.com/dsoprea/go-exif/v2 v2.0.0-20210428042052-dca55bf8ca15 h1:a73ubT6QCaR0G6ZfkA0i3ecR+bB3OFCa9VoKjZT8H24= | ||||
| github.com/dsoprea/go-exif/v2 v2.0.0-20210428042052-dca55bf8ca15/go.mod h1:oKrjk2kb3rAR5NbtSTLUMvMSbc+k8ZosI3MaVH47noc= | ||||
| github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM= | ||||
| github.com/dsoprea/go-jpeg-image-structure v0.0.0-20210128210355-86b1014917f2 h1:ULCSN6v0WISNbALxomGPXh4dSjRKPW+7+seYoMz8UTc= | ||||
| github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413 h1:YDRiMEm32T60Kpm35YzOK9ZHgjsS1Qrid+XskNcsdp8= | ||||
| github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM= | ||||
| github.com/dsoprea/go-jpeg-image-structure v0.0.0-20210128210355-86b1014917f2/go.mod h1:ZoOP3yUG0HD1T4IUjIFsz/2OAB2yB4YX6NSm4K+uJRg= | ||||
| github.com/dsoprea/go-jpeg-image-structure v0.0.0-20210505113650-8010c634293c h1:g2vhZhMoEz2oqTPT5xV1pvOc93KXMeRsz2dSeVDG0zs= | ||||
| github.com/dsoprea/go-jpeg-image-structure v0.0.0-20210505113650-8010c634293c/go.mod h1:ZoOP3yUG0HD1T4IUjIFsz/2OAB2yB4YX6NSm4K+uJRg= | ||||
| github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= | ||||
| github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d h1:F/7L5wr/fP/SKeO5HuMlNEX9Ipyx2MbH2rV9G4zJRpk= | ||||
| github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= | ||||
| github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c h1:7j5aWACOzROpr+dvMtu8GnI97g9ShLWD72XIELMgn+c= | ||||
| github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg= | ||||
| github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= | ||||
| github.com/dsoprea/go-photoshop-info-format v0.0.0-20200609050348-3db9b63b202c/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E= | ||||
| github.com/dsoprea/go-png-image-structure v0.0.0-20200807080309-a98d4e94ac82 h1:RdwKOEEe2ND/JmoKh6I/EQlR9idKJTDOMffPFK6vN2M= | ||||
| github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d h1:dg6UMHa50VI01WuPWXPbNJpO8QSyvIF5T5n2IZiqX3A= | ||||
| github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E= | ||||
| github.com/dsoprea/go-png-image-structure v0.0.0-20200807080309-a98d4e94ac82/go.mod h1:aDYQkL/5gfRNZkoxiLTSWU4Y8/gV/4MVsy/MU9uwTak= | ||||
| github.com/dsoprea/go-utility v0.0.0-20200512094054-1abbbc781176 h1:CfXezFYb2STGOd1+n1HshvE191zVx+QX3A1nML5xxME= | ||||
| github.com/dsoprea/go-png-image-structure v0.0.0-20210428043356-45b892641b59 h1:4CJr4z+gM6jmak9k6vzMWwj+cM8jYSFje+AxTDns1PA= | ||||
| github.com/dsoprea/go-png-image-structure v0.0.0-20210428043356-45b892641b59/go.mod h1:aDYQkL/5gfRNZkoxiLTSWU4Y8/gV/4MVsy/MU9uwTak= | ||||
| github.com/dsoprea/go-utility v0.0.0-20200512094054-1abbbc781176/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= | ||||
| github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8= | ||||
| github.com/dsoprea/go-utility v0.0.0-20200717064901-2fccff4aa15e h1:ojqYA1mU6LuRm8XzrVOvyfb000y59cbUcu6Wt8sFSAs= | ||||
| github.com/dsoprea/go-utility v0.0.0-20200717064901-2fccff4aa15e/go.mod h1:KVK+/Hul09ujXAGq+42UBgCTnXkiJZRnLYdURGjQUwo= | ||||
| github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||
| github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | ||||
| github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= | ||||
|  | @ -48,26 +57,30 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo | |||
| github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= | ||||
| github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8= | ||||
| github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= | ||||
| github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA= | ||||
| github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk= | ||||
| github.com/gin-contrib/sessions v0.0.3 h1:PoBXki+44XdJdlgDqDrY5nDVe3Wk7wDV/UCOuLP6fBI= | ||||
| github.com/gin-contrib/sessions v0.0.3/go.mod h1:8C/J6cad3Il1mWYYgtw0w+hqasmpvy25mPkXdOgeB9I= | ||||
| github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= | ||||
| github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= | ||||
| github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= | ||||
| github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= | ||||
| github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= | ||||
| github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8= | ||||
| github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= | ||||
| github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= | ||||
| github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= | ||||
| github.com/go-errors/errors v1.0.2 h1:xMxH9j2fNg/L4hLn/4y3M0IUsn0M6Wbu/Uh9QlOfBh4= | ||||
| github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= | ||||
| github.com/go-fed/activity v1.0.0 h1:j7w3auHZnVCjUcgA1mE+UqSOjFBhvW2Z2res3vNol+o= | ||||
| github.com/go-fed/activity v1.0.0/go.mod h1:v4QoPaAzjWZ8zN2VFVGL5ep9C02mst0hQYHUpQwso4Q= | ||||
| github.com/go-fed/httpsig v0.1.1-0.20190914113940-c2de3672e5b5 h1:WLvFZqoXnuVTBKA6U/1FnEHNQ0Rq0QM0rGhY8Tx6R1g= | ||||
| github.com/go-errors/errors v1.2.0 h1:g5NHvR3mlTvaIa23r4xj7JAHlIhdVhOK8rEOGauEMCY= | ||||
| github.com/go-errors/errors v1.2.0/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= | ||||
| github.com/go-fed/activity v1.0.1-0.20210426194615-e0de0863dcc1 h1:go9MogQW0eTLwdOs/ZfNCGpwUkVcr7IMUbI3u8wYQxw= | ||||
| github.com/go-fed/activity v1.0.1-0.20210426194615-e0de0863dcc1/go.mod h1:v4QoPaAzjWZ8zN2VFVGL5ep9C02mst0hQYHUpQwso4Q= | ||||
| github.com/go-fed/httpsig v0.1.1-0.20190914113940-c2de3672e5b5/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= | ||||
| github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= | ||||
| github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= | ||||
| github.com/go-pg/pg/extra/pgdebug v0.2.0 h1:t62UhMiV6KYAxSWojwIJiyX06TdepkzCeIzdeb00184= | ||||
| github.com/go-pg/pg/extra/pgdebug v0.2.0/go.mod h1:KmW//PLshMAQunfInLv9mFIbYXuGplOY9bc6qo3CaY0= | ||||
| github.com/go-pg/pg/v10 v10.6.2/go.mod h1:BfgPoQnD2wXNd986RYEHzikqv9iE875PrFaZ9vXvtNM= | ||||
| github.com/go-pg/pg/v10 v10.8.0 h1:7L1VmOwW/VMmPtz5K3TWWMdM68MDgRs8Yb3c3NTMNgI= | ||||
| github.com/go-pg/pg/v10 v10.8.0/go.mod h1:0ZZA18+5xlUPvKjlDxoMyU79ZSuJtI+EeM2/GEd4RVo= | ||||
| github.com/go-pg/pg/v10 v10.9.1 h1:kU4t84zWGGaU0Qsu49FbNtToUVrlSTkNOngW8aQmwvk= | ||||
| github.com/go-pg/pg/v10 v10.9.1/go.mod h1:rgmTPgHgl5EN2CNKKoMwC7QT62t8BqsdpEkUQuiZMQs= | ||||
| github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= | ||||
| github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= | ||||
| github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= | ||||
|  | @ -78,20 +91,23 @@ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTM | |||
| github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= | ||||
| github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= | ||||
| github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= | ||||
| github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= | ||||
| github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= | ||||
| github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= | ||||
| github.com/go-playground/validator/v10 v10.6.0 h1:UGIt4xR++fD9QrBOoo/ascJfGe3AGHEB9s6COnss4Rk= | ||||
| github.com/go-playground/validator/v10 v10.6.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= | ||||
| github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= | ||||
| github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= | ||||
| github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | ||||
| github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= | ||||
| github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= | ||||
| github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= | ||||
| github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc= | ||||
| github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= | ||||
| github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= | ||||
| github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= | ||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||
| github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||||
| github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||||
| github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= | ||||
| github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= | ||||
| github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= | ||||
| github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= | ||||
| github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
|  | @ -104,8 +120,10 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W | |||
| github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= | ||||
| 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.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= | ||||
| github.com/golang/protobuf v1.4.3/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/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= | ||||
| github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | ||||
| github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||
|  | @ -113,8 +131,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw | |||
| github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= | ||||
| github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= | ||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= | ||||
| github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= | ||||
| github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||
|  | @ -128,8 +146,9 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51 | |||
| github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= | ||||
| github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= | ||||
| github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= | ||||
| github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= | ||||
| github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= | ||||
| github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= | ||||
| github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= | ||||
| github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= | ||||
| github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||
| github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4= | ||||
|  | @ -141,8 +160,9 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS | |||
| github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= | ||||
| github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= | ||||
| github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||||
| github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= | ||||
| github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||||
| github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= | ||||
| github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||||
| github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | ||||
| github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | ||||
| github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= | ||||
|  | @ -157,10 +177,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |||
| github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= | ||||
| github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= | ||||
| github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= | ||||
| github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g= | ||||
| github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= | ||||
| github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= | ||||
| github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= | ||||
| github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | ||||
| github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= | ||||
| github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= | ||||
|  | @ -177,39 +196,42 @@ github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOA | |||
| github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= | ||||
| github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= | ||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||||
| github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= | ||||
| github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= | ||||
| github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= | ||||
| github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= | ||||
| github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||
| github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= | ||||
| github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= | ||||
| github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= | ||||
| github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= | ||||
| github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= | ||||
| github.com/onsi/ginkgo v1.16.2 h1:HFB2fbVIlhIfCfOW81bZFbiC/RvnpXSdhbF2/DJr134= | ||||
| github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= | ||||
| github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= | ||||
| github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= | ||||
| github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= | ||||
| github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= | ||||
| github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= | ||||
| github.com/onsi/gomega v1.12.0 h1:p4oGGk2M2UJc0wWN4lHFvIB71lxsh0T/UiKCCgFADY8= | ||||
| github.com/onsi/gomega v1.12.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= | ||||
| 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438 h1:jnz/4VenymvySjE+Ez511s0pqVzkUOmr1fwCVytNNWk= | ||||
| github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= | ||||
| github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= | ||||
| github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc= | ||||
| github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= | ||||
| github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||
| github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= | ||||
| github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||
| github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= | ||||
| github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= | ||||
| github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= | ||||
| github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= | ||||
| github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= | ||||
| github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU= | ||||
| github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= | ||||
| github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= | ||||
| github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | ||||
| github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= | ||||
| github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | ||||
| github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= | ||||
| github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | ||||
| github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= | ||||
| github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= | ||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||
|  | @ -222,26 +244,25 @@ github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203/go | |||
| github.com/superseriousbusiness/oauth2/v4 v4.2.1-0.20210327102222-902aba1ef45f h1:0YcjA/ieDuDFHJPg5w2hk3r5kIWNvEyl7GsoArxdI3s= | ||||
| github.com/superseriousbusiness/oauth2/v4 v4.2.1-0.20210327102222-902aba1ef45f/go.mod h1:8p0a/BEN9hhsGzE3tPaFFlIZgxAaLyLN5KY0bPg9ZBc= | ||||
| github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= | ||||
| github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= | ||||
| github.com/tidwall/btree v0.4.2 h1:aLwwJlG+InuFzdAPuBf9YCAR1LvSQ9zhC5aorFPlIPs= | ||||
| github.com/tidwall/btree v0.4.2/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= | ||||
| github.com/tidwall/btree v0.5.0 h1:IBfCtOj4uOMQcodv3wzYVo0zPqSJObm71mE039/dlXY= | ||||
| github.com/tidwall/btree v0.5.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4= | ||||
| github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI= | ||||
| github.com/tidwall/buntdb v1.2.0 h1:8KOzf5Gg97DoCMSOgcwZjnM0FfROtq0fcZkPW54oGKU= | ||||
| github.com/tidwall/buntdb v1.2.0/go.mod h1:XLza/dhlwzO6dc5o/KWor4kfZSt3BP8QV+77ZMKfI58= | ||||
| github.com/tidwall/buntdb v1.2.3 h1:AoGVe4yrhKmnEPHrPrW5EUOATHOCIk4VtFvd8xn/ZtU= | ||||
| github.com/tidwall/buntdb v1.2.3/go.mod h1:+i/gBwYOHWG19wLgwMXFLkl00twh9+VWkkaOhuNQ4PA= | ||||
| github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= | ||||
| github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= | ||||
| github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= | ||||
| github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w= | ||||
| github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= | ||||
| github.com/tidwall/gjson v1.7.4 h1:19cchw8FOxkG5mdLRkGf9jqIqEyqdZhPqW60XfyFxk8= | ||||
| github.com/tidwall/gjson v1.7.4/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= | ||||
| github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= | ||||
| github.com/tidwall/grect v0.1.0 h1:ICcKWD5uu5A5fmxApGIa0QRvfGnSWKRd07POT08CQSA= | ||||
| github.com/tidwall/grect v0.1.0/go.mod h1:sa5O42oP6jWfTShL9ka6Sgmg3TgIK649veZe05B7+J8= | ||||
| github.com/tidwall/grect v0.1.1 h1:+kMEkxhoqB7rniVXzMEIA66XwU07STgINqxh+qVIndY= | ||||
| github.com/tidwall/grect v0.1.1/go.mod h1:CzvbGiFbWUwiJ1JohXLb28McpyBsI00TK9Y6pDWLGRQ= | ||||
| github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8= | ||||
| github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= | ||||
| github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= | ||||
| github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= | ||||
| github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= | ||||
| github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= | ||||
| github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= | ||||
| github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8= | ||||
| github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= | ||||
| github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= | ||||
|  | @ -252,10 +273,12 @@ github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaym | |||
| github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= | ||||
| github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= | ||||
| github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= | ||||
| github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= | ||||
| github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= | ||||
| github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= | ||||
| github.com/ugorji/go v1.2.5 h1:NozRHfUeEta89taVkyfsDVSy2f7v89Frft4pjnWuGuc= | ||||
| github.com/ugorji/go v1.2.5/go.mod h1:gat2tIT8KJG8TVI8yv77nEO/KYT6dV7JE1gfUa8Xuls= | ||||
| github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= | ||||
| github.com/ugorji/go/codec v1.2.5 h1:8WobZKAk18Msm2CothY2jnztY56YVY8kF1oQrj21iis= | ||||
| github.com/ugorji/go/codec v1.2.5/go.mod h1:QPxoTbPKSEAlAHPYt02++xp/en9B/wUdwFCz+hj5caA= | ||||
| github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= | ||||
| github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= | ||||
| github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||||
|  | @ -267,8 +290,9 @@ github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6cz | |||
| github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= | ||||
| github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= | ||||
| github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1/go.mod h1:xlngVLeyQ/Qi05oQxhQ+oTuqa03RjMwMfk/7/TCs+QI= | ||||
| github.com/vmihailenco/msgpack/v5 v5.2.0 h1:ZhIAtVUP1mme8GIlpiAnmTzjSWMexA/uNF2We85DR0w= | ||||
| github.com/vmihailenco/msgpack/v5 v5.2.0/go.mod h1:fEM7KuHcnm0GvDCztRpw9hV0PuoO2ciTismP6vjggcM= | ||||
| github.com/vmihailenco/msgpack/v5 v5.3.0/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= | ||||
| github.com/vmihailenco/msgpack/v5 v5.3.1 h1:0i85a4dsZh8mC//wmyyTEzidDLPQfQAxZIOLtafGbFY= | ||||
| github.com/vmihailenco/msgpack/v5 v5.3.1/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= | ||||
| github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= | ||||
| github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= | ||||
| github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= | ||||
|  | @ -291,23 +315,27 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDf | |||
| github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= | ||||
| github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= | ||||
| go.opentelemetry.io/otel v0.18.0 h1:d5Of7+Zw4ANFOJB+TIn2K3QWsgS2Ht7OU9DqZHI6qu8= | ||||
| go.opentelemetry.io/otel v0.18.0/go.mod h1:PT5zQj4lTsR1YeARt8YNKcFb88/c2IKoSABK9mX0r78= | ||||
| go.opentelemetry.io/otel/metric v0.18.0 h1:yuZCmY9e1ZTaMlZXLrrbAPmYW6tW1A5ozOZeOYGaTaY= | ||||
| go.opentelemetry.io/otel/metric v0.18.0/go.mod h1:kEH2QtzAyBy3xDVQfGZKIcok4ZZFvd5xyKPfPcuK6pE= | ||||
| go.opentelemetry.io/otel/oteltest v0.18.0 h1:FbKDFm/LnQDOHuGjED+fy3s5YMVg0z019GJ9Er66hYo= | ||||
| go.opentelemetry.io/otel/oteltest v0.18.0/go.mod h1:NyierCU3/G8DLTva7KRzGii2fdxdR89zXKH1bNWY7Bo= | ||||
| go.opentelemetry.io/otel/trace v0.18.0 h1:ilCfc/fptVKaDMK1vWk0elxpolurJbEgey9J6g6s+wk= | ||||
| go.opentelemetry.io/otel/trace v0.18.0/go.mod h1:FzdUu3BPwZSZebfQ1vl5/tAa8LyMLXSJN57AXIt/iDk= | ||||
| go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg= | ||||
| go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g= | ||||
| go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= | ||||
| go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc= | ||||
| go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8= | ||||
| go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= | ||||
| go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA= | ||||
| go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw= | ||||
| go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= | ||||
| go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg= | ||||
| go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw= | ||||
| go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= | ||||
| golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= | ||||
| golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as= | ||||
| golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | ||||
| golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | ||||
| golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= | ||||
| golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
| golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | ||||
|  | @ -333,10 +361,10 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R | |||
| golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= | ||||
| golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||
| golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= | ||||
| golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= | ||||
| golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
|  | @ -361,20 +389,22 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w | |||
| golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210305034016-7844c3c200c3 h1:RdE7htvBru4I4VZQofQjCZk5W9+aLNlSF5n0zgVwm8s= | ||||
| golang.org/x/sys v0.0.0-20210305034016-7844c3c200c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= | ||||
| golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q= | ||||
| golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||||
| golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= | ||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= | ||||
| golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||||
| golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||||
| golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||
| golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||
|  | @ -401,8 +431,10 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi | |||
| google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||
| google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||
| google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||
| google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= | ||||
| google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= | ||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||
| google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= | ||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
|  | @ -419,11 +451,13 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | |||
| gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= | ||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||||
| gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= | ||||
| gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w= | ||||
|  |  | |||
|  | @ -28,6 +28,7 @@ import ( | |||
| 	"github.com/stretchr/testify/suite" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/pg" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
|  | @ -103,7 +104,7 @@ func (suite *AuthTestSuite) SetupTest() { | |||
| 
 | ||||
| 	log := logrus.New() | ||||
| 	log.SetLevel(logrus.TraceLevel) | ||||
| 	db, err := db.NewPostgresService(context.Background(), suite.config, log) | ||||
| 	db, err := pg.NewPostgresService(context.Background(), suite.config, log) | ||||
| 	if err != nil { | ||||
| 		logrus.Panicf("error creating database connection: %s", err) | ||||
| 	} | ||||
|  |  | |||
|  | @ -33,7 +33,7 @@ func (m *Module) OauthTokenMiddleware(c *gin.Context) { | |||
| 	l := m.log.WithField("func", "OauthTokenMiddleware") | ||||
| 	l.Trace("entering OauthTokenMiddleware") | ||||
| 
 | ||||
| 	ti, err := m.server.ValidationBearerToken(c.Request) | ||||
| 	ti, err := m.server.ValidationBearerToken(c.Copy().Request) | ||||
| 	if err != nil { | ||||
| 		l.Tracef("could not validate token: %s", err) | ||||
| 		return | ||||
|  | @ -74,4 +74,5 @@ func (m *Module) OauthTokenMiddleware(c *gin.Context) { | |||
| 		c.Set(oauth.SessionAuthorizedApplication, app) | ||||
| 		l.Tracef("set gin context %s to %+v", oauth.SessionAuthorizedApplication, app) | ||||
| 	} | ||||
| 	c.Next() | ||||
| } | ||||
|  |  | |||
|  | @ -20,16 +20,46 @@ package auth | |||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 
 | ||||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
| 
 | ||||
| type tokenBody struct { | ||||
| 	ClientID     *string `form:"client_id" json:"client_id" xml:"client_id"` | ||||
| 	ClientSecret *string `form:"client_secret" json:"client_secret" xml:"client_secret"` | ||||
| 	Code         *string `form:"code" json:"code" xml:"code"` | ||||
| 	GrantType    *string `form:"grant_type" json:"grant_type" xml:"grant_type"` | ||||
| 	RedirectURI  *string `form:"redirect_uri" json:"redirect_uri" xml:"redirect_uri"` | ||||
| } | ||||
| 
 | ||||
| // TokenPOSTHandler should be served as a POST at https://example.org/oauth/token | ||||
| // The idea here is to serve an oauth access token to a user, which can be used for authorizing against non-public APIs. | ||||
| // See https://docs.joinmastodon.org/methods/apps/oauth/#obtain-a-token | ||||
| func (m *Module) TokenPOSTHandler(c *gin.Context) { | ||||
| 	l := m.log.WithField("func", "TokenPOSTHandler") | ||||
| 	l.Trace("entered TokenPOSTHandler") | ||||
| 
 | ||||
| 	form := &tokenBody{} | ||||
| 	if err := c.ShouldBind(form); err == nil { | ||||
| 		c.Request.Form = url.Values{} | ||||
| 		if form.ClientID != nil { | ||||
| 			c.Request.Form.Set("client_id", *form.ClientID) | ||||
| 		} | ||||
| 		if form.ClientSecret != nil { | ||||
| 			c.Request.Form.Set("client_secret", *form.ClientSecret) | ||||
| 		} | ||||
| 		if form.Code != nil { | ||||
| 			c.Request.Form.Set("code", *form.Code) | ||||
| 		} | ||||
| 		if form.GrantType != nil { | ||||
| 			c.Request.Form.Set("grant_type", *form.GrantType) | ||||
| 		} | ||||
| 		if form.RedirectURI != nil { | ||||
| 			c.Request.Form.Set("redirect_uri", *form.RedirectURI) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if err := m.server.HandleTokenRequest(c.Writer, c.Request); err != nil { | ||||
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||||
| 	} | ||||
|  |  | |||
							
								
								
									
										57
									
								
								internal/api/client/followrequest/accept.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								internal/api/client/followrequest/accept.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,57 @@ | |||
| /* | ||||
|    GoToSocial | ||||
|    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||
| 
 | ||||
|    This program is free software: you can redistribute it and/or modify | ||||
|    it under the terms of the GNU Affero General Public License as published by | ||||
|    the Free Software Foundation, either version 3 of the License, or | ||||
|    (at your option) any later version. | ||||
| 
 | ||||
|    This program is distributed in the hope that it will be useful, | ||||
|    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|    GNU Affero General Public License for more details. | ||||
| 
 | ||||
|    You should have received a copy of the GNU Affero General Public License | ||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package followrequest | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| ) | ||||
| 
 | ||||
| // FollowRequestAcceptPOSTHandler deals with follow request accepting. It should be served at | ||||
| // /api/v1/follow_requests/:id/authorize | ||||
| func (m *Module) FollowRequestAcceptPOSTHandler(c *gin.Context) { | ||||
| 	l := m.log.WithField("func", "statusCreatePOSTHandler") | ||||
| 	authed, err := oauth.Authed(c, true, true, true, true) | ||||
| 	if err != nil { | ||||
| 		l.Debugf("couldn't auth: %s", err) | ||||
| 		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() { | ||||
| 		l.Debugf("couldn't auth: %s", err) | ||||
| 		c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	originAccountID := c.Param(IDKey) | ||||
| 	if originAccountID == "" { | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "no follow request origin account id provided"}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if errWithCode := m.processor.FollowRequestAccept(authed, originAccountID); errWithCode != nil { | ||||
| 		l.Debug(errWithCode.Error()) | ||||
| 		c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) | ||||
| 		return | ||||
| 	} | ||||
| 	c.Status(http.StatusOK) | ||||
| } | ||||
|  | @ -16,6 +16,12 @@ | |||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package db_test | ||||
| package followrequest | ||||
| 
 | ||||
| // TODO: write tests for postgres | ||||
| import "github.com/gin-gonic/gin" | ||||
| 
 | ||||
| // FollowRequestDenyPOSTHandler deals with follow request rejection. It should be served at | ||||
| // /api/v1/follow_requests/:id/reject | ||||
| func (m *Module) FollowRequestDenyPOSTHandler(c *gin.Context) { | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										68
									
								
								internal/api/client/followrequest/followrequest.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								internal/api/client/followrequest/followrequest.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | |||
| /* | ||||
|    GoToSocial | ||||
|    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||
| 
 | ||||
|    This program is free software: you can redistribute it and/or modify | ||||
|    it under the terms of the GNU Affero General Public License as published by | ||||
|    the Free Software Foundation, either version 3 of the License, or | ||||
|    (at your option) any later version. | ||||
| 
 | ||||
|    This program is distributed in the hope that it will be useful, | ||||
|    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|    GNU Affero General Public License for more details. | ||||
| 
 | ||||
|    You should have received a copy of the GNU Affero General Public License | ||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package followrequest | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/message" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/router" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// IDKey is for status UUIDs | ||||
| 	IDKey = "id" | ||||
| 	// BasePath is the base path for serving the follow request API | ||||
| 	BasePath = "/api/v1/follow_requests" | ||||
| 	// BasePathWithID is just the base path with the ID key in it. | ||||
| 	// Use this anywhere you need to know the ID of the follow request being queried. | ||||
| 	BasePathWithID = BasePath + "/:" + IDKey | ||||
| 
 | ||||
| 	// AcceptPath is used for accepting follow requests | ||||
| 	AcceptPath = BasePathWithID + "/authorize" | ||||
| 	// DenyPath is used for denying follow requests | ||||
| 	DenyPath = BasePathWithID + "/reject" | ||||
| ) | ||||
| 
 | ||||
| // Module implements the ClientAPIModule interface for every related to interacting with follow requests | ||||
| type Module struct { | ||||
| 	config    *config.Config | ||||
| 	processor message.Processor | ||||
| 	log       *logrus.Logger | ||||
| } | ||||
| 
 | ||||
| // New returns a new follow request module | ||||
| func New(config *config.Config, processor message.Processor, log *logrus.Logger) api.ClientModule { | ||||
| 	return &Module{ | ||||
| 		config:    config, | ||||
| 		processor: processor, | ||||
| 		log:       log, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Route attaches all routes from this module to the given router | ||||
| func (m *Module) Route(r router.Router) error { | ||||
| 	r.AttachHandler(http.MethodGet, BasePath, m.FollowRequestGETHandler) | ||||
| 	r.AttachHandler(http.MethodPost, AcceptPath, m.FollowRequestAcceptPOSTHandler) | ||||
| 	r.AttachHandler(http.MethodPost, DenyPath, m.FollowRequestDenyPOSTHandler) | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										51
									
								
								internal/api/client/followrequest/get.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								internal/api/client/followrequest/get.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| /* | ||||
|    GoToSocial | ||||
|    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||
| 
 | ||||
|    This program is free software: you can redistribute it and/or modify | ||||
|    it under the terms of the GNU Affero General Public License as published by | ||||
|    the Free Software Foundation, either version 3 of the License, or | ||||
|    (at your option) any later version. | ||||
| 
 | ||||
|    This program is distributed in the hope that it will be useful, | ||||
|    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|    GNU Affero General Public License for more details. | ||||
| 
 | ||||
|    You should have received a copy of the GNU Affero General Public License | ||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package followrequest | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| ) | ||||
| 
 | ||||
| // FollowRequestGETHandler allows clients to get a list of their incoming follow requests. | ||||
| func (m *Module) FollowRequestGETHandler(c *gin.Context) { | ||||
| 	l := m.log.WithField("func", "statusCreatePOSTHandler") | ||||
| 	authed, err := oauth.Authed(c, true, true, true, true) | ||||
| 	if err != nil { | ||||
| 		l.Debugf("couldn't auth: %s", err) | ||||
| 		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() { | ||||
| 		l.Debugf("couldn't auth: %s", err) | ||||
| 		c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	accts, errWithCode := m.processor.FollowRequestsGet(authed) | ||||
| 	if errWithCode != nil { | ||||
| 		c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	c.JSON(http.StatusOK, accts) | ||||
| } | ||||
|  | @ -11,7 +11,7 @@ import ( | |||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// InstanceInformationPath | ||||
| 	// InstanceInformationPath is for serving instance info requests | ||||
| 	InstanceInformationPath = "api/v1/instance" | ||||
| ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import ( | |||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
| 
 | ||||
| // InstanceInformationGETHandler is for serving instance information at /api/v1/instance | ||||
| func (m *Module) InstanceInformationGETHandler(c *gin.Context) { | ||||
| 	l := m.log.WithField("func", "InstanceInformationGETHandler") | ||||
| 
 | ||||
|  |  | |||
|  | @ -33,8 +33,10 @@ import ( | |||
| 
 | ||||
| // BasePath is the base API path for making media requests | ||||
| const BasePath = "/api/v1/media" | ||||
| 
 | ||||
| // IDKey is the key for media attachment IDs | ||||
| const IDKey = "id" | ||||
| 
 | ||||
| // BasePathWithID corresponds to a media attachment with the given ID | ||||
| const BasePathWithID = BasePath + "/:" + IDKey | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,30 +35,32 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) { | |||
| 	authed, err := oauth.Authed(c, true, true, true, true) // posting new media is serious business so we want *everything* | ||||
| 	if err != nil { | ||||
| 		l.Debugf("couldn't auth: %s", err) | ||||
| 		c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) | ||||
| 		c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// extract the media create form from the request context | ||||
| 	l.Tracef("parsing request form: %s", c.Request.Form) | ||||
| 	var form model.AttachmentRequest | ||||
| 	form := &model.AttachmentRequest{} | ||||
| 	if err := c.ShouldBind(&form); err != nil { | ||||
| 		l.Debugf("could not parse form from request: %s", err) | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "missing one or more required form values"}) | ||||
| 		l.Debugf("error parsing form: %s", err) | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("could not parse form: %s", err)}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Give the fields on the request form a first pass to make sure the request is superficially valid. | ||||
| 	l.Tracef("validating form %+v", form) | ||||
| 	if err := validateCreateMedia(&form, m.config.MediaConfig); err != nil { | ||||
| 	if err := validateCreateMedia(form, m.config.MediaConfig); err != nil { | ||||
| 		l.Debugf("error validating form: %s", err) | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||||
| 		c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	mastoAttachment, err := m.processor.MediaCreate(authed, &form) | ||||
| 	l.Debug("calling processor media create func") | ||||
| 	mastoAttachment, err := m.processor.MediaCreate(authed, form) | ||||
| 	if err != nil { | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||||
| 		l.Debugf("error creating attachment: %s", err) | ||||
| 		c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
|  | @ -67,7 +69,7 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) { | |||
| 
 | ||||
| func validateCreateMedia(form *model.AttachmentRequest, config *config.MediaConfig) error { | ||||
| 	// check there actually is a file attached and it's not size 0 | ||||
| 	if form.File == nil || form.File.Size == 0 { | ||||
| 	if form.File == nil { | ||||
| 		return errors.New("no attachment given") | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -43,7 +43,7 @@ func (m *Module) MediaGETHandler(c *gin.Context) { | |||
| 
 | ||||
| 	attachment, errWithCode := m.processor.MediaGet(authed, attachmentID) | ||||
| 	if errWithCode != nil { | ||||
| 		c.JSON(errWithCode.Code(),gin.H{"error":  errWithCode.Safe()}) | ||||
| 		c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -43,13 +43,13 @@ type Application struct { | |||
| // And here: https://docs.joinmastodon.org/client/token/ | ||||
| type ApplicationCreateRequest struct { | ||||
| 	// A name for your application | ||||
| 	ClientName string `form:"client_name" binding:"required"` | ||||
| 	ClientName string `form:"client_name" json:"client_name" xml:"client_name" binding:"required"` | ||||
| 	// Where the user should be redirected after authorization. | ||||
| 	// To display the authorization code to the user instead of redirecting | ||||
| 	// to a web page, use urn:ietf:wg:oauth:2.0:oob in this parameter. | ||||
| 	RedirectURIs string `form:"redirect_uris" binding:"required"` | ||||
| 	RedirectURIs string `form:"redirect_uris" json:"redirect_uris" xml:"redirect_uris" binding:"required"` | ||||
| 	// Space separated list of scopes. If none is provided, defaults to read. | ||||
| 	Scopes string `form:"scopes"` | ||||
| 	Scopes string `form:"scopes" json:"scopes" xml:"scopes"` | ||||
| 	// A URL to the homepage of your app | ||||
| 	Website string `form:"website"` | ||||
| 	Website string `form:"website" json:"website" xml:"website"` | ||||
| } | ||||
|  |  | |||
|  | @ -24,15 +24,15 @@ import "mime/multipart" | |||
| // See: https://docs.joinmastodon.org/methods/statuses/media/ | ||||
| type AttachmentRequest struct { | ||||
| 	File        *multipart.FileHeader `form:"file" binding:"required"` | ||||
| 	Description string                `form:"description" json:"description" xml:"description"` | ||||
| 	Focus       string                `form:"focus" json:"focus" xml:"focus"` | ||||
| 	Description string                `form:"description"` | ||||
| 	Focus       string                `form:"focus"` | ||||
| } | ||||
| 
 | ||||
| // AttachmentRequest represents the form data parameters submitted by a client during a media update/PUT request. | ||||
| // AttachmentUpdateRequest represents the form data parameters submitted by a client during a media update/PUT request. | ||||
| // See: https://docs.joinmastodon.org/methods/statuses/media/ | ||||
| type AttachmentUpdateRequest struct { | ||||
| 	Description *string                `form:"description" json:"description" xml:"description"` | ||||
| 	Focus       *string                `form:"focus" json:"focus" xml:"focus"` | ||||
| 	Description *string `form:"description" json:"description" xml:"description"` | ||||
| 	Focus       *string `form:"focus" json:"focus" xml:"focus"` | ||||
| } | ||||
| 
 | ||||
| // Attachment represents the object returned to a client after a successful media upload request. | ||||
|  | @ -63,7 +63,7 @@ type Attachment struct { | |||
| 	// See https://docs.joinmastodon.org/methods/statuses/media/#focal-points points for more. | ||||
| 	Meta MediaMeta `json:"meta,omitempty"` | ||||
| 	// Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load. | ||||
| 	Description string `json:"description"` | ||||
| 	Description string `json:"description,omitempty"` | ||||
| 	// A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet. | ||||
| 	// See https://github.com/woltapp/blurhash | ||||
| 	Blurhash string `json:"blurhash,omitempty"` | ||||
|  |  | |||
|  | @ -119,11 +119,15 @@ const ( | |||
| 	VisibilityDirect Visibility = "direct" | ||||
| ) | ||||
| 
 | ||||
| // AdvancedStatusCreateForm wraps the mastodon status create form along with the GTS advanced | ||||
| // visibility settings. | ||||
| type AdvancedStatusCreateForm struct { | ||||
| 	StatusCreateRequest | ||||
| 	AdvancedVisibilityFlagsForm | ||||
| } | ||||
| 
 | ||||
| // AdvancedVisibilityFlagsForm allows a few more advanced flags to be set on new statuses, in addition | ||||
| // to the standard mastodon-compatible ones. | ||||
| type AdvancedVisibilityFlagsForm struct { | ||||
| 	// The gotosocial visibility model | ||||
| 	VisibilityAdvanced *string `form:"visibility_advanced"` | ||||
|  |  | |||
							
								
								
									
										58
									
								
								internal/api/s2s/user/inboxpost.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								internal/api/s2s/user/inboxpost.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,58 @@ | |||
| /* | ||||
|    GoToSocial | ||||
|    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||
| 
 | ||||
|    This program is free software: you can redistribute it and/or modify | ||||
|    it under the terms of the GNU Affero General Public License as published by | ||||
|    the Free Software Foundation, either version 3 of the License, or | ||||
|    (at your option) any later version. | ||||
| 
 | ||||
|    This program is distributed in the hope that it will be useful, | ||||
|    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|    GNU Affero General Public License for more details. | ||||
| 
 | ||||
|    You should have received a copy of the GNU Affero General Public License | ||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package user | ||||
| 
 | ||||
| import ( | ||||
| 	"net/http" | ||||
| 
 | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/message" | ||||
| ) | ||||
| 
 | ||||
| // InboxPOSTHandler deals with incoming POST requests to an actor's inbox. | ||||
| // Eg., POST to https://example.org/users/whatever/inbox. | ||||
| func (m *Module) InboxPOSTHandler(c *gin.Context) { | ||||
| 	l := m.log.WithFields(logrus.Fields{ | ||||
| 		"func": "InboxPOSTHandler", | ||||
| 		"url":  c.Request.RequestURI, | ||||
| 	}) | ||||
| 
 | ||||
| 	requestedUsername := c.Param(UsernameKey) | ||||
| 	if requestedUsername == "" { | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	posted, err := m.processor.InboxPost(c.Request.Context(), c.Writer, c.Request) | ||||
| 	if err != nil { | ||||
| 		if withCode, ok := err.(message.ErrorWithCode); ok { | ||||
| 			l.Debug(withCode.Error()) | ||||
| 			c.JSON(withCode.Code(), withCode.Safe()) | ||||
| 			return | ||||
| 		} | ||||
| 		l.Debug(err) | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if !posted { | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "unable to process request"}) | ||||
| 	} | ||||
| } | ||||
|  | @ -38,6 +38,8 @@ const ( | |||
| 	// Use this anywhere you need to know the username of the user being queried. | ||||
| 	// Eg https://example.org/users/:username | ||||
| 	UsersBasePathWithUsername = UsersBasePath + "/:" + UsernameKey | ||||
| 	// UsersInboxPath is for serving POST requests to a user's inbox with the given username key. | ||||
| 	UsersInboxPath = UsersBasePathWithUsername + "/" + util.InboxPath | ||||
| ) | ||||
| 
 | ||||
| // ActivityPubAcceptHeaders represents the Accept headers mentioned here: | ||||
|  | @ -66,5 +68,6 @@ func New(config *config.Config, processor message.Processor, log *logrus.Logger) | |||
| // Route satisfies the RESTAPIModule interface | ||||
| func (m *Module) Route(s router.Router) error { | ||||
| 	s.AttachHandler(http.MethodGet, UsersBasePathWithUsername, m.UsersGETHandler) | ||||
| 	s.AttachHandler(http.MethodPost, UsersInboxPath, m.InboxPOSTHandler) | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ import ( | |||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// The base path for serving webfinger lookup requests | ||||
| 	// WebfingerBasePath is the base path for serving webfinger lookup requests | ||||
| 	WebfingerBasePath = ".well-known/webfinger" | ||||
| ) | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										8
									
								
								internal/api/security/extraheaders.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								internal/api/security/extraheaders.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| package security | ||||
| 
 | ||||
| import "github.com/gin-gonic/gin" | ||||
| 
 | ||||
| // ExtraHeaders adds any additional required headers to the response | ||||
| func (m *Module) ExtraHeaders(c *gin.Context) { | ||||
| 	c.Header("Server", "Mastodon") | ||||
| } | ||||
|  | @ -42,5 +42,6 @@ func New(config *config.Config, log *logrus.Logger) api.ClientModule { | |||
| // Route attaches security middleware to the given router | ||||
| func (m *Module) Route(s router.Router) error { | ||||
| 	s.AttachMiddleware(m.FlocBlock) | ||||
| 	s.AttachMiddleware(m.ExtraHeaders) | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
|  | @ -26,7 +26,10 @@ import ( | |||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| ) | ||||
| 
 | ||||
| const DBTypePostgres string = "POSTGRES" | ||||
| const ( | ||||
| 	// DBTypePostgres represents an underlying POSTGRES database type. | ||||
| 	DBTypePostgres string = "POSTGRES" | ||||
| ) | ||||
| 
 | ||||
| // ErrNoEntries is to be returned from the DB interface when no entries are found for a given query. | ||||
| type ErrNoEntries struct{} | ||||
|  | @ -112,6 +115,10 @@ type DB interface { | |||
| 		HANDY SHORTCUTS | ||||
| 	*/ | ||||
| 
 | ||||
| 	// AcceptFollowRequest moves a follow request in the database from the follow_requests table to the follows table. | ||||
| 	// In other words, it should create the follow, and delete the existing follow request. | ||||
| 	AcceptFollowRequest(originAccountID string, targetAccountID string) error | ||||
| 
 | ||||
| 	// CreateInstanceAccount creates an account in the database with the same username as the instance host value. | ||||
| 	// Ie., if the instance is hosted at 'example.org' the instance user will have a username of 'example.org'. | ||||
| 	// This is needed for things like serving files that belong to the instance and not an individual user/account. | ||||
|  | @ -148,6 +155,11 @@ type DB interface { | |||
| 	// In case of no entries, a 'no entries' error will be returned | ||||
| 	GetFollowersByAccountID(accountID string, followers *[]gtsmodel.Follow) error | ||||
| 
 | ||||
| 	// GetFavesByAccountID is a shortcut for the common action of fetching a list of faves made by the given accountID. | ||||
| 	// The given slice 'faves' will be set to the result of the query, whatever it is. | ||||
| 	// In case of no entries, a 'no entries' error will be returned | ||||
| 	GetFavesByAccountID(accountID string, faves *[]gtsmodel.StatusFave) error | ||||
| 
 | ||||
| 	// GetStatusesByAccountID is a shortcut for the common action of fetching a list of statuses produced by accountID. | ||||
| 	// The given slice 'statuses' will be set to the result of the query, whatever it is. | ||||
| 	// In case of no entries, a 'no entries' error will be returned | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package db | ||||
| package pg | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
|  | @ -37,6 +37,8 @@ import ( | |||
| 	"github.com/google/uuid" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
|  | @ -53,7 +55,7 @@ type postgresService struct { | |||
| 
 | ||||
| // NewPostgresService returns a postgresService derived from the provided config, which implements the go-fed DB interface. | ||||
| // Under the hood, it uses https://github.com/go-pg/pg to create and maintain a database connection. | ||||
| func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logger) (DB, error) { | ||||
| func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logger) (db.DB, error) { | ||||
| 	opts, err := derivePGOptions(c) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("could not create postgres service: %s", err) | ||||
|  | @ -95,7 +97,7 @@ func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logge | |||
| 		cancel: cancel, | ||||
| 	} | ||||
| 
 | ||||
| 	federatingDB := NewFederatingDB(ps, c, log) | ||||
| 	federatingDB := federation.NewFederatingDB(ps, c, log) | ||||
| 	ps.federationDB = federatingDB | ||||
| 
 | ||||
| 	// we can confidently return this useable postgres service now | ||||
|  | @ -109,8 +111,8 @@ func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logge | |||
| // derivePGOptions takes an application config and returns either a ready-to-use *pg.Options | ||||
| // with sensible defaults, or an error if it's not satisfied by the provided config. | ||||
| func derivePGOptions(c *config.Config) (*pg.Options, error) { | ||||
| 	if strings.ToUpper(c.DBConfig.Type) != DBTypePostgres { | ||||
| 		return nil, fmt.Errorf("expected db type of %s but got %s", DBTypePostgres, c.DBConfig.Type) | ||||
| 	if strings.ToUpper(c.DBConfig.Type) != db.DBTypePostgres { | ||||
| 		return nil, fmt.Errorf("expected db type of %s but got %s", db.DBTypePostgres, c.DBConfig.Type) | ||||
| 	} | ||||
| 
 | ||||
| 	// validate port | ||||
|  | @ -219,7 +221,7 @@ func (ps *postgresService) CreateSchema(ctx context.Context) error { | |||
| func (ps *postgresService) GetByID(id string, i interface{}) error { | ||||
| 	if err := ps.conn.Model(i).Where("id = ?", id).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 
 | ||||
|  | @ -230,7 +232,7 @@ func (ps *postgresService) GetByID(id string, i interface{}) error { | |||
| func (ps *postgresService) GetWhere(key string, value interface{}, i interface{}) error { | ||||
| 	if err := ps.conn.Model(i).Where("? = ?", pg.Safe(key), value).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -244,7 +246,7 @@ func (ps *postgresService) GetWhere(key string, value interface{}, i interface{} | |||
| func (ps *postgresService) GetAll(i interface{}) error { | ||||
| 	if err := ps.conn.Model(i).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -259,7 +261,7 @@ func (ps *postgresService) Put(i interface{}) error { | |||
| func (ps *postgresService) Upsert(i interface{}, conflictColumn string) error { | ||||
| 	if _, err := ps.conn.Model(i).OnConflict(fmt.Sprintf("(%s) DO UPDATE", conflictColumn)).Insert(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -269,7 +271,7 @@ func (ps *postgresService) Upsert(i interface{}, conflictColumn string) error { | |||
| func (ps *postgresService) UpdateByID(id string, i interface{}) error { | ||||
| 	if _, err := ps.conn.Model(i).Where("id = ?", id).OnConflict("(id) DO UPDATE").Insert(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -284,7 +286,7 @@ func (ps *postgresService) UpdateOneByID(id string, key string, value interface{ | |||
| func (ps *postgresService) DeleteByID(id string, i interface{}) error { | ||||
| 	if _, err := ps.conn.Model(i).Where("id = ?", id).Delete(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -294,7 +296,7 @@ func (ps *postgresService) DeleteByID(id string, i interface{}) error { | |||
| func (ps *postgresService) DeleteWhere(key string, value interface{}, i interface{}) error { | ||||
| 	if _, err := ps.conn.Model(i).Where("? = ?", pg.Safe(key), value).Delete(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -305,6 +307,32 @@ func (ps *postgresService) DeleteWhere(key string, value interface{}, i interfac | |||
| 	HANDY SHORTCUTS | ||||
| */ | ||||
| 
 | ||||
| func (ps *postgresService) AcceptFollowRequest(originAccountID string, targetAccountID string) error { | ||||
| 	fr := >smodel.FollowRequest{} | ||||
| 	if err := ps.conn.Model(fr).Where("account_id = ?", originAccountID).Where("target_account_id = ?", targetAccountID).Select(); err != nil { | ||||
| 		if err == pg.ErrMultiRows { | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	follow := >smodel.Follow{ | ||||
| 		AccountID:       originAccountID, | ||||
| 		TargetAccountID: targetAccountID, | ||||
| 		URI:             fr.URI, | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err := ps.conn.Model(follow).Insert(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if _, err := ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", originAccountID).Where("target_account_id = ?", targetAccountID).Delete(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (ps *postgresService) CreateInstanceAccount() error { | ||||
| 	username := ps.config.Host | ||||
| 	key, err := rsa.GenerateKey(rand.Reader, 2048) | ||||
|  | @ -365,13 +393,13 @@ func (ps *postgresService) GetAccountByUserID(userID string, account *gtsmodel.A | |||
| 	} | ||||
| 	if err := ps.conn.Model(user).Where("id = ?", userID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := ps.conn.Model(account).Where("id = ?", user.AccountID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -381,7 +409,7 @@ func (ps *postgresService) GetAccountByUserID(userID string, account *gtsmodel.A | |||
| func (ps *postgresService) GetLocalAccountByUsername(username string, account *gtsmodel.Account) error { | ||||
| 	if err := ps.conn.Model(account).Where("username = ?", username).Where("? IS NULL", pg.Ident("domain")).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -391,7 +419,7 @@ func (ps *postgresService) GetLocalAccountByUsername(username string, account *g | |||
| func (ps *postgresService) GetFollowRequestsForAccountID(accountID string, followRequests *[]gtsmodel.FollowRequest) error { | ||||
| 	if err := ps.conn.Model(followRequests).Where("target_account_id = ?", accountID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return nil | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -401,7 +429,7 @@ func (ps *postgresService) GetFollowRequestsForAccountID(accountID string, follo | |||
| func (ps *postgresService) GetFollowingByAccountID(accountID string, following *[]gtsmodel.Follow) error { | ||||
| 	if err := ps.conn.Model(following).Where("account_id = ?", accountID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return nil | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -411,7 +439,17 @@ func (ps *postgresService) GetFollowingByAccountID(accountID string, following * | |||
| func (ps *postgresService) GetFollowersByAccountID(accountID string, followers *[]gtsmodel.Follow) error { | ||||
| 	if err := ps.conn.Model(followers).Where("target_account_id = ?", accountID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return nil | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (ps *postgresService) GetFavesByAccountID(accountID string, faves *[]gtsmodel.StatusFave) error { | ||||
| 	if err := ps.conn.Model(faves).Where("account_id = ?", accountID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return nil | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -421,7 +459,7 @@ func (ps *postgresService) GetFollowersByAccountID(accountID string, followers * | |||
| func (ps *postgresService) GetStatusesByAccountID(accountID string, statuses *[]gtsmodel.Status) error { | ||||
| 	if err := ps.conn.Model(statuses).Where("account_id = ?", accountID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -438,7 +476,7 @@ func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuse | |||
| 	} | ||||
| 	if err := q.Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -448,7 +486,7 @@ func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuse | |||
| func (ps *postgresService) GetLastStatusForAccountID(accountID string, status *gtsmodel.Status) error { | ||||
| 	if err := ps.conn.Model(status).Order("created_at DESC").Limit(1).Where("account_id = ?", accountID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -574,18 +612,18 @@ func (ps *postgresService) GetHeaderForAccountID(header *gtsmodel.MediaAttachmen | |||
| 	acct := >smodel.Account{} | ||||
| 	if err := ps.conn.Model(acct).Where("id = ?", accountID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if acct.HeaderMediaAttachmentID == "" { | ||||
| 		return ErrNoEntries{} | ||||
| 		return db.ErrNoEntries{} | ||||
| 	} | ||||
| 
 | ||||
| 	if err := ps.conn.Model(header).Where("id = ?", acct.HeaderMediaAttachmentID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -596,18 +634,18 @@ func (ps *postgresService) GetAvatarForAccountID(avatar *gtsmodel.MediaAttachmen | |||
| 	acct := >smodel.Account{} | ||||
| 	if err := ps.conn.Model(acct).Where("id = ?", accountID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if acct.AvatarMediaAttachmentID == "" { | ||||
| 		return ErrNoEntries{} | ||||
| 		return db.ErrNoEntries{} | ||||
| 	} | ||||
| 
 | ||||
| 	if err := ps.conn.Model(avatar).Where("id = ?", acct.AvatarMediaAttachmentID).Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return ErrNoEntries{} | ||||
| 			return db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -645,7 +683,7 @@ func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAc | |||
| 	if err := ps.conn.Model(targetUser).Where("account_id = ?", targetAccount.ID).Select(); err != nil { | ||||
| 		l.Debug("target user could not be selected") | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			return false, ErrNoEntries{} | ||||
| 			return false, db.ErrNoEntries{} | ||||
| 		} | ||||
| 		return false, err | ||||
| 	} | ||||
|  | @ -37,6 +37,7 @@ func (c *Clock) Now() time.Time { | |||
| 	return time.Now() | ||||
| } | ||||
| 
 | ||||
| // NewClock returns a simple pub.Clock for use in federation interfaces. | ||||
| func NewClock() pub.Clock { | ||||
| 	return &Clock{} | ||||
| } | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ import ( | |||
| // authenticated must be true and error nil. The request will continue | ||||
| // to be processed. | ||||
| func (f *federator) AuthenticateGetInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) { | ||||
| 	// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through | ||||
| 	// IMPLEMENTATION NOTE: For GoToSocial, we serve GETS to outboxes and inboxes through | ||||
| 	// the CLIENT API, not through the federation API, so we just do nothing here. | ||||
| 	return nil, false, nil | ||||
| } | ||||
|  | @ -82,7 +82,7 @@ func (f *federator) AuthenticateGetInbox(ctx context.Context, w http.ResponseWri | |||
| // authenticated must be true and error nil. The request will continue | ||||
| // to be processed. | ||||
| func (f *federator) AuthenticateGetOutbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) { | ||||
| 	// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through | ||||
| 	// IMPLEMENTATION NOTE: For GoToSocial, we serve GETS to outboxes and inboxes through | ||||
| 	// the CLIENT API, not through the federation API, so we just do nothing here. | ||||
| 	return nil, false, nil | ||||
| } | ||||
|  | @ -96,7 +96,7 @@ func (f *federator) AuthenticateGetOutbox(ctx context.Context, w http.ResponseWr | |||
| // Always called, regardless whether the Federated Protocol or Social | ||||
| // API is enabled. | ||||
| func (f *federator) GetOutbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) { | ||||
| 	// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through | ||||
| 	// IMPLEMENTATION NOTE: For GoToSocial, we serve GETS to outboxes and inboxes through | ||||
| 	// the CLIENT API, not through the federation API, so we just do nothing here. | ||||
| 	return nil, nil | ||||
| } | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package db | ||||
| package federation | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
|  | @ -26,28 +26,35 @@ import ( | |||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/go-fed/activity/pub" | ||||
| 	"github.com/go-fed/activity/streams" | ||||
| 	"github.com/go-fed/activity/streams/vocab" | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| ) | ||||
| 
 | ||||
| // FederatingDB uses the underlying DB interface to implement the go-fed pub.Database interface. | ||||
| // It doesn't care what the underlying implementation of the DB interface is, as long as it works. | ||||
| type federatingDB struct { | ||||
| 	locks  *sync.Map | ||||
| 	db     DB | ||||
| 	config *config.Config | ||||
| 	log    *logrus.Logger | ||||
| 	locks         *sync.Map | ||||
| 	db            db.DB | ||||
| 	config        *config.Config | ||||
| 	log           *logrus.Logger | ||||
| 	typeConverter typeutils.TypeConverter | ||||
| } | ||||
| 
 | ||||
| func NewFederatingDB(db DB, config *config.Config, log *logrus.Logger) pub.Database { | ||||
| // NewFederatingDB returns a pub.Database interface using the given database, config, and logger. | ||||
| func NewFederatingDB(db db.DB, config *config.Config, log *logrus.Logger) pub.Database { | ||||
| 	return &federatingDB{ | ||||
| 		locks:  new(sync.Map), | ||||
| 		db:     db, | ||||
| 		config: config, | ||||
| 		log:    log, | ||||
| 		locks:         new(sync.Map), | ||||
| 		db:            db, | ||||
| 		config:        config, | ||||
| 		log:           log, | ||||
| 		typeConverter: typeutils.NewConverter(config, db), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -104,30 +111,42 @@ func (f *federatingDB) Unlock(c context.Context, id *url.URL) error { | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (contains bool, err error) { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func": "InboxContains", | ||||
| 			"id":   id.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("entering INBOXCONTAINS function with for inbox %s and id %s", inbox.String(), id.String()) | ||||
| 
 | ||||
| 	if !util.IsInboxPath(inbox) { | ||||
| 		return false, fmt.Errorf("%s is not an inbox URI", inbox.String()) | ||||
| 	} | ||||
| 
 | ||||
| 	if !util.IsStatusesPath(id) { | ||||
| 		return false, fmt.Errorf("%s is not a status URI", id.String()) | ||||
| 	activityI := c.Value(util.APActivity) | ||||
| 	if activityI == nil { | ||||
| 		return false, fmt.Errorf("no activity was set for id %s", id.String()) | ||||
| 	} | ||||
| 	_, statusID, err := util.ParseStatusesPath(inbox) | ||||
| 	if err != nil { | ||||
| 		return false, fmt.Errorf("status URI %s was not parseable: %s", id.String(), err) | ||||
| 	activity, ok := activityI.(pub.Activity) | ||||
| 	if !ok || activity == nil { | ||||
| 		return false, fmt.Errorf("could not parse contextual activity for id %s", id.String()) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := f.db.GetByID(statusID, >smodel.Status{}); err != nil { | ||||
| 		if _, ok := err.(ErrNoEntries); ok { | ||||
| 			// we don't have it | ||||
| 			return false, nil | ||||
| 		} | ||||
| 		// actual error | ||||
| 		return false, fmt.Errorf("error getting status from db: %s", err) | ||||
| 	} | ||||
| 	l.Debugf("activity type %s for id %s", activity.GetTypeName(), id.String()) | ||||
| 
 | ||||
| 	// we must have it | ||||
| 	return true, nil | ||||
| 	return false, nil | ||||
| 
 | ||||
| 	// if err := f.db.GetByID(statusID, >smodel.Status{}); err != nil { | ||||
| 	// 	if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 	// 		// we don't have it | ||||
| 	// 		return false, nil | ||||
| 	// 	} | ||||
| 	// 	// actual error | ||||
| 	// 	return false, fmt.Errorf("error getting status from db: %s", err) | ||||
| 	// } | ||||
| 
 | ||||
| 	// // we must have it | ||||
| 	// return true, nil | ||||
| } | ||||
| 
 | ||||
| // GetInbox returns the first ordered collection page of the outbox at | ||||
|  | @ -135,7 +154,13 @@ func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (con | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) GetInbox(c context.Context, inboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error) { | ||||
| 	return nil, nil | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func": "GetInbox", | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("entering GETINBOX function with inboxIRI %s", inboxIRI.String()) | ||||
| 	return streams.NewActivityStreamsOrderedCollectionPage(), nil | ||||
| } | ||||
| 
 | ||||
| // SetInbox saves the inbox value given from GetInbox, with new items | ||||
|  | @ -144,6 +169,12 @@ func (f *federatingDB) GetInbox(c context.Context, inboxIRI *url.URL) (inbox voc | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) SetInbox(c context.Context, inbox vocab.ActivityStreamsOrderedCollectionPage) error { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func": "SetInbox", | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debug("entering SETINBOX function") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -151,12 +182,21 @@ func (f *federatingDB) SetInbox(c context.Context, inbox vocab.ActivityStreamsOr | |||
| // the database has an entry for the IRI. | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func": "Owns", | ||||
| 			"id":   id.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("entering OWNS function with id %s", id.String()) | ||||
| 
 | ||||
| 	// if the id host isn't this instance host, we don't own this IRI | ||||
| 	if id.Host != f.config.Host { | ||||
| 		l.Debugf("we DO NOT own activity because the host is %s not %s", id.Host, f.config.Host) | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// apparently we own it, so what *is* it? | ||||
| 	// apparently it belongs to this host, so what *is* it? | ||||
| 
 | ||||
| 	// check if it's a status, eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS | ||||
| 	if util.IsStatusesPath(id) { | ||||
|  | @ -165,13 +205,14 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) { | |||
| 			return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err) | ||||
| 		} | ||||
| 		if err := f.db.GetWhere("uri", uid, >smodel.Status{}); err != nil { | ||||
| 			if _, ok := err.(ErrNoEntries); ok { | ||||
| 			if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 				// there are no entries for this status | ||||
| 				return false, nil | ||||
| 			} | ||||
| 			// an actual error happened | ||||
| 			return false, fmt.Errorf("database error fetching status with id %s: %s", uid, err) | ||||
| 		} | ||||
| 		l.Debug("we DO own this") | ||||
| 		return true, nil | ||||
| 	} | ||||
| 
 | ||||
|  | @ -182,13 +223,14 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) { | |||
| 			return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err) | ||||
| 		} | ||||
| 		if err := f.db.GetLocalAccountByUsername(username, >smodel.Account{}); err != nil { | ||||
| 			if _, ok := err.(ErrNoEntries); ok { | ||||
| 			if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 				// there are no entries for this username | ||||
| 				return false, nil | ||||
| 			} | ||||
| 			// an actual error happened | ||||
| 			return false, fmt.Errorf("database error fetching account with username %s: %s", username, err) | ||||
| 		} | ||||
| 		l.Debug("we DO own this") | ||||
| 		return true, nil | ||||
| 	} | ||||
| 
 | ||||
|  | @ -199,12 +241,20 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) { | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) ActorForOutbox(c context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func":     "ActorForOutbox", | ||||
| 			"inboxIRI": outboxIRI.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("entering ACTORFOROUTBOX function with outboxIRI %s", outboxIRI.String()) | ||||
| 
 | ||||
| 	if !util.IsOutboxPath(outboxIRI) { | ||||
| 		return nil, fmt.Errorf("%s is not an outbox URI", outboxIRI.String()) | ||||
| 	} | ||||
| 	acct := >smodel.Account{} | ||||
| 	if err := f.db.GetWhere("outbox_uri", outboxIRI.String(), acct); err != nil { | ||||
| 		if _, ok := err.(ErrNoEntries); ok { | ||||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			return nil, fmt.Errorf("no actor found that corresponds to outbox %s", outboxIRI.String()) | ||||
| 		} | ||||
| 		return nil, fmt.Errorf("db error searching for actor with outbox %s", outboxIRI.String()) | ||||
|  | @ -216,12 +266,20 @@ func (f *federatingDB) ActorForOutbox(c context.Context, outboxIRI *url.URL) (ac | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func":     "ActorForInbox", | ||||
| 			"inboxIRI": inboxIRI.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("entering ACTORFORINBOX function with inboxIRI %s", inboxIRI.String()) | ||||
| 
 | ||||
| 	if !util.IsInboxPath(inboxIRI) { | ||||
| 		return nil, fmt.Errorf("%s is not an inbox URI", inboxIRI.String()) | ||||
| 	} | ||||
| 	acct := >smodel.Account{} | ||||
| 	if err := f.db.GetWhere("inbox_uri", inboxIRI.String(), acct); err != nil { | ||||
| 		if _, ok := err.(ErrNoEntries); ok { | ||||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String()) | ||||
| 		} | ||||
| 		return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String()) | ||||
|  | @ -234,12 +292,20 @@ func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (acto | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) OutboxForInbox(c context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func":     "OutboxForInbox", | ||||
| 			"inboxIRI": inboxIRI.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("entering OUTBOXFORINBOX function with inboxIRI %s", inboxIRI.String()) | ||||
| 
 | ||||
| 	if !util.IsInboxPath(inboxIRI) { | ||||
| 		return nil, fmt.Errorf("%s is not an inbox URI", inboxIRI.String()) | ||||
| 	} | ||||
| 	acct := >smodel.Account{} | ||||
| 	if err := f.db.GetWhere("inbox_uri", inboxIRI.String(), acct); err != nil { | ||||
| 		if _, ok := err.(ErrNoEntries); ok { | ||||
| 		if _, ok := err.(db.ErrNoEntries); ok { | ||||
| 			return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String()) | ||||
| 		} | ||||
| 		return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String()) | ||||
|  | @ -252,6 +318,14 @@ func (f *federatingDB) OutboxForInbox(c context.Context, inboxIRI *url.URL) (out | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) Exists(c context.Context, id *url.URL) (exists bool, err error) { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func": "Exists", | ||||
| 			"id":   id.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("entering EXISTS function with id %s", id.String()) | ||||
| 
 | ||||
| 	return false, nil | ||||
| } | ||||
| 
 | ||||
|  | @ -259,6 +333,22 @@ func (f *federatingDB) Exists(c context.Context, id *url.URL) (exists bool, err | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) Get(c context.Context, id *url.URL) (value vocab.Type, err error) { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func": "Get", | ||||
| 			"id":   id.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debug("entering GET function") | ||||
| 
 | ||||
| 	if util.IsUserPath(id) { | ||||
| 		acct := >smodel.Account{} | ||||
| 		if err := f.db.GetWhere("uri", id.String(), acct); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return f.typeConverter.AccountToAS(acct) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil, nil | ||||
| } | ||||
| 
 | ||||
|  | @ -275,6 +365,49 @@ func (f *federatingDB) Get(c context.Context, id *url.URL) (value vocab.Type, er | |||
| // Under certain conditions and network activities, Create may be called | ||||
| // multiple times for the same ActivityStreams object. | ||||
| func (f *federatingDB) Create(c context.Context, asType vocab.Type) error { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func":   "Create", | ||||
| 			"asType": asType.GetTypeName(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("received CREATE asType %+v", asType) | ||||
| 
 | ||||
| 	switch gtsmodel.ActivityStreamsActivity(asType.GetTypeName()) { | ||||
| 	case gtsmodel.ActivityStreamsCreate: | ||||
| 		create, ok := asType.(vocab.ActivityStreamsCreate) | ||||
| 		if !ok { | ||||
| 			return errors.New("could not convert type to create") | ||||
| 		} | ||||
| 		object := create.GetActivityStreamsObject() | ||||
| 		for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() { | ||||
| 			switch gtsmodel.ActivityStreamsObject(objectIter.GetType().GetTypeName()) { | ||||
| 			case gtsmodel.ActivityStreamsNote: | ||||
| 				note := objectIter.GetActivityStreamsNote() | ||||
| 				status, err := f.typeConverter.ASStatusToStatus(note) | ||||
| 				if err != nil { | ||||
| 					return fmt.Errorf("error converting note to status: %s", err) | ||||
| 				} | ||||
| 				if err := f.db.Put(status); err != nil { | ||||
| 					return fmt.Errorf("database error inserting status: %s", err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	case gtsmodel.ActivityStreamsFollow: | ||||
| 		follow, ok := asType.(vocab.ActivityStreamsFollow) | ||||
| 		if !ok { | ||||
| 			return errors.New("could not convert type to follow") | ||||
| 		} | ||||
| 
 | ||||
| 		followRequest, err := f.typeConverter.ASFollowToFollowRequest(follow) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("could not convert Follow to follow request: %s", err) | ||||
| 		} | ||||
| 
 | ||||
| 		if err := f.db.Put(followRequest); err != nil { | ||||
| 			return fmt.Errorf("database error inserting follow request: %s", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -288,6 +421,13 @@ func (f *federatingDB) Create(c context.Context, asType vocab.Type) error { | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) Update(c context.Context, asType vocab.Type) error { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func":   "Update", | ||||
| 			"asType": asType.GetTypeName(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("received UPDATE asType %+v", asType) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -298,6 +438,13 @@ func (f *federatingDB) Update(c context.Context, asType vocab.Type) error { | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) Delete(c context.Context, id *url.URL) error { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func": "Delete", | ||||
| 			"id":   id.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("received DELETE id %s", id.String()) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -306,6 +453,13 @@ func (f *federatingDB) Delete(c context.Context, id *url.URL) error { | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) GetOutbox(c context.Context, outboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error) { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func": "GetOutbox", | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debug("entering GETOUTBOX function") | ||||
| 
 | ||||
| 	return nil, nil | ||||
| } | ||||
| 
 | ||||
|  | @ -315,6 +469,13 @@ func (f *federatingDB) GetOutbox(c context.Context, outboxIRI *url.URL) (inbox v | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) SetOutbox(c context.Context, outbox vocab.ActivityStreamsOrderedCollectionPage) error { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func": "SetOutbox", | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debug("entering SETOUTBOX function") | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -325,7 +486,15 @@ func (f *federatingDB) SetOutbox(c context.Context, outbox vocab.ActivityStreams | |||
| // The go-fed library will handle setting the 'id' property on the | ||||
| // activity or object provided with the value returned. | ||||
| func (f *federatingDB) NewID(c context.Context, t vocab.Type) (id *url.URL, err error) { | ||||
| 	return nil, nil | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func":   "NewID", | ||||
| 			"asType": t.GetTypeName(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("received NEWID request for asType %+v", t) | ||||
| 
 | ||||
| 	return url.Parse(fmt.Sprintf("%s://%s/", f.config.Protocol, uuid.NewString())) | ||||
| } | ||||
| 
 | ||||
| // Followers obtains the Followers Collection for an actor with the | ||||
|  | @ -335,7 +504,39 @@ func (f *federatingDB) NewID(c context.Context, t vocab.Type) (id *url.URL, err | |||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) Followers(c context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) { | ||||
| 	return nil, nil | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func":     "Followers", | ||||
| 			"actorIRI": actorIRI.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("entering FOLLOWERS function with actorIRI %s", actorIRI.String()) | ||||
| 
 | ||||
| 	acct := >smodel.Account{} | ||||
| 	if err := f.db.GetWhere("uri", actorIRI.String(), acct); err != nil { | ||||
| 		return nil, fmt.Errorf("db error getting account with uri %s: %s", actorIRI.String(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	acctFollowers := []gtsmodel.Follow{} | ||||
| 	if err := f.db.GetFollowersByAccountID(acct.ID, &acctFollowers); err != nil { | ||||
| 		return nil, fmt.Errorf("db error getting followers for account id %s: %s", acct.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	followers = streams.NewActivityStreamsCollection() | ||||
| 	items := streams.NewActivityStreamsItemsProperty() | ||||
| 	for _, follow := range acctFollowers { | ||||
| 		gtsFollower := >smodel.Account{} | ||||
| 		if err := f.db.GetByID(follow.AccountID, gtsFollower); err != nil { | ||||
| 			return nil, fmt.Errorf("db error getting account id %s: %s", follow.AccountID, err) | ||||
| 		} | ||||
| 		uri, err := url.Parse(gtsFollower.URI) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("error parsing %s as url: %s", gtsFollower.URI, err) | ||||
| 		} | ||||
| 		items.AppendIRI(uri) | ||||
| 	} | ||||
| 	followers.SetActivityStreamsItems(items) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Following obtains the Following Collection for an actor with the | ||||
|  | @ -344,8 +545,40 @@ func (f *federatingDB) Followers(c context.Context, actorIRI *url.URL) (follower | |||
| // If modified, the library will then call Update. | ||||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) Following(c context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) { | ||||
| 	return nil, nil | ||||
| func (f *federatingDB) Following(c context.Context, actorIRI *url.URL) (following vocab.ActivityStreamsCollection, err error) { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func":     "Following", | ||||
| 			"actorIRI": actorIRI.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("entering FOLLOWING function with actorIRI %s", actorIRI.String()) | ||||
| 
 | ||||
| 	acct := >smodel.Account{} | ||||
| 	if err := f.db.GetWhere("uri", actorIRI.String(), acct); err != nil { | ||||
| 		return nil, fmt.Errorf("db error getting account with uri %s: %s", actorIRI.String(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	acctFollowing := []gtsmodel.Follow{} | ||||
| 	if err := f.db.GetFollowingByAccountID(acct.ID, &acctFollowing); err != nil { | ||||
| 		return nil, fmt.Errorf("db error getting following for account id %s: %s", acct.ID, err) | ||||
| 	} | ||||
| 
 | ||||
| 	following = streams.NewActivityStreamsCollection() | ||||
| 	items := streams.NewActivityStreamsItemsProperty() | ||||
| 	for _, follow := range acctFollowing { | ||||
| 		gtsFollowing := >smodel.Account{} | ||||
| 		if err := f.db.GetByID(follow.AccountID, gtsFollowing); err != nil { | ||||
| 			return nil, fmt.Errorf("db error getting account id %s: %s", follow.AccountID, err) | ||||
| 		} | ||||
| 		uri, err := url.Parse(gtsFollowing.URI) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("error parsing %s as url: %s", gtsFollowing.URI, err) | ||||
| 		} | ||||
| 		items.AppendIRI(uri) | ||||
| 	} | ||||
| 	following.SetActivityStreamsItems(items) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Liked obtains the Liked Collection for an actor with the | ||||
|  | @ -354,6 +587,13 @@ func (f *federatingDB) Following(c context.Context, actorIRI *url.URL) (follower | |||
| // If modified, the library will then call Update. | ||||
| // | ||||
| // The library makes this call only after acquiring a lock first. | ||||
| func (f *federatingDB) Liked(c context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) { | ||||
| func (f *federatingDB) Liked(c context.Context, actorIRI *url.URL) (liked vocab.ActivityStreamsCollection, err error) { | ||||
| 	l := f.log.WithFields( | ||||
| 		logrus.Fields{ | ||||
| 			"func":     "Liked", | ||||
| 			"actorIRI": actorIRI.String(), | ||||
| 		}, | ||||
| 	) | ||||
| 	l.Debugf("entering LIKED function with actorIRI %s", actorIRI.String()) | ||||
| 	return nil, nil | ||||
| } | ||||
|  | @ -16,6 +16,6 @@ | |||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package db | ||||
| package federation | ||||
| 
 | ||||
| // TODO: write tests for pgfed | ||||
|  | @ -77,6 +77,13 @@ func (f *federatingActor) PostInbox(c context.Context, w http.ResponseWriter, r | |||
| 	return f.actor.PostInbox(c, w, r) | ||||
| } | ||||
| 
 | ||||
| // PostInboxScheme is similar to PostInbox, except clients are able to | ||||
| // specify which protocol scheme to handle the incoming request and the | ||||
| // data stored within the application (HTTP, HTTPS, etc). | ||||
| func (f *federatingActor) PostInboxScheme(c context.Context, w http.ResponseWriter, r *http.Request, scheme string) (bool, error) { | ||||
| 	return f.actor.PostInboxScheme(c, w, r, scheme) | ||||
| } | ||||
| 
 | ||||
| // GetInbox returns true if the request was handled as an ActivityPub | ||||
| // GET to an actor's inbox. If false, the request was not an ActivityPub | ||||
| // request and may still be handled by the caller in another way, such | ||||
|  | @ -118,6 +125,13 @@ func (f *federatingActor) PostOutbox(c context.Context, w http.ResponseWriter, r | |||
| 	return f.actor.PostOutbox(c, w, r) | ||||
| } | ||||
| 
 | ||||
| // PostOutboxScheme is similar to PostOutbox, except clients are able to | ||||
| // specify which protocol scheme to handle the incoming request and the | ||||
| // data stored within the application (HTTP, HTTPS, etc). | ||||
| func (f *federatingActor) PostOutboxScheme(c context.Context, w http.ResponseWriter, r *http.Request, scheme string) (bool, error) { | ||||
| 	return f.actor.PostOutboxScheme(c, w, r, scheme) | ||||
| } | ||||
| 
 | ||||
| // GetOutbox returns true if the request was handled as an ActivityPub | ||||
| // GET to an actor's outbox. If false, the request was not an | ||||
| // ActivityPub request. | ||||
|  |  | |||
|  | @ -72,8 +72,49 @@ func (f *federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Reques | |||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	ctxWithActivity := context.WithValue(ctx, util.APActivity, activity) | ||||
| 	return ctxWithActivity, nil | ||||
| 	// derefence the actor of the activity already | ||||
| 	// var requestingActorIRI *url.URL | ||||
| 	// actorProp := activity.GetActivityStreamsActor() | ||||
| 	// if actorProp != nil { | ||||
| 	// 	for i := actorProp.Begin(); i != actorProp.End(); i = i.Next() { | ||||
| 	// 		if i.IsIRI() { | ||||
| 	// 			requestingActorIRI = i.GetIRI() | ||||
| 	// 			break | ||||
| 	// 		} | ||||
| 	// 	} | ||||
| 	// } | ||||
| 	// if requestingActorIRI != nil { | ||||
| 
 | ||||
| 	// 	requestedAccountI := ctx.Value(util.APAccount) | ||||
| 	// 	requestedAccount, ok := requestedAccountI.(*gtsmodel.Account) | ||||
| 	// 	if !ok { | ||||
| 	// 		return nil, errors.New("requested account was not set on request context") | ||||
| 	// 	} | ||||
| 
 | ||||
| 	// 	requestingActor := >smodel.Account{} | ||||
| 	// 	if err := f.db.GetWhere("uri", requestingActorIRI.String(), requestingActor); err != nil { | ||||
| 	// 		// there's been a proper error so return it | ||||
| 	// 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||
| 	// 			return nil, fmt.Errorf("error getting requesting actor with id %s: %s", requestingActorIRI.String(), err) | ||||
| 	// 		} | ||||
| 
 | ||||
| 	// 		// we don't know this account (yet) so let's dereference it right now | ||||
| 	// 		person, err := f.DereferenceRemoteAccount(requestedAccount.Username, publicKeyOwnerURI) | ||||
| 	// 		if err != nil { | ||||
| 	// 			return ctx, false, fmt.Errorf("error dereferencing account with public key id %s: %s", publicKeyOwnerURI.String(), err) | ||||
| 	// 		} | ||||
| 
 | ||||
| 	// 		a, err := f.typeConverter.ASRepresentationToAccount(person) | ||||
| 	// 		if err != nil { | ||||
| 	// 			return ctx, false, fmt.Errorf("error converting person with public key id %s to account: %s", publicKeyOwnerURI.String(), err) | ||||
| 	// 		} | ||||
| 	// 		requestingAccount = a | ||||
| 	// 	} | ||||
| 	// } | ||||
| 
 | ||||
| 	// set the activity on the context for use later on | ||||
| 
 | ||||
| 	return context.WithValue(ctx, util.APActivity, activity), nil | ||||
| } | ||||
| 
 | ||||
| // AuthenticatePostInbox delegates the authentication of a POST to an | ||||
|  | @ -100,14 +141,22 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr | |||
| 	}) | ||||
| 	l.Trace("received request to authenticate") | ||||
| 
 | ||||
| 	requestedAccountI := ctx.Value(util.APAccount) | ||||
| 	if requestedAccountI == nil { | ||||
| 		return ctx, false, errors.New("requested account not set in context") | ||||
| 	if !util.IsInboxPath(r.URL) { | ||||
| 		return nil, false, fmt.Errorf("path %s was not an inbox path", r.URL.String()) | ||||
| 	} | ||||
| 
 | ||||
| 	requestedAccount, ok := requestedAccountI.(*gtsmodel.Account) | ||||
| 	if !ok || requestedAccount == nil { | ||||
| 		return ctx, false, errors.New("requested account not parsebale from context") | ||||
| 	username, err := util.ParseInboxPath(r.URL) | ||||
| 	if err != nil { | ||||
| 		return nil, false, fmt.Errorf("could not parse path %s: %s", r.URL.String(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	if username == "" { | ||||
| 		return nil, false, errors.New("username was empty") | ||||
| 	} | ||||
| 
 | ||||
| 	requestedAccount := >smodel.Account{} | ||||
| 	if err := f.db.GetLocalAccountByUsername(username, requestedAccount); err != nil { | ||||
| 		return nil, false, fmt.Errorf("could not fetch requested account with username %s: %s", username, err) | ||||
| 	} | ||||
| 
 | ||||
| 	publicKeyOwnerURI, err := f.AuthenticateFederatedRequest(requestedAccount.Username, r) | ||||
|  | @ -124,7 +173,6 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr | |||
| 		} | ||||
| 
 | ||||
| 		// we don't know this account (yet) so let's dereference it right now | ||||
| 		// TODO: slow-fed | ||||
| 		person, err := f.DereferenceRemoteAccount(requestedAccount.Username, publicKeyOwnerURI) | ||||
| 		if err != nil { | ||||
| 			return ctx, false, fmt.Errorf("error dereferencing account with public key id %s: %s", publicKeyOwnerURI.String(), err) | ||||
|  | @ -134,12 +182,17 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr | |||
| 		if err != nil { | ||||
| 			return ctx, false, fmt.Errorf("error converting person with public key id %s to account: %s", publicKeyOwnerURI.String(), err) | ||||
| 		} | ||||
| 
 | ||||
| 		if err := f.db.Put(a); err != nil { | ||||
| 			l.Errorf("error inserting dereferenced remote account: %s", err) | ||||
| 		} | ||||
| 
 | ||||
| 		requestingAccount = a | ||||
| 	} | ||||
| 
 | ||||
| 	contextWithRequestingAccount := context.WithValue(ctx, util.APRequestingAccount, requestingAccount) | ||||
| 
 | ||||
| 	return contextWithRequestingAccount, true, nil | ||||
| 	withRequester := context.WithValue(ctx, util.APRequestingAccount, requestingAccount) | ||||
| 	withRequested := context.WithValue(withRequester, util.APAccount, requestedAccount) | ||||
| 	return withRequested, true, nil | ||||
| } | ||||
| 
 | ||||
| // Blocked should determine whether to permit a set of actors given by | ||||
|  | @ -156,8 +209,40 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr | |||
| // Finally, if the authentication and authorization succeeds, then | ||||
| // blocked must be false and error nil. The request will continue | ||||
| // to be processed. | ||||
| // | ||||
| // TODO: implement domain block checking here as well | ||||
| func (f *federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, error) { | ||||
| 	// TODO | ||||
| 	l := f.log.WithFields(logrus.Fields{ | ||||
| 		"func": "Blocked", | ||||
| 	}) | ||||
| 	l.Debugf("entering BLOCKED function with IRI list: %+v", actorIRIs) | ||||
| 
 | ||||
| 	requestedAccountI := ctx.Value(util.APAccount) | ||||
| 	requestedAccount, ok := requestedAccountI.(*gtsmodel.Account) | ||||
| 	if !ok { | ||||
| 		f.log.Errorf("requested account not set on request context") | ||||
| 		return false, errors.New("requested account not set on request context, so couldn't determine blocks") | ||||
| 	} | ||||
| 
 | ||||
| 	for _, uri := range actorIRIs { | ||||
| 		a := >smodel.Account{} | ||||
| 		if err := f.db.GetWhere("uri", uri.String(), a); err != nil { | ||||
| 			_, ok := err.(db.ErrNoEntries) | ||||
| 			if ok { | ||||
| 				// we don't have an entry for this account so it's not blocked | ||||
| 				// TODO: allow a different default to be set for this behavior | ||||
| 				continue | ||||
| 			} | ||||
| 			return false, fmt.Errorf("error getting account with uri %s: %s", uri.String(), err) | ||||
| 		} | ||||
| 		blocked, err := f.db.Blocked(requestedAccount.ID, a.ID) | ||||
| 		if err != nil { | ||||
| 			return false, fmt.Errorf("error checking account blocks: %s", err) | ||||
| 		} | ||||
| 		if blocked { | ||||
| 			return true, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return false, nil | ||||
| } | ||||
| 
 | ||||
|  | @ -180,9 +265,40 @@ func (f *federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er | |||
| // | ||||
| // Applications are not expected to handle every single ActivityStreams | ||||
| // type and extension. The unhandled ones are passed to DefaultCallback. | ||||
| func (f *federator) FederatingCallbacks(ctx context.Context) (pub.FederatingWrappedCallbacks, []interface{}, error) { | ||||
| 	// TODO | ||||
| 	return pub.FederatingWrappedCallbacks{}, nil, nil | ||||
| func (f *federator) FederatingCallbacks(ctx context.Context) (wrapped pub.FederatingWrappedCallbacks, other []interface{}, err error) { | ||||
| 	l := f.log.WithFields(logrus.Fields{ | ||||
| 		"func": "FederatingCallbacks", | ||||
| 	}) | ||||
| 
 | ||||
| 	targetAcctI := ctx.Value(util.APAccount) | ||||
| 	if targetAcctI == nil { | ||||
| 		l.Error("target account wasn't set on context") | ||||
| 	} | ||||
| 	targetAcct, ok := targetAcctI.(*gtsmodel.Account) | ||||
| 	if !ok { | ||||
| 		l.Error("target account was set on context but couldn't be parsed") | ||||
| 	} | ||||
| 
 | ||||
| 	var onFollow pub.OnFollowBehavior = pub.OnFollowAutomaticallyAccept | ||||
| 	if targetAcct.Locked { | ||||
| 		onFollow = pub.OnFollowDoNothing | ||||
| 	} | ||||
| 
 | ||||
| 	wrapped = pub.FederatingWrappedCallbacks{ | ||||
| 		// Follow handles additional side effects for the Follow ActivityStreams | ||||
| 		// type, specific to the application using go-fed. | ||||
| 		// | ||||
| 		// The wrapping function can have one of several default behaviors, | ||||
| 		// depending on the value of the OnFollow setting. | ||||
| 		Follow: func(context.Context, vocab.ActivityStreamsFollow) error { | ||||
| 			return nil | ||||
| 		}, | ||||
| 		// OnFollow determines what action to take for this particular callback | ||||
| 		// if a Follow Activity is handled. | ||||
| 		OnFollow: onFollow, | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // DefaultCallback is called for types that go-fed can deserialize but | ||||
|  | @ -207,7 +323,7 @@ func (f *federator) DefaultCallback(ctx context.Context, activity pub.Activity) | |||
| // Zero or negative numbers indicate infinite recursion. | ||||
| func (f *federator) MaxInboxForwardingRecursionDepth(ctx context.Context) int { | ||||
| 	// TODO | ||||
| 	return 0 | ||||
| 	return 4 | ||||
| } | ||||
| 
 | ||||
| // MaxDeliveryRecursionDepth determines how deep to search within | ||||
|  | @ -217,7 +333,7 @@ func (f *federator) MaxInboxForwardingRecursionDepth(ctx context.Context) int { | |||
| // Zero or negative numbers indicate infinite recursion. | ||||
| func (f *federator) MaxDeliveryRecursionDepth(ctx context.Context) int { | ||||
| 	// TODO | ||||
| 	return 0 | ||||
| 	return 4 | ||||
| } | ||||
| 
 | ||||
| // FilterForwarding allows the implementation to apply business logic | ||||
|  | @ -241,7 +357,7 @@ func (f *federator) FilterForwarding(ctx context.Context, potentialRecipients [] | |||
| // Always called, regardless whether the Federated Protocol or Social | ||||
| // API is enabled. | ||||
| func (f *federator) GetInbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) { | ||||
| 	// IMPLEMENTATION NOTE: For GoToSocial, we serve outboxes and inboxes through | ||||
| 	// IMPLEMENTATION NOTE: For GoToSocial, we serve GETS to outboxes and inboxes through | ||||
| 	// the CLIENT API, not through the federation API, so we just do nothing here. | ||||
| 	return nil, nil | ||||
| } | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ import ( | |||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/client/app" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/client/auth" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/client/instance" | ||||
| 	mediaModule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" | ||||
|  | @ -41,7 +42,7 @@ import ( | |||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/s2s/webfinger" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/security" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/pg" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||
|  | @ -78,7 +79,7 @@ var models []interface{} = []interface{}{ | |||
| 
 | ||||
| // Run creates and starts a gotosocial server | ||||
| var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { | ||||
| 	dbService, err := db.NewPostgresService(ctx, c, log) | ||||
| 	dbService, err := pg.NewPostgresService(ctx, c, log) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error creating dbservice: %s", err) | ||||
| 	} | ||||
|  | @ -111,6 +112,7 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr | |||
| 	accountModule := account.New(c, processor, log) | ||||
| 	instanceModule := instance.New(c, processor, log) | ||||
| 	appsModule := app.New(c, processor, log) | ||||
| 	followRequestsModule := followrequest.New(c, processor, log) | ||||
| 	webfingerModule := webfinger.New(c, processor, log) | ||||
| 	usersModule := user.New(c, processor, log) | ||||
| 	mm := mediaModule.New(c, processor, log) | ||||
|  | @ -128,6 +130,7 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr | |||
| 		accountModule, | ||||
| 		instanceModule, | ||||
| 		appsModule, | ||||
| 		followRequestsModule, | ||||
| 		mm, | ||||
| 		fileServerModule, | ||||
| 		adminModule, | ||||
|  |  | |||
|  | @ -76,15 +76,15 @@ type Account struct { | |||
| 	*/ | ||||
| 
 | ||||
| 	// Does this account need an approval for new followers? | ||||
| 	Locked bool `pg:",default:true"` | ||||
| 	Locked bool `pg:",default:'true'"` | ||||
| 	// Should this account be shown in the instance's profile directory? | ||||
| 	Discoverable bool | ||||
| 	// Default post privacy for this account | ||||
| 	Privacy Visibility | ||||
| 	// Set posts from this account to sensitive by default? | ||||
| 	Sensitive bool `pg:",default:false"` | ||||
| 	Sensitive bool `pg:",default:'false'"` | ||||
| 	// What language does this account post in? | ||||
| 	Language string `pg:",default:en"` | ||||
| 	Language string `pg:",default:'en'"` | ||||
| 
 | ||||
| 	/* | ||||
| 		ACTIVITYPUB THINGS | ||||
|  |  | |||
|  | @ -30,10 +30,22 @@ type Mention struct { | |||
| 	CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` | ||||
| 	// When was this mention last updated? | ||||
| 	UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` | ||||
| 	// Who created this mention? | ||||
| 	// What's the internal account ID of the originator of the mention? | ||||
| 	OriginAccountID string `pg:",notnull"` | ||||
| 	// Who does this mention target? | ||||
| 	// What's the AP URI of the originator of the mention? | ||||
| 	OriginAccountURI string `pg:",notnull"` | ||||
| 	// What's the internal account ID of the mention target? | ||||
| 	TargetAccountID string `pg:",notnull"` | ||||
| 	// Prevent this mention from generating a notification? | ||||
| 	Silent bool | ||||
| 	// NameString is for putting in the namestring of the mentioned user | ||||
| 	// before the mention is dereferenced. Should be in a form along the lines of: | ||||
| 	// @whatever_username@example.org | ||||
| 	// | ||||
| 	// This will not be put in the database, it's just for convenience. | ||||
| 	NameString string `pg:"-"` | ||||
| 	// MentionedAccountURI is the AP ID (uri) of the user mentioned. | ||||
| 	// | ||||
| 	// This will not be put in the database, it's just for convenience. | ||||
| 	MentionedAccountURI string `pg:"-"` | ||||
| } | ||||
|  |  | |||
|  | @ -71,12 +71,14 @@ type Status struct { | |||
| 	Text string | ||||
| 
 | ||||
| 	/* | ||||
| 		NON-DATABASE FIELDS | ||||
| 		INTERNAL MODEL NON-DATABASE FIELDS | ||||
| 
 | ||||
| 		These are for convenience while passing the status around internally, | ||||
| 		but these fields should *never* be put in the db. | ||||
| 	*/ | ||||
| 
 | ||||
| 	// Account that created this status | ||||
| 	GTSAccount *Account `pg:"-"` | ||||
| 	// Mentions created in this status | ||||
| 	GTSMentions []*Mention `pg:"-"` | ||||
| 	// Hashtags used in this status | ||||
|  | @ -93,6 +95,20 @@ type Status struct { | |||
| 	GTSBoostedStatus *Status `pg:"-"` | ||||
| 	// Account of the boosted status | ||||
| 	GTSBoostedAccount *Account `pg:"-"` | ||||
| 
 | ||||
| 	/* | ||||
| 		AP NON-DATABASE FIELDS | ||||
| 
 | ||||
| 		These are for convenience while passing the status around internally, | ||||
| 		but these fields should *never* be put in the db. | ||||
| 	*/ | ||||
| 
 | ||||
| 	// AP URI of the status being replied to. | ||||
| 	// Useful when that status doesn't exist in the database yet and we still need to dereference it. | ||||
| 	APReplyToStatusURI string `pg:"-"` | ||||
| 	// The AP URI of the owner/creator of the status. | ||||
| 	// Useful when that account doesn't exist in the database yet and we still need to dereference it. | ||||
| 	APStatusOwnerURI string `pg:"-"` | ||||
| } | ||||
| 
 | ||||
| // Visibility represents the visibility granularity of a status. | ||||
|  |  | |||
|  | @ -24,6 +24,8 @@ import "time" | |||
| type Tag struct { | ||||
| 	// id of this tag in the database | ||||
| 	ID string `pg:",unique,type:uuid,default:gen_random_uuid(),pk,notnull"` | ||||
| 	// Href of this tag, eg https://example.org/tags/somehashtag | ||||
| 	URL string | ||||
| 	// name of this tag -- the tag without the hash part | ||||
| 	Name string `pg:",unique,pk,notnull"` | ||||
| 	// Which account ID is the first one we saw using this tag? | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ import ( | |||
| 	"github.com/stretchr/testify/suite" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/pg" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/storage" | ||||
| ) | ||||
|  | @ -78,7 +79,7 @@ func (suite *MediaTestSuite) SetupSuite() { | |||
| 	} | ||||
| 	suite.config = c | ||||
| 	// use an actual database for this, because it's just easier than mocking one out | ||||
| 	database, err := db.NewPostgresService(context.Background(), c, log) | ||||
| 	database, err := pg.NewPostgresService(context.Background(), c, log) | ||||
| 	if err != nil { | ||||
| 		suite.FailNow(err.Error()) | ||||
| 	} | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| package message | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 
 | ||||
|  | @ -8,6 +9,7 @@ import ( | |||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| ) | ||||
| 
 | ||||
| // authenticateAndDereferenceFediRequest authenticates the HTTP signature of an incoming federation request, using the given | ||||
|  | @ -130,3 +132,9 @@ func (p *processor) GetWebfingerAccount(requestedUsername string, request *http. | |||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) InboxPost(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { | ||||
| 	contextWithChannel := context.WithValue(ctx, util.APFromFederatorChanKey, p.fromFederator) | ||||
| 	posted, err := p.federator.FederatingActor().PostInbox(contextWithChannel, w, r) | ||||
| 	return posted, err | ||||
| } | ||||
|  |  | |||
							
								
								
									
										42
									
								
								internal/message/frprocess.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								internal/message/frprocess.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| package message | ||||
| 
 | ||||
| import ( | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| ) | ||||
| 
 | ||||
| func (p *processor) FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, ErrorWithCode) { | ||||
| 	frs := []gtsmodel.FollowRequest{} | ||||
| 	if err := p.db.GetFollowRequestsForAccountID(auth.Account.ID, &frs); err != nil { | ||||
| 		if _, ok := err.(db.ErrNoEntries); !ok { | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	accts := []apimodel.Account{} | ||||
| 	for _, fr := range frs { | ||||
| 		acct := >smodel.Account{} | ||||
| 		if err := p.db.GetByID(fr.AccountID, acct); err != nil { | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 		} | ||||
| 		mastoAcct, err := p.tc.AccountToMastoPublic(acct) | ||||
| 		if err != nil { | ||||
| 			return nil, NewErrorInternalError(err) | ||||
| 		} | ||||
| 		accts = append(accts, *mastoAcct) | ||||
| 	} | ||||
| 	return accts, nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) ErrorWithCode { | ||||
| 	if err := p.db.AcceptFollowRequest(accountID, auth.Account.ID); err != nil { | ||||
| 		return NewErrorNotFound(err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (p *processor) FollowRequestDeny(auth *oauth.Auth) ErrorWithCode { | ||||
| 	return nil | ||||
| } | ||||
|  | @ -19,7 +19,11 @@ | |||
| package message | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 
 | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||
|  | @ -77,6 +81,11 @@ type Processor interface { | |||
| 	// FileGet handles the fetching of a media attachment file via the fileserver. | ||||
| 	FileGet(authed *oauth.Auth, form *apimodel.GetContentRequestForm) (*apimodel.Content, error) | ||||
| 
 | ||||
| 	// FollowRequestsGet handles the getting of the authed account's incoming follow requests | ||||
| 	FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, ErrorWithCode) | ||||
| 	// FollowRequestAccept handles the acceptance of a follow request from the given account ID | ||||
| 	FollowRequestAccept(auth *oauth.Auth, accountID string) ErrorWithCode | ||||
| 
 | ||||
| 	// InstanceGet retrieves instance information for serving at api/v1/instance | ||||
| 	InstanceGet(domain string) (*apimodel.Instance, ErrorWithCode) | ||||
| 
 | ||||
|  | @ -116,6 +125,18 @@ type Processor interface { | |||
| 
 | ||||
| 	// GetWebfingerAccount handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups. | ||||
| 	GetWebfingerAccount(requestedUsername string, request *http.Request) (*apimodel.WebfingerAccountResponse, ErrorWithCode) | ||||
| 
 | ||||
| 	// InboxPost handles POST requests to a user's inbox for new activitypub messages. | ||||
| 	// | ||||
| 	// InboxPost returns true if the request was handled as an ActivityPub POST to an actor's inbox. | ||||
| 	// If false, the request was not an ActivityPub request and may still be handled by the caller in another way, such as serving a web page. | ||||
| 	// | ||||
| 	// If the error is nil, then the ResponseWriter's headers and response has already been written. If a non-nil error is returned, then no response has been written. | ||||
| 	// | ||||
| 	// If the Actor was constructed with the Federated Protocol enabled, side effects will occur. | ||||
| 	// | ||||
| 	// If the Federated Protocol is not enabled, writes the http.StatusMethodNotAllowed status code in the response. No side effects occur. | ||||
| 	InboxPost(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) | ||||
| } | ||||
| 
 | ||||
| // processor just implements the Processor interface | ||||
|  | @ -181,6 +202,9 @@ func (p *processor) Start() error { | |||
| 				p.log.Infof("received message TO client API: %+v", clientMsg) | ||||
| 			case clientMsg := <-p.fromClientAPI: | ||||
| 				p.log.Infof("received message FROM client API: %+v", clientMsg) | ||||
| 				if err := p.processFromClientAPI(clientMsg); err != nil { | ||||
| 					p.log.Error(err) | ||||
| 				} | ||||
| 			case federatorMsg := <-p.toFederator: | ||||
| 				p.log.Infof("received message TO federator: %+v", federatorMsg) | ||||
| 			case federatorMsg := <-p.fromFederator: | ||||
|  | @ -227,3 +251,54 @@ type FromFederator struct { | |||
| 	APActivityType gtsmodel.ActivityStreamsActivity | ||||
| 	Activity       interface{} | ||||
| } | ||||
| 
 | ||||
| func (p *processor) processFromClientAPI(clientMsg FromClientAPI) error { | ||||
| 	switch clientMsg.APObjectType { | ||||
| 	case gtsmodel.ActivityStreamsNote: | ||||
| 		status, ok := clientMsg.Activity.(*gtsmodel.Status) | ||||
| 		if !ok { | ||||
| 			return errors.New("note was not parseable as *gtsmodel.Status") | ||||
| 		} | ||||
| 
 | ||||
| 		if err := p.notifyStatus(status); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		if status.VisibilityAdvanced.Federated { | ||||
| 			return p.federateStatus(status) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return fmt.Errorf("message type unprocessable: %+v", clientMsg) | ||||
| } | ||||
| 
 | ||||
| func (p *processor) federateStatus(status *gtsmodel.Status) error { | ||||
| 	// derive the sending account -- it might be attached to the status already | ||||
| 	sendingAcct := >smodel.Account{} | ||||
| 	if status.GTSAccount != nil { | ||||
| 		sendingAcct = status.GTSAccount | ||||
| 	} else { | ||||
| 		// it wasn't attached so get it from the db instead | ||||
| 		if err := p.db.GetByID(status.AccountID, sendingAcct); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	outboxURI, err := url.Parse(sendingAcct.OutboxURI) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// convert the status to AS format Note | ||||
| 	note, err := p.tc.StatusToAS(status) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = p.federator.FederatingActor().Send(context.Background(), outboxURI, note) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (p *processor) notifyStatus(status *gtsmodel.Status) error { | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
|  | @ -179,7 +179,7 @@ func (p *processor) processLanguage(form *apimodel.AdvancedStatusCreateForm, acc | |||
| 
 | ||||
| func (p *processor) processMentions(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error { | ||||
| 	menchies := []string{} | ||||
| 	gtsMenchies, err := p.db.MentionStringsToMentions(util.DeriveMentions(form.Status), accountID, status.ID) | ||||
| 	gtsMenchies, err := p.db.MentionStringsToMentions(util.DeriveMentionsFromStatus(form.Status), accountID, status.ID) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error generating mentions from status: %s", err) | ||||
| 	} | ||||
|  | @ -198,7 +198,7 @@ func (p *processor) processMentions(form *apimodel.AdvancedStatusCreateForm, acc | |||
| 
 | ||||
| func (p *processor) processTags(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error { | ||||
| 	tags := []string{} | ||||
| 	gtsTags, err := p.db.TagStringsToTags(util.DeriveHashtags(form.Status), accountID, status.ID) | ||||
| 	gtsTags, err := p.db.TagStringsToTags(util.DeriveHashtagsFromStatus(form.Status), accountID, status.ID) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error generating hashtags from status: %s", err) | ||||
| 	} | ||||
|  | @ -217,7 +217,7 @@ func (p *processor) processTags(form *apimodel.AdvancedStatusCreateForm, account | |||
| 
 | ||||
| func (p *processor) processEmojis(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error { | ||||
| 	emojis := []string{} | ||||
| 	gtsEmojis, err := p.db.EmojiStringsToEmojis(util.DeriveEmojis(form.Status), accountID, status.ID) | ||||
| 	gtsEmojis, err := p.db.EmojiStringsToEmojis(util.DeriveEmojisFromStatus(form.Status), accountID, status.ID) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error generating emojis from status: %s", err) | ||||
| 	} | ||||
|  |  | |||
|  | @ -81,6 +81,13 @@ func (p *processor) StatusCreate(auth *oauth.Auth, form *apimodel.AdvancedStatus | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// put the new status in the appropriate channel for async processing | ||||
| 	p.fromClientAPI <- FromClientAPI{ | ||||
| 		APObjectType:   newStatus.ActivityStreamsType, | ||||
| 		APActivityType: gtsmodel.ActivityStreamsCreate, | ||||
| 		Activity:       newStatus, | ||||
| 	} | ||||
| 
 | ||||
| 	// return the frontend representation of the new status to the submitter | ||||
| 	return p.tc.StatusToMasto(newStatus, auth.Account, auth.Account, nil, newStatus.GTSReplyToAccount, nil) | ||||
| } | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ import ( | |||
| 	"github.com/stretchr/testify/suite" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/pg" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| 	"github.com/superseriousbusiness/oauth2/v4/models" | ||||
| ) | ||||
|  | @ -62,7 +63,7 @@ func (suite *PgClientStoreTestSuite) SetupTest() { | |||
| 		Database:        "postgres", | ||||
| 		ApplicationName: "gotosocial", | ||||
| 	} | ||||
| 	db, err := db.NewPostgresService(context.Background(), c, log) | ||||
| 	db, err := pg.NewPostgresService(context.Background(), c, log) | ||||
| 	if err != nil { | ||||
| 		logrus.Panicf("error creating database connection: %s", err) | ||||
| 	} | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ import ( | |||
| 	"path/filepath" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/gin-contrib/cors" | ||||
| 	"github.com/gin-contrib/sessions" | ||||
| 	"github.com/gin-contrib/sessions/memstore" | ||||
| 	"github.com/gin-gonic/gin" | ||||
|  | @ -123,6 +124,14 @@ func New(config *config.Config, logger *logrus.Logger) (Router, error) { | |||
| 
 | ||||
| 	// create the actual engine here -- this is the core request routing handler for gts | ||||
| 	engine := gin.Default() | ||||
| 	engine.Use(cors.New(cors.Config{ | ||||
| 		AllowAllOrigins:  true, | ||||
| 		AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, | ||||
| 		AllowHeaders:     []string{"Origin", "Content-Length", "Content-Type", "Authorization"}, | ||||
| 		AllowCredentials: false, | ||||
| 		MaxAge:           12 * time.Hour, | ||||
| 	})) | ||||
| 	engine.MaxMultipartMemory = 8 << 20 // 8 MiB | ||||
| 
 | ||||
| 	// create a new session store middleware | ||||
| 	store, err := sessionStore() | ||||
|  | @ -143,10 +152,10 @@ func New(config *config.Config, logger *logrus.Logger) (Router, error) { | |||
| 	// create the actual http server here | ||||
| 	s := &http.Server{ | ||||
| 		Handler:           engine, | ||||
| 		ReadTimeout:       1 * time.Second, | ||||
| 		WriteTimeout:      1 * time.Second, | ||||
| 		ReadTimeout:       60 * time.Second, | ||||
| 		WriteTimeout:      5 * time.Second, | ||||
| 		IdleTimeout:       30 * time.Second, | ||||
| 		ReadHeaderTimeout: 2 * time.Second, | ||||
| 		ReadHeaderTimeout: 30 * time.Second, | ||||
| 	} | ||||
| 	var m *autocert.Manager | ||||
| 
 | ||||
|  |  | |||
|  | @ -54,15 +54,15 @@ func NewController(config *config.Config, clock pub.Clock, client pub.HttpClient | |||
| func (c *controller) NewTransport(pubKeyID string, privkey crypto.PrivateKey) (pub.Transport, error) { | ||||
| 	prefs := []httpsig.Algorithm{httpsig.RSA_SHA256, httpsig.RSA_SHA512} | ||||
| 	digestAlgo := httpsig.DigestSha256 | ||||
| 	getHeaders := []string{"(request-target)", "date", "accept"} | ||||
| 	postHeaders := []string{"(request-target)", "date", "accept", "digest"} | ||||
| 	getHeaders := []string{"(request-target)", "host", "date"} | ||||
| 	postHeaders := []string{"(request-target)", "host", "date", "digest"} | ||||
| 
 | ||||
| 	getSigner, _, err := httpsig.NewSigner(prefs, digestAlgo, getHeaders, httpsig.Signature) | ||||
| 	getSigner, _, err := httpsig.NewSigner(prefs, digestAlgo, getHeaders, httpsig.Signature, 120) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error creating get signer: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	postSigner, _, err := httpsig.NewSigner(prefs, digestAlgo, postHeaders, httpsig.Signature) | ||||
| 	postSigner, _, err := httpsig.NewSigner(prefs, digestAlgo, postHeaders, httpsig.Signature, 120) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error creating post signer: %s", err) | ||||
| 	} | ||||
|  |  | |||
|  | @ -1,101 +0,0 @@ | |||
| /* | ||||
|    GoToSocial | ||||
|    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||
| 
 | ||||
|    This program is free software: you can redistribute it and/or modify | ||||
|    it under the terms of the GNU Affero General Public License as published by | ||||
|    the Free Software Foundation, either version 3 of the License, or | ||||
|    (at your option) any later version. | ||||
| 
 | ||||
|    This program is distributed in the hope that it will be useful, | ||||
|    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|    GNU Affero General Public License for more details. | ||||
| 
 | ||||
|    You should have received a copy of the GNU Affero General Public License | ||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package typeutils | ||||
| 
 | ||||
| import "github.com/go-fed/activity/streams/vocab" | ||||
| 
 | ||||
| // Accountable represents the minimum activitypub interface for representing an 'account'. | ||||
| // This interface is fulfilled by: Person, Application, Organization, Service, and Group | ||||
| type Accountable interface { | ||||
| 	withJSONLDId | ||||
| 	withGetTypeName | ||||
| 	withPreferredUsername | ||||
| 	withIcon | ||||
| 	withDisplayName | ||||
| 	withImage | ||||
| 	withSummary | ||||
| 	withDiscoverable | ||||
| 	withURL | ||||
| 	withPublicKey | ||||
| 	withInbox | ||||
| 	withOutbox | ||||
| 	withFollowing | ||||
| 	withFollowers | ||||
| 	withFeatured | ||||
| } | ||||
| 
 | ||||
| type withJSONLDId interface { | ||||
| 	GetJSONLDId() vocab.JSONLDIdProperty | ||||
| } | ||||
| 
 | ||||
| type withGetTypeName interface { | ||||
| 	GetTypeName() string | ||||
| } | ||||
| 
 | ||||
| type withPreferredUsername interface { | ||||
| 	GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty | ||||
| } | ||||
| 
 | ||||
| type withIcon interface { | ||||
| 	GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty | ||||
| } | ||||
| 
 | ||||
| type withDisplayName interface { | ||||
| 	GetActivityStreamsName() vocab.ActivityStreamsNameProperty | ||||
| } | ||||
| 
 | ||||
| type withImage interface { | ||||
| 	GetActivityStreamsImage() vocab.ActivityStreamsImageProperty | ||||
| } | ||||
| 
 | ||||
| type withSummary interface { | ||||
| 	GetActivityStreamsSummary() vocab.ActivityStreamsSummaryProperty | ||||
| } | ||||
| 
 | ||||
| type withDiscoverable interface { | ||||
| 	GetTootDiscoverable() vocab.TootDiscoverableProperty | ||||
| } | ||||
| 
 | ||||
| type withURL interface { | ||||
| 	GetActivityStreamsUrl() vocab.ActivityStreamsUrlProperty | ||||
| } | ||||
| 
 | ||||
| type withPublicKey interface { | ||||
| 	GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty | ||||
| } | ||||
| 
 | ||||
| type withInbox interface { | ||||
| 	GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty | ||||
| } | ||||
| 
 | ||||
| type withOutbox interface { | ||||
| 	GetActivityStreamsOutbox() vocab.ActivityStreamsOutboxProperty | ||||
| } | ||||
| 
 | ||||
| type withFollowing interface { | ||||
| 	GetActivityStreamsFollowing() vocab.ActivityStreamsFollowingProperty | ||||
| } | ||||
| 
 | ||||
| type withFollowers interface { | ||||
| 	GetActivityStreamsFollowers() vocab.ActivityStreamsFollowersProperty | ||||
| } | ||||
| 
 | ||||
| type withFeatured interface { | ||||
| 	GetTootFeatured() vocab.TootFeaturedProperty | ||||
| } | ||||
|  | @ -25,8 +25,12 @@ import ( | |||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/go-fed/activity/pub" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| ) | ||||
| 
 | ||||
| func extractPreferredUsername(i withPreferredUsername) (string, error) { | ||||
|  | @ -40,22 +44,89 @@ func extractPreferredUsername(i withPreferredUsername) (string, error) { | |||
| 	return u.GetXMLSchemaString(), nil | ||||
| } | ||||
| 
 | ||||
| func extractName(i withDisplayName) (string, error) { | ||||
| func extractName(i withName) (string, error) { | ||||
| 	nameProp := i.GetActivityStreamsName() | ||||
| 	if nameProp == nil { | ||||
| 		return "", errors.New("activityStreamsName not found") | ||||
| 	} | ||||
| 
 | ||||
| 	// take the first name string we can find | ||||
| 	for nameIter := nameProp.Begin(); nameIter != nameProp.End(); nameIter = nameIter.Next() { | ||||
| 		if nameIter.IsXMLSchemaString() && nameIter.GetXMLSchemaString() != "" { | ||||
| 			return nameIter.GetXMLSchemaString(), nil | ||||
| 	for iter := nameProp.Begin(); iter != nameProp.End(); iter = iter.Next() { | ||||
| 		if iter.IsXMLSchemaString() && iter.GetXMLSchemaString() != "" { | ||||
| 			return iter.GetXMLSchemaString(), nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return "", errors.New("activityStreamsName not found") | ||||
| } | ||||
| 
 | ||||
| func extractInReplyToURI(i withInReplyTo) (*url.URL, error) { | ||||
| 	inReplyToProp := i.GetActivityStreamsInReplyTo() | ||||
| 	for iter := inReplyToProp.Begin(); iter != inReplyToProp.End(); iter = iter.Next() { | ||||
| 		if iter.IsIRI() { | ||||
| 			if iter.GetIRI() != nil { | ||||
| 				return iter.GetIRI(), nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, errors.New("couldn't find iri for in reply to") | ||||
| } | ||||
| 
 | ||||
| func extractTos(i withTo) ([]*url.URL, error) { | ||||
| 	to := []*url.URL{} | ||||
| 	toProp := i.GetActivityStreamsTo() | ||||
| 	for iter := toProp.Begin(); iter != toProp.End(); iter = iter.Next() { | ||||
| 		if iter.IsIRI() { | ||||
| 			if iter.GetIRI() != nil { | ||||
| 				to = append(to, iter.GetIRI()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return to, nil | ||||
| } | ||||
| 
 | ||||
| func extractCCs(i withCC) ([]*url.URL, error) { | ||||
| 	cc := []*url.URL{} | ||||
| 	ccProp := i.GetActivityStreamsCc() | ||||
| 	for iter := ccProp.Begin(); iter != ccProp.End(); iter = iter.Next() { | ||||
| 		if iter.IsIRI() { | ||||
| 			if iter.GetIRI() != nil { | ||||
| 				cc = append(cc, iter.GetIRI()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return cc, nil | ||||
| } | ||||
| 
 | ||||
| func extractAttributedTo(i withAttributedTo) (*url.URL, error) { | ||||
| 	attributedToProp := i.GetActivityStreamsAttributedTo() | ||||
| 	for iter := attributedToProp.Begin(); iter != attributedToProp.End(); iter = iter.Next() { | ||||
| 		if iter.IsIRI() { | ||||
| 			if iter.GetIRI() != nil { | ||||
| 				return iter.GetIRI(), nil | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, errors.New("couldn't find iri for attributed to") | ||||
| } | ||||
| 
 | ||||
| func extractPublished(i withPublished) (time.Time, error) { | ||||
| 	publishedProp := i.GetActivityStreamsPublished() | ||||
| 	if publishedProp == nil { | ||||
| 		return time.Time{}, errors.New("published prop was nil") | ||||
| 	} | ||||
| 
 | ||||
| 	if !publishedProp.IsXMLSchemaDateTime() { | ||||
| 		return time.Time{}, errors.New("published prop was not date time") | ||||
| 	} | ||||
| 
 | ||||
| 	t := publishedProp.Get() | ||||
| 	if t.IsZero() { | ||||
| 		return time.Time{}, errors.New("published time was zero") | ||||
| 	} | ||||
| 	return t, nil | ||||
| } | ||||
| 
 | ||||
| // extractIconURL extracts a URL to a supported image file from something like: | ||||
| //   "icon": { | ||||
| //     "mediaType": "image/jpeg", | ||||
|  | @ -72,12 +143,12 @@ func extractIconURL(i withIcon) (*url.URL, error) { | |||
| 	// here in order to find the first one that meets these criteria: | ||||
| 	// 1. is an image | ||||
| 	// 2. has a URL so we can grab it | ||||
| 	for iconIter := iconProp.Begin(); iconIter != iconProp.End(); iconIter = iconIter.Next() { | ||||
| 	for iter := iconProp.Begin(); iter != iconProp.End(); iter = iter.Next() { | ||||
| 		// 1. is an image | ||||
| 		if !iconIter.IsActivityStreamsImage() { | ||||
| 		if !iter.IsActivityStreamsImage() { | ||||
| 			continue | ||||
| 		} | ||||
| 		imageValue := iconIter.GetActivityStreamsImage() | ||||
| 		imageValue := iter.GetActivityStreamsImage() | ||||
| 		if imageValue == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | @ -108,12 +179,12 @@ func extractImageURL(i withImage) (*url.URL, error) { | |||
| 	// here in order to find the first one that meets these criteria: | ||||
| 	// 1. is an image | ||||
| 	// 2. has a URL so we can grab it | ||||
| 	for imageIter := imageProp.Begin(); imageIter != imageProp.End(); imageIter = imageIter.Next() { | ||||
| 	for iter := imageProp.Begin(); iter != imageProp.End(); iter = iter.Next() { | ||||
| 		// 1. is an image | ||||
| 		if !imageIter.IsActivityStreamsImage() { | ||||
| 		if !iter.IsActivityStreamsImage() { | ||||
| 			continue | ||||
| 		} | ||||
| 		imageValue := imageIter.GetActivityStreamsImage() | ||||
| 		imageValue := iter.GetActivityStreamsImage() | ||||
| 		if imageValue == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | @ -134,9 +205,9 @@ func extractSummary(i withSummary) (string, error) { | |||
| 		return "", errors.New("summary property was nil") | ||||
| 	} | ||||
| 
 | ||||
| 	for summaryIter := summaryProp.Begin(); summaryIter != summaryProp.End(); summaryIter = summaryIter.Next() { | ||||
| 		if summaryIter.IsXMLSchemaString() && summaryIter.GetXMLSchemaString() != "" { | ||||
| 			return summaryIter.GetXMLSchemaString(), nil | ||||
| 	for iter := summaryProp.Begin(); iter != summaryProp.End(); iter = iter.Next() { | ||||
| 		if iter.IsXMLSchemaString() && iter.GetXMLSchemaString() != "" { | ||||
| 			return iter.GetXMLSchemaString(), nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -156,9 +227,9 @@ func extractURL(i withURL) (*url.URL, error) { | |||
| 		return nil, errors.New("url property was nil") | ||||
| 	} | ||||
| 
 | ||||
| 	for urlIter := urlProp.Begin(); urlIter != urlProp.End(); urlIter = urlIter.Next() { | ||||
| 		if urlIter.IsIRI() && urlIter.GetIRI() != nil { | ||||
| 			return urlIter.GetIRI(), nil | ||||
| 	for iter := urlProp.Begin(); iter != urlProp.End(); iter = iter.Next() { | ||||
| 		if iter.IsIRI() && iter.GetIRI() != nil { | ||||
| 			return iter.GetIRI(), nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -171,8 +242,8 @@ func extractPublicKeyForOwner(i withPublicKey, forOwner *url.URL) (*rsa.PublicKe | |||
| 		return nil, nil, errors.New("public key property was nil") | ||||
| 	} | ||||
| 
 | ||||
| 	for publicKeyIter := publicKeyProp.Begin(); publicKeyIter != publicKeyProp.End(); publicKeyIter = publicKeyIter.Next() { | ||||
| 		pkey := publicKeyIter.Get() | ||||
| 	for iter := publicKeyProp.Begin(); iter != publicKeyProp.End(); iter = iter.Next() { | ||||
| 		pkey := iter.Get() | ||||
| 		if pkey == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | @ -214,3 +285,263 @@ func extractPublicKeyForOwner(i withPublicKey, forOwner *url.URL) (*rsa.PublicKe | |||
| 	} | ||||
| 	return nil, nil, errors.New("couldn't find public key") | ||||
| } | ||||
| 
 | ||||
| func extractContent(i withContent) (string, error) { | ||||
| 	contentProperty := i.GetActivityStreamsContent() | ||||
| 	if contentProperty == nil { | ||||
| 		return "", errors.New("content property was nil") | ||||
| 	} | ||||
| 	for iter := contentProperty.Begin(); iter != contentProperty.End(); iter = iter.Next() { | ||||
| 		if iter.IsXMLSchemaString() && iter.GetXMLSchemaString() != "" { | ||||
| 			return iter.GetXMLSchemaString(), nil | ||||
| 		} | ||||
| 	} | ||||
| 	return "", errors.New("no content found") | ||||
| } | ||||
| 
 | ||||
| func extractAttachments(i withAttachment) ([]*gtsmodel.MediaAttachment, error) { | ||||
| 	attachments := []*gtsmodel.MediaAttachment{} | ||||
| 
 | ||||
| 	attachmentProp := i.GetActivityStreamsAttachment() | ||||
| 	for iter := attachmentProp.Begin(); iter != attachmentProp.End(); iter = iter.Next() { | ||||
| 		attachmentable, ok := iter.(Attachmentable) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 		attachment, err := extractAttachment(attachmentable) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		attachments = append(attachments, attachment) | ||||
| 	} | ||||
| 	return attachments, nil | ||||
| } | ||||
| 
 | ||||
| func extractAttachment(i Attachmentable) (*gtsmodel.MediaAttachment, error) { | ||||
| 	attachment := >smodel.MediaAttachment{ | ||||
| 		File: gtsmodel.File{}, | ||||
| 	} | ||||
| 
 | ||||
| 	attachmentURL, err := extractURL(i) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	attachment.RemoteURL = attachmentURL.String() | ||||
| 
 | ||||
| 	mediaType := i.GetActivityStreamsMediaType() | ||||
| 	if mediaType == nil { | ||||
| 		return nil, errors.New("no media type") | ||||
| 	} | ||||
| 	if mediaType.Get() == "" { | ||||
| 		return nil, errors.New("no media type") | ||||
| 	} | ||||
| 	attachment.File.ContentType = mediaType.Get() | ||||
| 	attachment.Type = gtsmodel.FileTypeImage | ||||
| 
 | ||||
| 	name, err := extractName(i) | ||||
| 	if err == nil { | ||||
| 		attachment.Description = name | ||||
| 	} | ||||
| 
 | ||||
| 	blurhash, err := extractBlurhash(i) | ||||
| 	if err == nil { | ||||
| 		attachment.Blurhash = blurhash | ||||
| 	} | ||||
| 
 | ||||
| 	return attachment, nil | ||||
| } | ||||
| 
 | ||||
| func extractBlurhash(i withBlurhash) (string, error) { | ||||
| 	if i.GetTootBlurhashProperty() == nil { | ||||
| 		return "", errors.New("blurhash property was nil") | ||||
| 	} | ||||
| 	if i.GetTootBlurhashProperty().Get() == "" { | ||||
| 		return "", errors.New("empty blurhash string") | ||||
| 	} | ||||
| 	return i.GetTootBlurhashProperty().Get(), nil | ||||
| } | ||||
| 
 | ||||
| func extractHashtags(i withTag) ([]*gtsmodel.Tag, error) { | ||||
| 	tags := []*gtsmodel.Tag{} | ||||
| 
 | ||||
| 	tagsProp := i.GetActivityStreamsTag() | ||||
| 	for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() { | ||||
| 		t := iter.GetType() | ||||
| 		if t == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if t.GetTypeName() != "Hashtag" { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		hashtaggable, ok := t.(Hashtaggable) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		tag, err := extractHashtag(hashtaggable) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		tags = append(tags, tag) | ||||
| 	} | ||||
| 	return tags, nil | ||||
| } | ||||
| 
 | ||||
| func extractHashtag(i Hashtaggable) (*gtsmodel.Tag, error) { | ||||
| 	tag := >smodel.Tag{} | ||||
| 
 | ||||
| 	hrefProp := i.GetActivityStreamsHref() | ||||
| 	if hrefProp == nil || !hrefProp.IsIRI() { | ||||
| 		return nil, errors.New("no href prop") | ||||
| 	} | ||||
| 	tag.URL = hrefProp.GetIRI().String() | ||||
| 
 | ||||
| 	name, err := extractName(i) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	tag.Name = strings.TrimPrefix(name, "#") | ||||
| 
 | ||||
| 	return tag, nil | ||||
| } | ||||
| 
 | ||||
| func extractEmojis(i withTag) ([]*gtsmodel.Emoji, error) { | ||||
| 	emojis := []*gtsmodel.Emoji{} | ||||
| 	tagsProp := i.GetActivityStreamsTag() | ||||
| 	for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() { | ||||
| 		t := iter.GetType() | ||||
| 		if t == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if t.GetTypeName() != "Emoji" { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		emojiable, ok := t.(Emojiable) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		emoji, err := extractEmoji(emojiable) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		emojis = append(emojis, emoji) | ||||
| 	} | ||||
| 	return emojis, nil | ||||
| } | ||||
| 
 | ||||
| func extractEmoji(i Emojiable) (*gtsmodel.Emoji, error) { | ||||
| 	emoji := >smodel.Emoji{} | ||||
| 
 | ||||
| 	idProp := i.GetJSONLDId() | ||||
| 	if idProp == nil || !idProp.IsIRI() { | ||||
| 		return nil, errors.New("no id for emoji") | ||||
| 	} | ||||
| 	uri := idProp.GetIRI() | ||||
| 	emoji.URI = uri.String() | ||||
| 	emoji.Domain = uri.Host | ||||
| 
 | ||||
| 	name, err := extractName(i) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	emoji.Shortcode = strings.Trim(name, ":") | ||||
| 
 | ||||
| 	if i.GetActivityStreamsIcon() == nil { | ||||
| 		return nil, errors.New("no icon for emoji") | ||||
| 	} | ||||
| 	imageURL, err := extractIconURL(i) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("no url for emoji image") | ||||
| 	} | ||||
| 	emoji.ImageRemoteURL = imageURL.String() | ||||
| 
 | ||||
| 	return emoji, nil | ||||
| } | ||||
| 
 | ||||
| func extractMentions(i withTag) ([]*gtsmodel.Mention, error) { | ||||
| 	mentions := []*gtsmodel.Mention{} | ||||
| 	tagsProp := i.GetActivityStreamsTag() | ||||
| 	for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() { | ||||
| 		t := iter.GetType() | ||||
| 		if t == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		if t.GetTypeName() != "Mention" { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		mentionable, ok := t.(Mentionable) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		mention, err := extractMention(mentionable) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		mentions = append(mentions, mention) | ||||
| 	} | ||||
| 	return mentions, nil | ||||
| } | ||||
| 
 | ||||
| func extractMention(i Mentionable) (*gtsmodel.Mention, error) { | ||||
| 	mention := >smodel.Mention{} | ||||
| 
 | ||||
| 	mentionString, err := extractName(i) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// just make sure the mention string is valid so we can handle it properly later on... | ||||
| 	username, domain, err := util.ExtractMentionParts(mentionString) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if username == "" || domain == "" { | ||||
| 		return nil, errors.New("username or domain was empty") | ||||
| 	} | ||||
| 	mention.NameString = mentionString | ||||
| 
 | ||||
| 	// the href prop should be the AP URI of a user we know, eg https://example.org/users/whatever_user | ||||
| 	hrefProp := i.GetActivityStreamsHref() | ||||
| 	if hrefProp == nil || !hrefProp.IsIRI() { | ||||
| 		return nil, errors.New("no href prop") | ||||
| 	} | ||||
| 	mention.MentionedAccountURI = hrefProp.GetIRI().String() | ||||
| 	return mention, nil | ||||
| } | ||||
| 
 | ||||
| func extractActor(i withActor) (*url.URL, error) { | ||||
| 	actorProp := i.GetActivityStreamsActor() | ||||
| 	if actorProp == nil { | ||||
| 		return nil, errors.New("actor property was nil") | ||||
| 	} | ||||
| 	for iter := actorProp.Begin(); iter != actorProp.End(); iter = iter.Next() { | ||||
| 		if iter.IsIRI() && iter.GetIRI() != nil { | ||||
| 			return iter.GetIRI(), nil | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, errors.New("no iri found for actor prop") | ||||
| } | ||||
| 
 | ||||
| func extractObject(i withObject) (*url.URL, error) { | ||||
| 	objectProp := i.GetActivityStreamsObject() | ||||
| 	if objectProp == nil { | ||||
| 		return nil, errors.New("object property was nil") | ||||
| 	} | ||||
| 	for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() { | ||||
| 		if iter.IsIRI() && iter.GetIRI() != nil { | ||||
| 			return iter.GetIRI(), nil | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, errors.New("no iri found for object prop") | ||||
| } | ||||
|  |  | |||
							
								
								
									
										237
									
								
								internal/typeutils/asinterfaces.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								internal/typeutils/asinterfaces.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,237 @@ | |||
| /* | ||||
|    GoToSocial | ||||
|    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||
| 
 | ||||
|    This program is free software: you can redistribute it and/or modify | ||||
|    it under the terms of the GNU Affero General Public License as published by | ||||
|    the Free Software Foundation, either version 3 of the License, or | ||||
|    (at your option) any later version. | ||||
| 
 | ||||
|    This program is distributed in the hope that it will be useful, | ||||
|    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|    GNU Affero General Public License for more details. | ||||
| 
 | ||||
|    You should have received a copy of the GNU Affero General Public License | ||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| package typeutils | ||||
| 
 | ||||
| import "github.com/go-fed/activity/streams/vocab" | ||||
| 
 | ||||
| // Accountable represents the minimum activitypub interface for representing an 'account'. | ||||
| // This interface is fulfilled by: Person, Application, Organization, Service, and Group | ||||
| type Accountable interface { | ||||
| 	withJSONLDId | ||||
| 	withTypeName | ||||
| 
 | ||||
| 	withPreferredUsername | ||||
| 	withIcon | ||||
| 	withName | ||||
| 	withImage | ||||
| 	withSummary | ||||
| 	withDiscoverable | ||||
| 	withURL | ||||
| 	withPublicKey | ||||
| 	withInbox | ||||
| 	withOutbox | ||||
| 	withFollowing | ||||
| 	withFollowers | ||||
| 	withFeatured | ||||
| } | ||||
| 
 | ||||
| // Statusable represents the minimum activitypub interface for representing a 'status'. | ||||
| // This interface is fulfilled by: Article, Document, Image, Video, Note, Page, Event, Place, Mention, Profile | ||||
| type Statusable interface { | ||||
| 	withJSONLDId | ||||
| 	withTypeName | ||||
| 
 | ||||
| 	withSummary | ||||
| 	withInReplyTo | ||||
| 	withPublished | ||||
| 	withURL | ||||
| 	withAttributedTo | ||||
| 	withTo | ||||
| 	withCC | ||||
| 	withSensitive | ||||
| 	withConversation | ||||
| 	withContent | ||||
| 	withAttachment | ||||
| 	withTag | ||||
| 	withReplies | ||||
| } | ||||
| 
 | ||||
| // Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'. | ||||
| // This interface is fulfilled by: Audio, Document, Image, Video | ||||
| type Attachmentable interface { | ||||
| 	withTypeName | ||||
| 	withMediaType | ||||
| 	withURL | ||||
| 	withName | ||||
| 	withBlurhash | ||||
| 	withFocalPoint | ||||
| } | ||||
| 
 | ||||
| // Hashtaggable represents the minimum activitypub interface for representing a 'hashtag' tag. | ||||
| type Hashtaggable interface { | ||||
| 	withTypeName | ||||
| 	withHref | ||||
| 	withName | ||||
| } | ||||
| 
 | ||||
| // Emojiable represents the minimum interface for an 'emoji' tag. | ||||
| type Emojiable interface { | ||||
| 	withJSONLDId | ||||
| 	withTypeName | ||||
| 	withName | ||||
| 	withUpdated | ||||
| 	withIcon | ||||
| } | ||||
| 
 | ||||
| // Mentionable represents the minimum interface for a 'mention' tag. | ||||
| type Mentionable interface { | ||||
| 	withName | ||||
| 	withHref | ||||
| } | ||||
| 
 | ||||
| // Followable represents the minimum interface for an activitystreams 'follow' activity. | ||||
| type Followable interface { | ||||
| 	withJSONLDId | ||||
| 	withTypeName | ||||
| 
 | ||||
| 	withActor | ||||
| 	withObject | ||||
| } | ||||
| 
 | ||||
| type withJSONLDId interface { | ||||
| 	GetJSONLDId() vocab.JSONLDIdProperty | ||||
| } | ||||
| 
 | ||||
| type withTypeName interface { | ||||
| 	GetTypeName() string | ||||
| } | ||||
| 
 | ||||
| type withPreferredUsername interface { | ||||
| 	GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty | ||||
| } | ||||
| 
 | ||||
| type withIcon interface { | ||||
| 	GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty | ||||
| } | ||||
| 
 | ||||
| type withName interface { | ||||
| 	GetActivityStreamsName() vocab.ActivityStreamsNameProperty | ||||
| } | ||||
| 
 | ||||
| type withImage interface { | ||||
| 	GetActivityStreamsImage() vocab.ActivityStreamsImageProperty | ||||
| } | ||||
| 
 | ||||
| type withSummary interface { | ||||
| 	GetActivityStreamsSummary() vocab.ActivityStreamsSummaryProperty | ||||
| } | ||||
| 
 | ||||
| type withDiscoverable interface { | ||||
| 	GetTootDiscoverable() vocab.TootDiscoverableProperty | ||||
| } | ||||
| 
 | ||||
| type withURL interface { | ||||
| 	GetActivityStreamsUrl() vocab.ActivityStreamsUrlProperty | ||||
| } | ||||
| 
 | ||||
| type withPublicKey interface { | ||||
| 	GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty | ||||
| } | ||||
| 
 | ||||
| type withInbox interface { | ||||
| 	GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty | ||||
| } | ||||
| 
 | ||||
| type withOutbox interface { | ||||
| 	GetActivityStreamsOutbox() vocab.ActivityStreamsOutboxProperty | ||||
| } | ||||
| 
 | ||||
| type withFollowing interface { | ||||
| 	GetActivityStreamsFollowing() vocab.ActivityStreamsFollowingProperty | ||||
| } | ||||
| 
 | ||||
| type withFollowers interface { | ||||
| 	GetActivityStreamsFollowers() vocab.ActivityStreamsFollowersProperty | ||||
| } | ||||
| 
 | ||||
| type withFeatured interface { | ||||
| 	GetTootFeatured() vocab.TootFeaturedProperty | ||||
| } | ||||
| 
 | ||||
| type withAttributedTo interface { | ||||
| 	GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty | ||||
| } | ||||
| 
 | ||||
| type withAttachment interface { | ||||
| 	GetActivityStreamsAttachment() vocab.ActivityStreamsAttachmentProperty | ||||
| } | ||||
| 
 | ||||
| type withTo interface { | ||||
| 	GetActivityStreamsTo() vocab.ActivityStreamsToProperty | ||||
| } | ||||
| 
 | ||||
| type withInReplyTo interface { | ||||
| 	GetActivityStreamsInReplyTo() vocab.ActivityStreamsInReplyToProperty | ||||
| } | ||||
| 
 | ||||
| type withCC interface { | ||||
| 	GetActivityStreamsCc() vocab.ActivityStreamsCcProperty | ||||
| } | ||||
| 
 | ||||
| type withSensitive interface { | ||||
| 	// TODO | ||||
| } | ||||
| 
 | ||||
| type withConversation interface { | ||||
| 	// TODO | ||||
| } | ||||
| 
 | ||||
| type withContent interface { | ||||
| 	GetActivityStreamsContent() vocab.ActivityStreamsContentProperty | ||||
| } | ||||
| 
 | ||||
| type withPublished interface { | ||||
| 	GetActivityStreamsPublished() vocab.ActivityStreamsPublishedProperty | ||||
| } | ||||
| 
 | ||||
| type withTag interface { | ||||
| 	GetActivityStreamsTag() vocab.ActivityStreamsTagProperty | ||||
| } | ||||
| 
 | ||||
| type withReplies interface { | ||||
| 	GetActivityStreamsReplies() vocab.ActivityStreamsRepliesProperty | ||||
| } | ||||
| 
 | ||||
| type withMediaType interface { | ||||
| 	GetActivityStreamsMediaType() vocab.ActivityStreamsMediaTypeProperty | ||||
| } | ||||
| 
 | ||||
| type withBlurhash interface { | ||||
| 	GetTootBlurhashProperty() vocab.TootBlurhashProperty | ||||
| } | ||||
| 
 | ||||
| type withFocalPoint interface { | ||||
| 	// TODO | ||||
| } | ||||
| 
 | ||||
| type withHref interface { | ||||
| 	GetActivityStreamsHref() vocab.ActivityStreamsHrefProperty | ||||
| } | ||||
| 
 | ||||
| type withUpdated interface { | ||||
| 	GetActivityStreamsUpdated() vocab.ActivityStreamsUpdatedProperty | ||||
| } | ||||
| 
 | ||||
| type withActor interface { | ||||
| 	GetActivityStreamsActor() vocab.ActivityStreamsActorProperty | ||||
| } | ||||
| 
 | ||||
| type withObject interface { | ||||
| 	GetActivityStreamsObject() vocab.ActivityStreamsObjectProperty | ||||
| } | ||||
|  | @ -21,6 +21,8 @@ package typeutils | |||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
|  | @ -157,3 +159,202 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode | |||
| 
 | ||||
| 	return acct, nil | ||||
| } | ||||
| 
 | ||||
| func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error) { | ||||
| 	status := >smodel.Status{} | ||||
| 
 | ||||
| 	// uri at which this status is reachable | ||||
| 	uriProp := statusable.GetJSONLDId() | ||||
| 	if uriProp == nil || !uriProp.IsIRI() { | ||||
| 		return nil, errors.New("no id property found, or id was not an iri") | ||||
| 	} | ||||
| 	status.URI = uriProp.GetIRI().String() | ||||
| 
 | ||||
| 	// web url for viewing this status | ||||
| 	if statusURL, err := extractURL(statusable); err == nil { | ||||
| 		status.URL = statusURL.String() | ||||
| 	} | ||||
| 
 | ||||
| 	// the html-formatted content of this status | ||||
| 	if content, err := extractContent(statusable); err == nil { | ||||
| 		status.Content = content | ||||
| 	} | ||||
| 
 | ||||
| 	// attachments to dereference and fetch later on (we don't do that here) | ||||
| 	if attachments, err := extractAttachments(statusable); err == nil { | ||||
| 		status.GTSMediaAttachments = attachments | ||||
| 	} | ||||
| 
 | ||||
| 	// hashtags to dereference later on | ||||
| 	if hashtags, err := extractHashtags(statusable); err == nil { | ||||
| 		status.GTSTags = hashtags | ||||
| 	} | ||||
| 
 | ||||
| 	// emojis to dereference and fetch later on | ||||
| 	if emojis, err := extractEmojis(statusable); err == nil { | ||||
| 		status.GTSEmojis = emojis | ||||
| 	} | ||||
| 
 | ||||
| 	// mentions to dereference later on | ||||
| 	if mentions, err := extractMentions(statusable); err == nil { | ||||
| 		status.GTSMentions = mentions | ||||
| 	} | ||||
| 
 | ||||
| 	// cw string for this status | ||||
| 	if cw, err := extractSummary(statusable); err == nil { | ||||
| 		status.ContentWarning = cw | ||||
| 	} | ||||
| 
 | ||||
| 	// when was this status created? | ||||
| 	published, err := extractPublished(statusable) | ||||
| 	if err == nil { | ||||
| 		status.CreatedAt = published | ||||
| 	} | ||||
| 
 | ||||
| 	// which account posted this status? | ||||
| 	// if we don't know the account yet we can dereference it later | ||||
| 	attributedTo, err := extractAttributedTo(statusable) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("attributedTo was empty") | ||||
| 	} | ||||
| 	status.APStatusOwnerURI = attributedTo.String() | ||||
| 
 | ||||
| 	statusOwner := >smodel.Account{} | ||||
| 	if err := c.db.GetWhere("uri", attributedTo.String(), statusOwner); err != nil { | ||||
| 		return nil, fmt.Errorf("couldn't get status owner from db: %s", err) | ||||
| 	} | ||||
| 	status.AccountID = statusOwner.ID | ||||
| 	status.GTSAccount = statusOwner | ||||
| 
 | ||||
| 	// check if there's a post that this is a reply to | ||||
| 	inReplyToURI, err := extractInReplyToURI(statusable) | ||||
| 	if err == nil { | ||||
| 		// something is set so we can at least set this field on the | ||||
| 		// status and dereference using this later if we need to | ||||
| 		status.APReplyToStatusURI = inReplyToURI.String() | ||||
| 
 | ||||
| 		// now we can check if we have the replied-to status in our db already | ||||
| 		inReplyToStatus := >smodel.Status{} | ||||
| 		if err := c.db.GetWhere("uri", inReplyToURI.String(), inReplyToStatus); err == nil { | ||||
| 			// we have the status in our database already | ||||
| 			// so we can set these fields here and then... | ||||
| 			status.InReplyToID = inReplyToStatus.ID | ||||
| 			status.InReplyToAccountID = inReplyToStatus.AccountID | ||||
| 			status.GTSReplyToStatus = inReplyToStatus | ||||
| 
 | ||||
| 			// ... check if we've seen the account already | ||||
| 			inReplyToAccount := >smodel.Account{} | ||||
| 			if err := c.db.GetByID(inReplyToStatus.AccountID, inReplyToAccount); err == nil { | ||||
| 				status.GTSReplyToAccount = inReplyToAccount | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// visibility entry for this status | ||||
| 	var visibility gtsmodel.Visibility | ||||
| 
 | ||||
| 	to, err := extractTos(statusable) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error extracting TO values: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	cc, err := extractCCs(statusable) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error extracting CC values: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if len(to) == 0 && len(cc) == 0 { | ||||
| 		return nil, errors.New("message wasn't TO or CC anyone") | ||||
| 	} | ||||
| 
 | ||||
| 	// for visibility derivation, we start by assuming most restrictive, and work our way to least restrictive | ||||
| 
 | ||||
| 	// if it's a DM then it's addressed to SPECIFIC ACCOUNTS and not followers or public | ||||
| 	if len(to) != 0 && len(cc) == 0 { | ||||
| 		visibility = gtsmodel.VisibilityDirect | ||||
| 	} | ||||
| 
 | ||||
| 	// if it's just got followers in TO and it's not also CC'ed to public, it's followers only | ||||
| 	if isFollowers(to, statusOwner.FollowersURI) { | ||||
| 		visibility = gtsmodel.VisibilityFollowersOnly | ||||
| 	} | ||||
| 
 | ||||
| 	// if it's CC'ed to public, it's public or unlocked | ||||
| 	// mentioned SPECIFIC ACCOUNTS also get added to CC'es if it's not a direct message | ||||
| 	if isPublic(to) { | ||||
| 		visibility = gtsmodel.VisibilityPublic | ||||
| 	} | ||||
| 
 | ||||
| 	// we should have a visibility by now | ||||
| 	if visibility == "" { | ||||
| 		return nil, errors.New("couldn't derive visibility") | ||||
| 	} | ||||
| 	status.Visibility = visibility | ||||
| 
 | ||||
| 	// advanced visibility for this status | ||||
| 	// TODO: a lot of work to be done here -- a new type needs to be created for this in go-fed/activity using ASTOOL | ||||
| 
 | ||||
| 	// sensitive | ||||
| 	// TODO: this is a bool | ||||
| 
 | ||||
| 	// language | ||||
| 	// we might be able to extract this from the contentMap field | ||||
| 
 | ||||
| 	// ActivityStreamsType | ||||
| 	status.ActivityStreamsType = gtsmodel.ActivityStreamsObject(statusable.GetTypeName()) | ||||
| 
 | ||||
| 	return status, nil | ||||
| } | ||||
| 
 | ||||
| func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error) { | ||||
| 
 | ||||
| 	idProp := followable.GetJSONLDId() | ||||
| 	if idProp == nil || !idProp.IsIRI() { | ||||
| 		return nil, errors.New("no id property set on follow, or was not an iri") | ||||
| 	} | ||||
| 	uri := idProp.GetIRI().String() | ||||
| 
 | ||||
| 	origin, err := extractActor(followable) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("error extracting actor property from follow") | ||||
| 	} | ||||
| 	originAccount := >smodel.Account{} | ||||
| 	if err := c.db.GetWhere("uri", origin.String(), originAccount); err != nil { | ||||
| 		return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	target, err := extractObject(followable) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("error extracting object property from follow") | ||||
| 	} | ||||
| 	targetAccount := >smodel.Account{} | ||||
| 	if err := c.db.GetWhere("uri", target.String(), targetAccount); err != nil { | ||||
| 		return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) | ||||
| 	} | ||||
| 
 | ||||
| 	followRequest := >smodel.FollowRequest{ | ||||
| 		URI:             uri, | ||||
| 		AccountID:       originAccount.ID, | ||||
| 		TargetAccountID: targetAccount.ID, | ||||
| 	} | ||||
| 
 | ||||
| 	return followRequest, nil | ||||
| } | ||||
| 
 | ||||
| func isPublic(tos []*url.URL) bool { | ||||
| 	for _, entry := range tos { | ||||
| 		if strings.EqualFold(entry.String(), "https://www.w3.org/ns/activitystreams#Public") { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func isFollowers(ccs []*url.URL, followersURI string) bool { | ||||
| 	for _, entry := range ccs { | ||||
| 		if strings.EqualFold(entry.String(), followersURI) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ import ( | |||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/go-fed/activity/streams" | ||||
| 	"github.com/go-fed/activity/streams/vocab" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/stretchr/testify/suite" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||
|  | @ -36,6 +37,182 @@ type ASToInternalTestSuite struct { | |||
| } | ||||
| 
 | ||||
| const ( | ||||
| 	statusWithMentionsActivityJson = `{ | ||||
| 		"@context": [ | ||||
| 		  "https://www.w3.org/ns/activitystreams", | ||||
| 		  { | ||||
| 			"ostatus": "http://ostatus.org#", | ||||
| 			"atomUri": "ostatus:atomUri", | ||||
| 			"inReplyToAtomUri": "ostatus:inReplyToAtomUri", | ||||
| 			"conversation": "ostatus:conversation", | ||||
| 			"sensitive": "as:sensitive", | ||||
| 			"toot": "http://joinmastodon.org/ns#", | ||||
| 			"votersCount": "toot:votersCount" | ||||
| 		  } | ||||
| 		], | ||||
| 		"id": "https://ondergrond.org/users/dumpsterqueer/statuses/106221634728637552/activity", | ||||
| 		"type": "Create", | ||||
| 		"actor": "https://ondergrond.org/users/dumpsterqueer", | ||||
| 		"published": "2021-05-12T09:58:38Z", | ||||
| 		"to": [ | ||||
| 		  "https://ondergrond.org/users/dumpsterqueer/followers" | ||||
| 		], | ||||
| 		"cc": [ | ||||
| 		  "https://www.w3.org/ns/activitystreams#Public", | ||||
| 		  "https://social.pixie.town/users/f0x" | ||||
| 		], | ||||
| 		"object": { | ||||
| 		  "id": "https://ondergrond.org/users/dumpsterqueer/statuses/106221634728637552", | ||||
| 		  "type": "Note", | ||||
| 		  "summary": null, | ||||
| 		  "inReplyTo": "https://social.pixie.town/users/f0x/statuses/106221628567855262", | ||||
| 		  "published": "2021-05-12T09:58:38Z", | ||||
| 		  "url": "https://ondergrond.org/@dumpsterqueer/106221634728637552", | ||||
| 		  "attributedTo": "https://ondergrond.org/users/dumpsterqueer", | ||||
| 		  "to": [ | ||||
| 			"https://ondergrond.org/users/dumpsterqueer/followers" | ||||
| 		  ], | ||||
| 		  "cc": [ | ||||
| 			"https://www.w3.org/ns/activitystreams#Public", | ||||
| 			"https://social.pixie.town/users/f0x" | ||||
| 		  ], | ||||
| 		  "sensitive": false, | ||||
| 		  "atomUri": "https://ondergrond.org/users/dumpsterqueer/statuses/106221634728637552", | ||||
| 		  "inReplyToAtomUri": "https://social.pixie.town/users/f0x/statuses/106221628567855262", | ||||
| 		  "conversation": "tag:ondergrond.org,2021-05-12:objectId=1132361:objectType=Conversation", | ||||
| 		  "content": "<p><span class=\"h-card\"><a href=\"https://social.pixie.town/@f0x\" class=\"u-url mention\">@<span>f0x</span></a></span> nice there it is:</p><p><a href=\"https://social.pixie.town/users/f0x/statuses/106221628567855262/activity\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">social.pixie.town/users/f0x/st</span><span class=\"invisible\">atuses/106221628567855262/activity</span></a></p>", | ||||
| 		  "contentMap": { | ||||
| 			"en": "<p><span class=\"h-card\"><a href=\"https://social.pixie.town/@f0x\" class=\"u-url mention\">@<span>f0x</span></a></span> nice there it is:</p><p><a href=\"https://social.pixie.town/users/f0x/statuses/106221628567855262/activity\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">social.pixie.town/users/f0x/st</span><span class=\"invisible\">atuses/106221628567855262/activity</span></a></p>" | ||||
| 		  }, | ||||
| 		  "attachment": [], | ||||
| 		  "tag": [ | ||||
| 			{ | ||||
| 			  "type": "Mention", | ||||
| 			  "href": "https://social.pixie.town/users/f0x", | ||||
| 			  "name": "@f0x@pixie.town" | ||||
| 			} | ||||
| 		  ], | ||||
| 		  "replies": { | ||||
| 			"id": "https://ondergrond.org/users/dumpsterqueer/statuses/106221634728637552/replies", | ||||
| 			"type": "Collection", | ||||
| 			"first": { | ||||
| 			  "type": "CollectionPage", | ||||
| 			  "next": "https://ondergrond.org/users/dumpsterqueer/statuses/106221634728637552/replies?only_other_accounts=true&page=true", | ||||
| 			  "partOf": "https://ondergrond.org/users/dumpsterqueer/statuses/106221634728637552/replies", | ||||
| 			  "items": [] | ||||
| 			} | ||||
| 		  } | ||||
| 		} | ||||
| 	  }` | ||||
| 	statusWithEmojisAndTagsAsActivityJson = `{ | ||||
| 		"@context": [ | ||||
| 		  "https://www.w3.org/ns/activitystreams", | ||||
| 		  { | ||||
| 			"ostatus": "http://ostatus.org#", | ||||
| 			"atomUri": "ostatus:atomUri", | ||||
| 			"inReplyToAtomUri": "ostatus:inReplyToAtomUri", | ||||
| 			"conversation": "ostatus:conversation", | ||||
| 			"sensitive": "as:sensitive", | ||||
| 			"toot": "http://joinmastodon.org/ns#", | ||||
| 			"votersCount": "toot:votersCount", | ||||
| 			"Hashtag": "as:Hashtag", | ||||
| 			"Emoji": "toot:Emoji", | ||||
| 			"focalPoint": { | ||||
| 			  "@container": "@list", | ||||
| 			  "@id": "toot:focalPoint" | ||||
| 			} | ||||
| 		  } | ||||
| 		], | ||||
| 		"id": "https://ondergrond.org/users/dumpsterqueer/statuses/106221567884565704/activity", | ||||
| 		"type": "Create", | ||||
| 		"actor": "https://ondergrond.org/users/dumpsterqueer", | ||||
| 		"published": "2021-05-12T09:41:38Z", | ||||
| 		"to": [ | ||||
| 		  "https://ondergrond.org/users/dumpsterqueer/followers" | ||||
| 		], | ||||
| 		"cc": [ | ||||
| 		  "https://www.w3.org/ns/activitystreams#Public" | ||||
| 		], | ||||
| 		"object": { | ||||
| 		  "id": "https://ondergrond.org/users/dumpsterqueer/statuses/106221567884565704", | ||||
| 		  "type": "Note", | ||||
| 		  "summary": null, | ||||
| 		  "inReplyTo": null, | ||||
| 		  "published": "2021-05-12T09:41:38Z", | ||||
| 		  "url": "https://ondergrond.org/@dumpsterqueer/106221567884565704", | ||||
| 		  "attributedTo": "https://ondergrond.org/users/dumpsterqueer", | ||||
| 		  "to": [ | ||||
| 			"https://ondergrond.org/users/dumpsterqueer/followers" | ||||
| 		  ], | ||||
| 		  "cc": [ | ||||
| 			"https://www.w3.org/ns/activitystreams#Public" | ||||
| 		  ], | ||||
| 		  "sensitive": false, | ||||
| 		  "atomUri": "https://ondergrond.org/users/dumpsterqueer/statuses/106221567884565704", | ||||
| 		  "inReplyToAtomUri": null, | ||||
| 		  "conversation": "tag:ondergrond.org,2021-05-12:objectId=1132361:objectType=Conversation", | ||||
| 		  "content": "<p>just testing activitypub representations of <a href=\"https://ondergrond.org/tags/tags\" class=\"mention hashtag\" rel=\"tag\">#<span>tags</span></a> and <a href=\"https://ondergrond.org/tags/emoji\" class=\"mention hashtag\" rel=\"tag\">#<span>emoji</span></a>  :party_parrot: :amaze: :blobsunglasses: </p><p>don't mind me....</p>", | ||||
| 		  "contentMap": { | ||||
| 			"en": "<p>just testing activitypub representations of <a href=\"https://ondergrond.org/tags/tags\" class=\"mention hashtag\" rel=\"tag\">#<span>tags</span></a> and <a href=\"https://ondergrond.org/tags/emoji\" class=\"mention hashtag\" rel=\"tag\">#<span>emoji</span></a>  :party_parrot: :amaze: :blobsunglasses: </p><p>don't mind me....</p>" | ||||
| 		  }, | ||||
| 		  "attachment": [], | ||||
| 		  "tag": [ | ||||
| 			{ | ||||
| 			  "type": "Hashtag", | ||||
| 			  "href": "https://ondergrond.org/tags/tags", | ||||
| 			  "name": "#tags" | ||||
| 			}, | ||||
| 			{ | ||||
| 			  "type": "Hashtag", | ||||
| 			  "href": "https://ondergrond.org/tags/emoji", | ||||
| 			  "name": "#emoji" | ||||
| 			}, | ||||
| 			{ | ||||
| 			  "id": "https://ondergrond.org/emojis/2390", | ||||
| 			  "type": "Emoji", | ||||
| 			  "name": ":party_parrot:", | ||||
| 			  "updated": "2020-11-06T13:42:11Z", | ||||
| 			  "icon": { | ||||
| 				"type": "Image", | ||||
| 				"mediaType": "image/gif", | ||||
| 				"url": "https://ondergrond.org/system/custom_emojis/images/000/002/390/original/ef133aac7ab23341.gif" | ||||
| 			  } | ||||
| 			}, | ||||
| 			{ | ||||
| 			  "id": "https://ondergrond.org/emojis/2395", | ||||
| 			  "type": "Emoji", | ||||
| 			  "name": ":amaze:", | ||||
| 			  "updated": "2020-09-26T12:29:56Z", | ||||
| 			  "icon": { | ||||
| 				"type": "Image", | ||||
| 				"mediaType": "image/png", | ||||
| 				"url": "https://ondergrond.org/system/custom_emojis/images/000/002/395/original/2c7d9345e57367ed.png" | ||||
| 			  } | ||||
| 			}, | ||||
| 			{ | ||||
| 			  "id": "https://ondergrond.org/emojis/764", | ||||
| 			  "type": "Emoji", | ||||
| 			  "name": ":blobsunglasses:", | ||||
| 			  "updated": "2020-09-26T12:13:23Z", | ||||
| 			  "icon": { | ||||
| 				"type": "Image", | ||||
| 				"mediaType": "image/png", | ||||
| 				"url": "https://ondergrond.org/system/custom_emojis/images/000/000/764/original/3f8eef9de773c90d.png" | ||||
| 			  } | ||||
| 			} | ||||
| 		  ], | ||||
| 		  "replies": { | ||||
| 			"id": "https://ondergrond.org/users/dumpsterqueer/statuses/106221567884565704/replies", | ||||
| 			"type": "Collection", | ||||
| 			"first": { | ||||
| 			  "type": "CollectionPage", | ||||
| 			  "next": "https://ondergrond.org/users/dumpsterqueer/statuses/106221567884565704/replies?only_other_accounts=true&page=true", | ||||
| 			  "partOf": "https://ondergrond.org/users/dumpsterqueer/statuses/106221567884565704/replies", | ||||
| 			  "items": [] | ||||
| 			} | ||||
| 		  } | ||||
| 		} | ||||
| 	  }` | ||||
| 	gargronAsActivityJson = `{ | ||||
| 		"@context": [ | ||||
| 		  "https://www.w3.org/ns/activitystreams", | ||||
|  | @ -197,6 +374,62 @@ func (suite *ASToInternalTestSuite) TestParseGargron() { | |||
| 	// TODO: write assertions here, rn we're just eyeballing the output | ||||
| } | ||||
| 
 | ||||
| func (suite *ASToInternalTestSuite) TestParseStatus() { | ||||
| 	m := make(map[string]interface{}) | ||||
| 	err := json.Unmarshal([]byte(statusWithEmojisAndTagsAsActivityJson), &m) | ||||
| 	assert.NoError(suite.T(), err) | ||||
| 
 | ||||
| 	t, err := streams.ToType(context.Background(), m) | ||||
| 	assert.NoError(suite.T(), err) | ||||
| 
 | ||||
| 	create, ok := t.(vocab.ActivityStreamsCreate) | ||||
| 	assert.True(suite.T(), ok) | ||||
| 
 | ||||
| 	obj := create.GetActivityStreamsObject() | ||||
| 	assert.NotNil(suite.T(), obj) | ||||
| 
 | ||||
| 	first := obj.Begin() | ||||
| 	assert.NotNil(suite.T(), first) | ||||
| 
 | ||||
| 	rep, ok := first.GetType().(typeutils.Statusable) | ||||
| 	assert.True(suite.T(), ok) | ||||
| 
 | ||||
| 	status, err := suite.typeconverter.ASStatusToStatus(rep) | ||||
| 	assert.NoError(suite.T(), err) | ||||
| 
 | ||||
| 	assert.Len(suite.T(), status.GTSEmojis, 3) | ||||
| 	// assert.Len(suite.T(), status.GTSTags, 2) TODO: implement this first so that it can pick up tags | ||||
| } | ||||
| 
 | ||||
| func (suite *ASToInternalTestSuite) TestParseStatusWithMention() { | ||||
| 	m := make(map[string]interface{}) | ||||
| 	err := json.Unmarshal([]byte(statusWithMentionsActivityJson), &m) | ||||
| 	assert.NoError(suite.T(), err) | ||||
| 
 | ||||
| 	t, err := streams.ToType(context.Background(), m) | ||||
| 	assert.NoError(suite.T(), err) | ||||
| 
 | ||||
| 	create, ok := t.(vocab.ActivityStreamsCreate) | ||||
| 	assert.True(suite.T(), ok) | ||||
| 
 | ||||
| 	obj := create.GetActivityStreamsObject() | ||||
| 	assert.NotNil(suite.T(), obj) | ||||
| 
 | ||||
| 	first := obj.Begin() | ||||
| 	assert.NotNil(suite.T(), first) | ||||
| 
 | ||||
| 	rep, ok := first.GetType().(typeutils.Statusable) | ||||
| 	assert.True(suite.T(), ok) | ||||
| 
 | ||||
| 	status, err := suite.typeconverter.ASStatusToStatus(rep) | ||||
| 	assert.NoError(suite.T(), err) | ||||
| 
 | ||||
| 	fmt.Printf("%+v", status) | ||||
| 
 | ||||
| 	assert.Len(suite.T(), status.GTSMentions, 1) | ||||
| 	fmt.Println(status.GTSMentions[0]) | ||||
| } | ||||
| 
 | ||||
| func (suite *ASToInternalTestSuite) TearDownTest() { | ||||
| 	testrig.StandardDBTeardown(suite.db) | ||||
| } | ||||
|  |  | |||
|  | @ -90,6 +90,10 @@ type TypeConverter interface { | |||
| 
 | ||||
| 	// ASPersonToAccount converts a remote account/person/application representation into a gts model account | ||||
| 	ASRepresentationToAccount(accountable Accountable) (*gtsmodel.Account, error) | ||||
| 	// ASStatus converts a remote activitystreams 'status' representation into a gts model status. | ||||
| 	ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error) | ||||
| 	// ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request. | ||||
| 	ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error) | ||||
| 
 | ||||
| 	/* | ||||
| 		INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL | ||||
|  |  | |||
|  | @ -200,15 +200,15 @@ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerso | |||
| 	// icon | ||||
| 	// Used as profile avatar. | ||||
| 	if a.AvatarMediaAttachmentID != "" { | ||||
| 		iconProperty := streams.NewActivityStreamsIconProperty() | ||||
| 
 | ||||
| 		iconImage := streams.NewActivityStreamsImage() | ||||
| 
 | ||||
| 		avatar := >smodel.MediaAttachment{} | ||||
| 		if err := c.db.GetByID(a.AvatarMediaAttachmentID, avatar); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		iconProperty := streams.NewActivityStreamsIconProperty() | ||||
| 
 | ||||
| 		iconImage := streams.NewActivityStreamsImage() | ||||
| 
 | ||||
| 		mediaType := streams.NewActivityStreamsMediaTypeProperty() | ||||
| 		mediaType.Set(avatar.File.ContentType) | ||||
| 		iconImage.SetActivityStreamsMediaType(mediaType) | ||||
|  | @ -228,15 +228,15 @@ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerso | |||
| 	// image | ||||
| 	// Used as profile header. | ||||
| 	if a.HeaderMediaAttachmentID != "" { | ||||
| 		headerProperty := streams.NewActivityStreamsImageProperty() | ||||
| 
 | ||||
| 		headerImage := streams.NewActivityStreamsImage() | ||||
| 
 | ||||
| 		header := >smodel.MediaAttachment{} | ||||
| 		if err := c.db.GetByID(a.HeaderMediaAttachmentID, header); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		headerProperty := streams.NewActivityStreamsImageProperty() | ||||
| 
 | ||||
| 		headerImage := streams.NewActivityStreamsImage() | ||||
| 
 | ||||
| 		mediaType := streams.NewActivityStreamsMediaTypeProperty() | ||||
| 		mediaType.Set(header.File.ContentType) | ||||
| 		headerImage.SetActivityStreamsMediaType(mediaType) | ||||
|  |  | |||
|  | @ -35,6 +35,11 @@ const ( | |||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	mentionNameRegexString = `^@([a-zA-Z0-9_]+)(?:@([a-zA-Z0-9_\-\.]+)?)$` | ||||
| 	// mention name regex captures the username and domain part from a mention string | ||||
| 	// such as @whatever_user@example.org, returning whatever_user and example.org (without the @ symbols) | ||||
| 	mentionNameRegex = regexp.MustCompile(mentionNameRegexString) | ||||
| 
 | ||||
| 	// mention regex can be played around with here: https://regex101.com/r/qwM9D3/1 | ||||
| 	mentionFinderRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?: |\n)` | ||||
| 	mentionFinderRegex       = regexp.MustCompile(mentionFinderRegexString) | ||||
|  |  | |||
|  | @ -19,17 +19,18 @@ | |||
| package util | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| // DeriveMentions takes a plaintext (ie., not html-formatted) status, | ||||
| // DeriveMentionsFromStatus takes a plaintext (ie., not html-formatted) status, | ||||
| // and applies a regex to it to return a deduplicated list of accounts | ||||
| // mentioned in that status. | ||||
| // | ||||
| // It will look for fully-qualified account names in the form "@user@example.org". | ||||
| // or the form "@username" for local users. | ||||
| // The case of the returned mentions will be lowered, for consistency. | ||||
| func DeriveMentions(status string) []string { | ||||
| func DeriveMentionsFromStatus(status string) []string { | ||||
| 	mentionedAccounts := []string{} | ||||
| 	for _, m := range mentionFinderRegex.FindAllStringSubmatch(status, -1) { | ||||
| 		mentionedAccounts = append(mentionedAccounts, m[1]) | ||||
|  | @ -37,11 +38,11 @@ func DeriveMentions(status string) []string { | |||
| 	return lower(unique(mentionedAccounts)) | ||||
| } | ||||
| 
 | ||||
| // DeriveHashtags takes a plaintext (ie., not html-formatted) status, | ||||
| // DeriveHashtagsFromStatus takes a plaintext (ie., not html-formatted) status, | ||||
| // and applies a regex to it to return a deduplicated list of hashtags | ||||
| // used in that status, without the leading #. The case of the returned | ||||
| // tags will be lowered, for consistency. | ||||
| func DeriveHashtags(status string) []string { | ||||
| func DeriveHashtagsFromStatus(status string) []string { | ||||
| 	tags := []string{} | ||||
| 	for _, m := range hashtagFinderRegex.FindAllStringSubmatch(status, -1) { | ||||
| 		tags = append(tags, m[1]) | ||||
|  | @ -49,11 +50,11 @@ func DeriveHashtags(status string) []string { | |||
| 	return lower(unique(tags)) | ||||
| } | ||||
| 
 | ||||
| // DeriveEmojis takes a plaintext (ie., not html-formatted) status, | ||||
| // DeriveEmojisFromStatus takes a plaintext (ie., not html-formatted) status, | ||||
| // and applies a regex to it to return a deduplicated list of emojis | ||||
| // used in that status, without the surround ::. The case of the returned | ||||
| // emojis will be lowered, for consistency. | ||||
| func DeriveEmojis(status string) []string { | ||||
| func DeriveEmojisFromStatus(status string) []string { | ||||
| 	emojis := []string{} | ||||
| 	for _, m := range emojiFinderRegex.FindAllStringSubmatch(status, -1) { | ||||
| 		emojis = append(emojis, m[1]) | ||||
|  | @ -61,6 +62,21 @@ func DeriveEmojis(status string) []string { | |||
| 	return lower(unique(emojis)) | ||||
| } | ||||
| 
 | ||||
| // ExtractMentionParts extracts the username test_user and the domain example.org | ||||
| // from a mention string like @test_user@example.org. | ||||
| // | ||||
| // If nothing is matched, it will return an error. | ||||
| func ExtractMentionParts(mention string) (username, domain string, err error) { | ||||
| 	matches := mentionNameRegex.FindStringSubmatch(mention) | ||||
| 	if matches == nil || len(matches) != 3 { | ||||
| 		err = fmt.Errorf("could't match mention %s", mention) | ||||
| 		return | ||||
| 	} | ||||
| 	username = matches[1] | ||||
| 	domain = matches[2] | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // unique returns a deduplicated version of a given string slice. | ||||
| func unique(s []string) []string { | ||||
| 	keys := make(map[string]bool) | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ func (suite *StatusTestSuite) TestDeriveMentionsOK() { | |||
| 	here is a duplicate mention: @hello@test.lgbt | ||||
| 	` | ||||
| 
 | ||||
| 	menchies := util.DeriveMentions(statusText) | ||||
| 	menchies := util.DeriveMentionsFromStatus(statusText) | ||||
| 	assert.Len(suite.T(), menchies, 4) | ||||
| 	assert.Equal(suite.T(), "@dumpsterqueer@example.org", menchies[0]) | ||||
| 	assert.Equal(suite.T(), "@someone_else@testing.best-horse.com", menchies[1]) | ||||
|  | @ -52,7 +52,7 @@ func (suite *StatusTestSuite) TestDeriveMentionsOK() { | |||
| 
 | ||||
| func (suite *StatusTestSuite) TestDeriveMentionsEmpty() { | ||||
| 	statusText := `` | ||||
| 	menchies := util.DeriveMentions(statusText) | ||||
| 	menchies := util.DeriveMentionsFromStatus(statusText) | ||||
| 	assert.Len(suite.T(), menchies, 0) | ||||
| } | ||||
| 
 | ||||
|  | @ -67,7 +67,7 @@ func (suite *StatusTestSuite) TestDeriveHashtagsOK() { | |||
| 
 | ||||
| #111111 thisalsoshouldn'twork#### ##` | ||||
| 
 | ||||
| 	tags := util.DeriveHashtags(statusText) | ||||
| 	tags := util.DeriveHashtagsFromStatus(statusText) | ||||
| 	assert.Len(suite.T(), tags, 5) | ||||
| 	assert.Equal(suite.T(), "testing123", tags[0]) | ||||
| 	assert.Equal(suite.T(), "also", tags[1]) | ||||
|  | @ -90,7 +90,7 @@ Here's some normal text with an :emoji: at the end | |||
| :underscores_ok_too: | ||||
| ` | ||||
| 
 | ||||
| 	tags := util.DeriveEmojis(statusText) | ||||
| 	tags := util.DeriveEmojisFromStatus(statusText) | ||||
| 	assert.Len(suite.T(), tags, 7) | ||||
| 	assert.Equal(suite.T(), "test", tags[0]) | ||||
| 	assert.Equal(suite.T(), "another", tags[1]) | ||||
|  |  | |||
|  | @ -58,9 +58,15 @@ const ( | |||
| 	// APAccount can be used the set and retrieve the account being interacted with | ||||
| 	APAccount APContextKey = "account" | ||||
| 	// APRequestingAccount can be used to set and retrieve the account of an incoming federation request. | ||||
| 	// This will often be the actor of the instance that's posting the request. | ||||
| 	APRequestingAccount APContextKey = "requestingAccount" | ||||
| 	// APRequestingActorIRI can be used to set and retrieve the actor of an incoming federation request. | ||||
| 	// This will usually be the owner of whatever activity is being posted. | ||||
| 	APRequestingActorIRI APContextKey = "requestingActorIRI" | ||||
| 	// APRequestingPublicKeyID can be used to set and retrieve the public key ID of an incoming federation request. | ||||
| 	APRequestingPublicKeyID APContextKey = "requestingPublicKeyID" | ||||
| 	// APFromFederatorChanKey can be used to pass a pointer to the fromFederator channel into the federator for use in callbacks. | ||||
| 	APFromFederatorChanKey APContextKey = "fromFederatorChan" | ||||
| ) | ||||
| 
 | ||||
| type ginContextKey struct{} | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ import ( | |||
| 
 | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/pg" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| ) | ||||
|  | @ -54,7 +55,7 @@ func NewTestDB() db.DB { | |||
| 	config := NewTestConfig() | ||||
| 	l := logrus.New() | ||||
| 	l.SetLevel(logrus.TraceLevel) | ||||
| 	testDB, err := db.NewPostgresService(context.Background(), config, l) | ||||
| 	testDB, err := pg.NewPostgresService(context.Background(), config, l) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ import ( | |||
| 	"github.com/superseriousbusiness/gotosocial/internal/transport" | ||||
| ) | ||||
| 
 | ||||
| // NewTestFederator returns a federator with the given database and (mock!!) transport controller. | ||||
| func NewTestFederator(db db.DB, tc transport.Controller) federation.Federator { | ||||
| 	return federation.NewFederator(db, tc, NewTestConfig(), NewTestLog(), NewTestTypeConverter(db)) | ||||
| } | ||||
|  |  | |||
|  | @ -1037,6 +1037,7 @@ func NewTestFaves() map[string]*gtsmodel.StatusFave { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // ActivityWithSignature wraps a pub.Activity along with its signature headers, for testing. | ||||
| type ActivityWithSignature struct { | ||||
| 	Activity        pub.Activity | ||||
| 	SignatureHeader string | ||||
|  | @ -1076,11 +1077,11 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit | |||
| 
 | ||||
| // NewTestFediPeople returns a bunch of activity pub Person representations for testing converters and so on. | ||||
| func NewTestFediPeople() map[string]typeutils.Accountable { | ||||
| 	new_person_1priv, err := rsa.GenerateKey(rand.Reader, 2048) | ||||
| 	newPerson1Priv, err := rsa.GenerateKey(rand.Reader, 2048) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	new_person_1pub := &new_person_1priv.PublicKey | ||||
| 	newPerson1Pub := &newPerson1Priv.PublicKey | ||||
| 
 | ||||
| 	return map[string]typeutils.Accountable{ | ||||
| 		"new_person_1": newPerson( | ||||
|  | @ -1096,7 +1097,7 @@ func NewTestFediPeople() map[string]typeutils.Accountable { | |||
| 			URLMustParse("https://unknown-instance.com/@brand_new_person"), | ||||
| 			true, | ||||
| 			URLMustParse("https://unknown-instance.com/users/brand_new_person#main-key"), | ||||
| 			new_person_1pub, | ||||
| 			newPerson1Pub, | ||||
| 			URLMustParse("https://unknown-instance.com/media/some_avatar_filename.jpeg"), | ||||
| 			"image/jpeg", | ||||
| 			URLMustParse("https://unknown-instance.com/media/some_header_filename.jpeg"), | ||||
|  | @ -1105,6 +1106,7 @@ func NewTestFediPeople() map[string]typeutils.Accountable { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // NewTestDereferenceRequests returns a map of incoming dereference requests, with their signatures. | ||||
| func NewTestDereferenceRequests(accounts map[string]*gtsmodel.Account) map[string]ActivityWithSignature { | ||||
| 	sig, digest, date := getSignatureForDereference(accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].URI)) | ||||
| 	return map[string]ActivityWithSignature{ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue