diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/api/client/followrequest/get_test.go | 2 | ||||
| -rw-r--r-- | internal/api/client/instance/instancepatch_test.go | 8 | ||||
| -rw-r--r-- | internal/config/validate.go | 5 | ||||
| -rw-r--r-- | internal/config/validate_test.go | 9 | ||||
| -rw-r--r-- | internal/processing/streaming/notification_test.go | 2 | ||||
| -rw-r--r-- | internal/typeutils/converter.go | 10 | ||||
| -rw-r--r-- | internal/typeutils/defaulticons.go | 138 | ||||
| -rw-r--r-- | internal/typeutils/internaltofrontend.go | 3 | ||||
| -rw-r--r-- | internal/typeutils/internaltofrontend_test.go | 4 | ||||
| -rw-r--r-- | internal/web/assets.go | 10 | ||||
| -rw-r--r-- | internal/web/assetscache.go | 2 | ||||
| -rw-r--r-- | internal/web/panels.go | 28 | ||||
| -rw-r--r-- | internal/web/profile.go | 16 | ||||
| -rw-r--r-- | internal/web/thread.go | 26 | ||||
| -rw-r--r-- | internal/web/web.go | 73 | 
15 files changed, 203 insertions, 133 deletions
diff --git a/internal/api/client/followrequest/get_test.go b/internal/api/client/followrequest/get_test.go index 2af9c7744..adf55c8b6 100644 --- a/internal/api/client/followrequest/get_test.go +++ b/internal/api/client/followrequest/get_test.go @@ -70,7 +70,7 @@ func (suite *GetTestSuite) TestGet() {  	b, err := ioutil.ReadAll(result.Body)  	assert.NoError(suite.T(), err) -	suite.Equal(`[{"id":"01FHMQX3GAABWSM0S2VZEC2SWC","username":"some_user","acct":"some_user@example.org","display_name":"some user","locked":true,"bot":false,"created_at":"2020-08-10T12:13:28.000Z","note":"i'm a real son of a gun","url":"http://example.org/@some_user","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":0,"following_count":0,"statuses_count":0,"last_status_at":"","emojis":[],"fields":[]}]`, string(b)) +	suite.Equal(`[{"id":"01FHMQX3GAABWSM0S2VZEC2SWC","username":"some_user","acct":"some_user@example.org","display_name":"some user","locked":true,"bot":false,"created_at":"2020-08-10T12:13:28.000Z","note":"i'm a real son of a gun","url":"http://example.org/@some_user","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":0,"following_count":0,"statuses_count":0,"last_status_at":"","emojis":[],"fields":[]}]`, string(b))  }  func TestGetTestSuite(t *testing.T) { diff --git a/internal/api/client/instance/instancepatch_test.go b/internal/api/client/instance/instancepatch_test.go index c44f8d093..4ecf0d62c 100644 --- a/internal/api/client/instance/instancepatch_test.go +++ b/internal/api/client/instance/instancepatch_test.go @@ -63,7 +63,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {  	b, err := io.ReadAll(result.Body)  	suite.NoError(err) -	suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"Example Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"someone@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":999},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b)) +	suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"Example Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"someone@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":999},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b))  }  func (suite *InstancePatchTestSuite) TestInstancePatch2() { @@ -93,7 +93,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {  	b, err := io.ReadAll(result.Body)  	suite.NoError(err) -	suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"Geoff's Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"admin@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":999},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b)) +	suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"Geoff's Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"admin@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":999},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b))  }  func (suite *InstancePatchTestSuite) TestInstancePatch3() { @@ -123,7 +123,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {  	b, err := io.ReadAll(result.Body)  	suite.NoError(err) -	suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"GoToSocial Testrig Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is some html, which is \u003cem\u003eallowed\u003c/em\u003e in short descriptions.\u003c/p\u003e","email":"admin@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":999},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b)) +	suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"GoToSocial Testrig Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is some html, which is \u003cem\u003eallowed\u003c/em\u003e in short descriptions.\u003c/p\u003e","email":"admin@example.org","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":999},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b))  }  func (suite *InstancePatchTestSuite) TestInstancePatch4() { @@ -214,7 +214,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {  	b, err := io.ReadAll(result.Body)  	suite.NoError(err) -	suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"GoToSocial Testrig Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":999},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b)) +	suite.Equal(`{"uri":"http://localhost:8080","account_domain":"localhost:8080","title":"GoToSocial Testrig Instance","description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","short_description":"\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e","email":"","version":"0.0.0-testrig","registrations":true,"approval_required":true,"invites_enabled":false,"configuration":{"statuses":{"max_characters":5000,"max_media_attachments":6,"characters_reserved_per_url":999},"media_attachments":{"supported_mime_types":["image/jpeg","image/gif","image/png"],"image_size_limit":10485760,"image_matrix_limit":16777216,"video_size_limit":41943040,"video_frame_rate_limit":60,"video_matrix_limit":16777216},"polls":{"max_options":6,"max_characters_per_option":50,"min_expiration":300,"max_expiration":2629746}},"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":2,"status_count":16,"user_count":4},"thumbnail":"http://localhost:8080/assets/logo.png","contact_account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"max_toot_chars":5000}`, string(b))  }  func (suite *InstancePatchTestSuite) TestInstancePatch7() { diff --git a/internal/config/validate.go b/internal/config/validate.go index b68cb2c62..064eae07a 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -62,6 +62,11 @@ func Validate() error {  		errs = append(errs, fmt.Errorf("%s must be set to either http or https, provided value was %s", ProtocolFlag(), proto))  	} +	webAssetsBaseDir := GetWebAssetBaseDir() +	if webAssetsBaseDir == "" { +		errs = append(errs, fmt.Errorf("%s must be set", WebAssetBaseDirFlag())) +	} +  	if len(errs) > 0 {  		errStrings := []string{}  		for _, err := range errs { diff --git a/internal/config/validate_test.go b/internal/config/validate_test.go index 6b832de36..c3a998a4a 100644 --- a/internal/config/validate_test.go +++ b/internal/config/validate_test.go @@ -103,6 +103,15 @@ func (suite *ConfigValidateTestSuite) TestValidateConfigNoProtocol() {  	suite.EqualError(err, "protocol must be set")  } +func (suite *ConfigValidateTestSuite) TestValidateConfigNoWebAssetBaseDir() { +	testrig.InitTestConfig() + +	config.SetWebAssetBaseDir("") + +	err := config.Validate() +	suite.EqualError(err, "web-asset-base-dir must be set") +} +  func (suite *ConfigValidateTestSuite) TestValidateConfigNoProtocolOrHost() {  	testrig.InitTestConfig() diff --git a/internal/processing/streaming/notification_test.go b/internal/processing/streaming/notification_test.go index 960d9408d..0017dac04 100644 --- a/internal/processing/streaming/notification_test.go +++ b/internal/processing/streaming/notification_test.go @@ -52,7 +52,7 @@ func (suite *NotificationTestSuite) TestStreamNotification() {  	suite.NoError(err)  	msg := <-openStream.Messages -	suite.Equal(`{"id":"01FH57SJCMDWQGEAJ0X08CE3WV","type":"follow","created_at":"2021-10-04T08:52:36.000Z","account":{"id":"01F8MH5ZK5VRH73AKHQM6Y9VNX","username":"foss_satan","acct":"foss_satan@fossbros-anonymous.io","display_name":"big gerald","locked":false,"bot":false,"created_at":"2021-09-26T10:52:36.000Z","note":"i post about like, i dunno, stuff, or whatever!!!!","url":"http://fossbros-anonymous.io/@foss_satan","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":0,"following_count":0,"statuses_count":1,"last_status_at":"2021-09-20T10:40:37.000Z","emojis":[],"fields":[]}}`, msg.Payload) +	suite.Equal(`{"id":"01FH57SJCMDWQGEAJ0X08CE3WV","type":"follow","created_at":"2021-10-04T08:52:36.000Z","account":{"id":"01F8MH5ZK5VRH73AKHQM6Y9VNX","username":"foss_satan","acct":"foss_satan@fossbros-anonymous.io","display_name":"big gerald","locked":false,"bot":false,"created_at":"2021-09-26T10:52:36.000Z","note":"i post about like, i dunno, stuff, or whatever!!!!","url":"http://fossbros-anonymous.io/@foss_satan","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":0,"following_count":0,"statuses_count":1,"last_status_at":"2021-09-20T10:40:37.000Z","emojis":[],"fields":[]}}`, msg.Payload)  }  func TestNotificationTestSuite(t *testing.T) { diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index 6996599ae..3effb9388 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -21,6 +21,7 @@ package typeutils  import (  	"context"  	"net/url" +	"sync"  	"github.com/superseriousbusiness/activity/streams/vocab"  	"github.com/superseriousbusiness/gotosocial/internal/ap" @@ -195,10 +196,15 @@ type TypeConverter interface {  }  type converter struct { -	db db.DB +	db             db.DB +	defaultAvatars []string +	randAvatars    sync.Map  }  // NewConverter returns a new Converter  func NewConverter(db db.DB) TypeConverter { -	return &converter{db: db} +	return &converter{ +		db:             db, +		defaultAvatars: populateDefaultAvatars(), +	}  } diff --git a/internal/typeutils/defaulticons.go b/internal/typeutils/defaulticons.go new file mode 100644 index 000000000..b2a9858af --- /dev/null +++ b/internal/typeutils/defaulticons.go @@ -0,0 +1,138 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 ( +	"io/ioutil" +	"math/rand" +	"path/filepath" +	"strings" + +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/config" +	"github.com/superseriousbusiness/gotosocial/internal/log" +) + +const defaultHeaderPath = "/assets/default_header.png" + +// populateDefaultAvatars returns a slice of standard avatars found +// in the path [web-assets-base-dir]/default_avatars. The slice +// entries correspond to the relative url via which they can be +// retrieved from the server. +// +// So for example, an avatar called default.jpeg would be returned +// in the slice as "/assets/default_avatars/default.jpeg". +func populateDefaultAvatars() (defaultAvatars []string) { +	webAssetsAbsFilePath, err := filepath.Abs(config.GetWebAssetBaseDir()) +	if err != nil { +		log.Panicf("populateDefaultAvatars: error getting abs path for web assets: %s", err) +	} + +	defaultAvatarsAbsFilePath := filepath.Join(webAssetsAbsFilePath, "default_avatars") +	defaultAvatarFiles, err := ioutil.ReadDir(defaultAvatarsAbsFilePath) +	if err != nil { +		log.Warnf("populateDefaultAvatars: error reading default avatars at %s: %s", defaultAvatarsAbsFilePath, err) +		return +	} + +	for _, f := range defaultAvatarFiles { +		// ignore directories +		if f.IsDir() { +			continue +		} + +		// ignore files bigger than 50kb +		if f.Size() > 50000 { +			continue +		} + +		// get the name of the file, eg avatar.jpeg +		fileName := f.Name() + +		// get just the .jpeg, for example, from avatar.jpeg +		extensionWithDot := filepath.Ext(fileName) + +		// remove the leading . to just get, eg, jpeg +		extension := strings.TrimPrefix(extensionWithDot, ".") + +		// take only files with simple extensions +		// that we know will work OK as avatars +		switch strings.ToLower(extension) { +		case "jpeg", "jpg", "gif", "png": +			avatarURL := config.GetProtocol() + "://" + config.GetHost() + "/assets/default_avatars/" + fileName +			defaultAvatars = append(defaultAvatars, avatarURL) +		default: +			continue +		} +	} + +	return +} + +// ensureAvatar ensures that the given account has a value set +// for the avatar URL. +// +// If no value is set, an avatar will be selected at random from +// the available default avatars. This selection is 'sticky', so +// the same account will get the same result on subsequent calls. +// +// If a value for the avatar URL is already set, this function is +// a no-op. +// +// If there are no default avatars available, this function is a +// no-op. +func (c *converter) ensureAvatar(account *apimodel.Account) { +	if (account.Avatar != "" && account.AvatarStatic != "") || len(c.defaultAvatars) == 0 { +		return +	} + +	var avatar string +	if avatarI, ok := c.randAvatars.Load(account.ID); ok { +		// we already have a default avatar stored for this account +		avatar, ok = avatarI.(string) +		if !ok { +			panic("avatarI was not a string") +		} +	} else { +		// select + store a default avatar for this account at random +		randomIndex := rand.Intn(len(c.defaultAvatars)) //nolint:gosec +		avatar = c.defaultAvatars[randomIndex] +		c.randAvatars.Store(account.ID, avatar) +	} + +	account.Avatar = avatar +	account.AvatarStatic = avatar +} + +// EnsureAvatar ensures that the given account has a value set +// for the header URL. +// +// If no value is set, the default header will be set. +// +// If a value for the header URL is already set, this function is +// a no-op. +func (c *converter) ensureHeader(account *apimodel.Account) { +	if account.Header != "" && account.HeaderStatic != "" { +		return +	} + +	h := config.GetProtocol() + "://" + config.GetHost() + defaultHeaderPath +	account.Header = h +	account.HeaderStatic = h +} diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 6ccbed340..0243e4732 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -190,6 +190,9 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A  		Suspended:      suspended,  	} +	c.ensureAvatar(accountFrontend) +	c.ensureHeader(accountFrontend) +  	return accountFrontend, nil  } diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go index 648c90fba..dc92260e1 100644 --- a/internal/typeutils/internaltofrontend_test.go +++ b/internal/typeutils/internaltofrontend_test.go @@ -63,7 +63,7 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() {  	b, err := json.Marshal(apiStatus)  	suite.NoError(err) -	suite.Equal(`{"id":"01F8MH75CBF9JFX4ZAD54N0W0R","created_at":"2021-10-20T11:36:45.000Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","url":"http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","replies_count":0,"reblogs_count":0,"favourites_count":1,"favourited":true,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"hello world! #welcome ! first post on the instance :rainbow: !","reblog":null,"application":{"name":"superseriousbusiness","website":"https://superserious.business"},"account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"media_attachments":[{"id":"01F8MH6NEM8D7527KZAECTCR76","type":"image","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","text_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","preview_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpeg","remote_url":null,"preview_remote_url":null,"meta":{"original":{"width":1200,"height":630,"size":"1200x630","aspect":1.9047619},"small":{"width":256,"height":134,"size":"256x134","aspect":1.9104477},"focus":{"x":0,"y":0}},"description":"Black and white image of some 50's style text saying: Welcome On Board","blurhash":"LNJRdVM{00Rj%Mayt7j[4nWBofRj"}],"mentions":[],"tags":[{"name":"welcome","url":"http://localhost:8080/tags/welcome"}],"emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"card":null,"poll":null}`, string(b)) +	suite.Equal(`{"id":"01F8MH75CBF9JFX4ZAD54N0W0R","created_at":"2021-10-20T11:36:45.000Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","url":"http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","replies_count":0,"reblogs_count":0,"favourites_count":1,"favourited":true,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"hello world! #welcome ! first post on the instance :rainbow: !","reblog":null,"application":{"name":"superseriousbusiness","website":"https://superserious.business"},"account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"media_attachments":[{"id":"01F8MH6NEM8D7527KZAECTCR76","type":"image","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","text_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","preview_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpeg","remote_url":null,"preview_remote_url":null,"meta":{"original":{"width":1200,"height":630,"size":"1200x630","aspect":1.9047619},"small":{"width":256,"height":134,"size":"256x134","aspect":1.9104477},"focus":{"x":0,"y":0}},"description":"Black and white image of some 50's style text saying: Welcome On Board","blurhash":"LNJRdVM{00Rj%Mayt7j[4nWBofRj"}],"mentions":[],"tags":[{"name":"welcome","url":"http://localhost:8080/tags/welcome"}],"emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"card":null,"poll":null}`, string(b))  }  func (suite *InternalToFrontendTestSuite) TestInstanceToFrontend() { @@ -108,7 +108,7 @@ func (suite *InternalToFrontendTestSuite) TestInstanceToFrontendWithAdminAccount  	b, err := json.Marshal(apiInstance)  	suite.NoError(err) -	suite.Equal(`{"uri":"https://example.org","title":"example instance","description":"a much longer description","short_description":"a little description","email":"someone@example.org","version":"software-from-hell 0.666","registrations":false,"approval_required":false,"invites_enabled":false,"thumbnail":"","contact_account":{"id":"01FHMQX3GAABWSM0S2VZEC2SWC","username":"some_user","acct":"some_user@example.org","display_name":"some user","locked":true,"bot":false,"created_at":"2020-08-10T12:13:28.000Z","note":"i'm a real son of a gun","url":"http://example.org/@some_user","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":0,"following_count":0,"statuses_count":0,"last_status_at":"","emojis":[],"fields":[]},"max_toot_chars":0}`, string(b)) +	suite.Equal(`{"uri":"https://example.org","title":"example instance","description":"a much longer description","short_description":"a little description","email":"someone@example.org","version":"software-from-hell 0.666","registrations":false,"approval_required":false,"invites_enabled":false,"thumbnail":"","contact_account":{"id":"01FHMQX3GAABWSM0S2VZEC2SWC","username":"some_user","acct":"some_user@example.org","display_name":"some user","locked":true,"bot":false,"created_at":"2020-08-10T12:13:28.000Z","note":"i'm a real son of a gun","url":"http://example.org/@some_user","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":0,"following_count":0,"statuses_count":0,"last_status_at":"","emojis":[],"fields":[]},"max_toot_chars":0}`, string(b))  }  func TestInternalToFrontendTestSuite(t *testing.T) { diff --git a/internal/web/assets.go b/internal/web/assets.go index f67f38363..397870862 100644 --- a/internal/web/assets.go +++ b/internal/web/assets.go @@ -20,9 +20,12 @@ package web  import (  	"net/http" +	"path/filepath"  	"strings"  	"github.com/gin-gonic/gin" +	"github.com/superseriousbusiness/gotosocial/internal/config" +	"github.com/superseriousbusiness/gotosocial/internal/log"  )  type fileSystem struct { @@ -49,7 +52,12 @@ func (fs fileSystem) Open(path string) (http.File, error) {  }  func (m *Module) mountAssetsFilesystem(group *gin.RouterGroup) { -	fs := fileSystem{http.Dir(m.webAssetsAbsFilePath)} +	webAssetsAbsFilePath, err := filepath.Abs(config.GetWebAssetBaseDir()) +	if err != nil { +		log.Panicf("mountAssetsFilesystem: error getting absolute path of assets dir: %s", err) +	} + +	fs := fileSystem{http.Dir(webAssetsAbsFilePath)}  	// use the cache middleware on all handlers in this group  	group.Use(m.cacheControlMiddleware(fs)) diff --git a/internal/web/assetscache.go b/internal/web/assetscache.go index 57a0ade0b..fccc95993 100644 --- a/internal/web/assetscache.go +++ b/internal/web/assetscache.go @@ -114,7 +114,7 @@ func (m *Module) cacheControlMiddleware(fs http.FileSystem) gin.HandlerFunc {  		if !strings.HasPrefix(upath, "/") {  			upath = "/" + upath  		} -		assetFilePath := strings.TrimPrefix(path.Clean(upath), assetsPath) +		assetFilePath := strings.TrimPrefix(path.Clean(upath), assetsPathPrefix)  		// either fetch etag from ttlcache or generate it  		eTag, err := m.getAssetETag(assetFilePath, fs) diff --git a/internal/web/panels.go b/internal/web/panels.go index dafd0abc7..fdec87a33 100644 --- a/internal/web/panels.go +++ b/internal/web/panels.go @@ -38,15 +38,15 @@ func (m *Module) UserPanelHandler(c *gin.Context) {  	c.HTML(http.StatusOK, "frontend.tmpl", gin.H{  		"instance": instance,  		"stylesheets": []string{ -			assetsPath + "/Fork-Awesome/css/fork-awesome.min.css", -			assetsPath + "/dist/_colors.css", -			assetsPath + "/dist/base.css", -			assetsPath + "/dist/panels-base.css", -			assetsPath + "/dist/panels-user-style.css", +			assetsPathPrefix + "/Fork-Awesome/css/fork-awesome.min.css", +			assetsPathPrefix + "/dist/_colors.css", +			assetsPathPrefix + "/dist/base.css", +			assetsPathPrefix + "/dist/panels-base.css", +			assetsPathPrefix + "/dist/panels-user-style.css",  		},  		"javascript": []string{ -			assetsPath + "/dist/bundle.js", -			assetsPath + "/dist/user-panel.js", +			assetsPathPrefix + "/dist/bundle.js", +			assetsPathPrefix + "/dist/user-panel.js",  		},  	})  } @@ -63,15 +63,15 @@ func (m *Module) AdminPanelHandler(c *gin.Context) {  	c.HTML(http.StatusOK, "frontend.tmpl", gin.H{  		"instance": instance,  		"stylesheets": []string{ -			assetsPath + "/Fork-Awesome/css/fork-awesome.min.css", -			assetsPath + "/dist/_colors.css", -			assetsPath + "/dist/base.css", -			assetsPath + "/dist/panels-base.css", -			assetsPath + "/dist/panels-admin-style.css", +			assetsPathPrefix + "/Fork-Awesome/css/fork-awesome.min.css", +			assetsPathPrefix + "/dist/_colors.css", +			assetsPathPrefix + "/dist/base.css", +			assetsPathPrefix + "/dist/panels-base.css", +			assetsPathPrefix + "/dist/panels-admin-style.css",  		},  		"javascript": []string{ -			assetsPath + "/dist/bundle.js", -			assetsPath + "/dist/admin-panel.js", +			assetsPathPrefix + "/dist/bundle.js", +			assetsPathPrefix + "/dist/admin-panel.js",  		},  	})  } diff --git a/internal/web/profile.go b/internal/web/profile.go index 542c015f1..61f7c57e7 100644 --- a/internal/web/profile.go +++ b/internal/web/profile.go @@ -23,7 +23,6 @@ import (  	"encoding/json"  	"errors"  	"fmt" -	"math/rand"  	"net/http"  	"strings" @@ -100,21 +99,6 @@ func (m *Module) profileGETHandler(c *gin.Context) {  		return  	} -	// pick a random dummy avatar if this account avatar isn't set yet -	if account.Avatar == "" && len(m.defaultAvatars) > 0 { -		//nolint:gosec -		randomIndex := rand.Intn(len(m.defaultAvatars)) -		dummyAvatar := m.defaultAvatars[randomIndex] -		account.Avatar = dummyAvatar -		for _, i := range statusResp.Items { -			s, ok := i.(*apimodel.Status) -			if !ok { -				panic("timelineable was not *apimodel.Status") -			} -			s.Account.Avatar = dummyAvatar -		} -	} -  	c.HTML(http.StatusOK, "profile.tmpl", gin.H{  		"instance":         instance,  		"account":          account, diff --git a/internal/web/thread.go b/internal/web/thread.go index d3b92bde3..3db4952c7 100644 --- a/internal/web/thread.go +++ b/internal/web/thread.go @@ -23,7 +23,6 @@ import (  	"encoding/json"  	"errors"  	"fmt" -	"math/rand"  	"net/http"  	"strings" @@ -36,21 +35,6 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) -var randAvatars = make(map[string]string) - -func (m *Module) ensureAvatar(status apimodel.Status) { -	if status.Account.Avatar == "" && len(m.defaultAvatars) > 0 { -		avatar, ok := randAvatars[status.Account.ID] -		if !ok { -			//nolint:gosec -			randomIndex := rand.Intn(len(m.defaultAvatars)) -			avatar = m.defaultAvatars[randomIndex] -			randAvatars[status.Account.ID] = avatar -		} -		status.Account.Avatar = avatar -	} -} -  func (m *Module) threadGETHandler(c *gin.Context) {  	ctx := c.Request.Context() @@ -120,16 +104,6 @@ func (m *Module) threadGETHandler(c *gin.Context) {  		return  	} -	m.ensureAvatar(*status) - -	for _, status := range context.Descendants { -		m.ensureAvatar(status) -	} - -	for _, status := range context.Ancestors { -		m.ensureAvatar(status) -	} -  	c.HTML(http.StatusOK, "thread.tmpl", gin.H{  		"instance": instance,  		"status":   status, diff --git a/internal/web/web.go b/internal/web/web.go index fe270ac6c..336525938 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -20,17 +20,12 @@ package web  import (  	"errors" -	"fmt" -	"io/ioutil"  	"net/http" -	"path/filepath" -	"strings"  	"time"  	"codeberg.org/gruf/go-cache/v2"  	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/processing"  	"github.com/superseriousbusiness/gotosocial/internal/router" @@ -43,7 +38,7 @@ const (  	statusPath       = profilePath + "/statuses/:" + statusIDKey  	adminPanelPath   = "/admin"  	userPanelpath    = "/user" -	assetsPath       = "/assets" +	assetsPathPrefix = "/assets"  	tokenParam  = "token"  	usernameKey = "username" @@ -52,78 +47,26 @@ const (  // Module implements the api.ClientModule interface for web pages.  type Module struct { -	processor            processing.Processor -	webAssetsAbsFilePath string -	assetsETagCache      cache.Cache[string, eTagCacheEntry] -	defaultAvatars       []string +	processor       processing.Processor +	assetsETagCache cache.Cache[string, eTagCacheEntry]  }  // New returns a new api.ClientModule for web pages. -func New(processor processing.Processor) (api.ClientModule, error) { -	webAssetsBaseDir := config.GetWebAssetBaseDir() -	if webAssetsBaseDir == "" { -		return nil, fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.WebAssetBaseDirFlag()) -	} - -	webAssetsAbsFilePath, err := filepath.Abs(webAssetsBaseDir) -	if err != nil { -		return nil, fmt.Errorf("error getting absolute path of %s: %s", webAssetsBaseDir, err) -	} - -	defaultAvatarsAbsFilePath := filepath.Join(webAssetsAbsFilePath, "default_avatars") -	defaultAvatarFiles, err := ioutil.ReadDir(defaultAvatarsAbsFilePath) -	if err != nil { -		return nil, fmt.Errorf("error reading default avatars at %s: %s", defaultAvatarsAbsFilePath, err) -	} - -	defaultAvatars := []string{} -	for _, f := range defaultAvatarFiles { -		// ignore directories -		if f.IsDir() { -			continue -		} - -		// ignore files bigger than 50kb -		if f.Size() > 50000 { -			continue -		} - -		// get the name of the file, eg avatar.jpeg -		fileName := f.Name() - -		// get just the .jpeg, for example, from avatar.jpeg -		extensionWithDot := filepath.Ext(fileName) - -		// remove the leading . to just get, eg, jpeg -		extension := strings.TrimPrefix(extensionWithDot, ".") - -		// take only files with simple extensions -		// that we know will work OK as avatars -		switch strings.ToLower(extension) { -		case "svg", "jpeg", "jpg", "gif", "png": -			avatar := fmt.Sprintf("%s/default_avatars/%s", assetsPath, f.Name()) -			defaultAvatars = append(defaultAvatars, avatar) -		default: -			continue -		} -	} - +func New(processor processing.Processor) api.ClientModule {  	assetsETagCache := cache.New[string, eTagCacheEntry]()  	assetsETagCache.SetTTL(time.Hour, false)  	assetsETagCache.Start(time.Minute)  	return &Module{ -		processor:            processor, -		webAssetsAbsFilePath: webAssetsAbsFilePath, -		assetsETagCache:      assetsETagCache, -		defaultAvatars:       defaultAvatars, -	}, nil +		processor:       processor, +		assetsETagCache: assetsETagCache, +	}  }  // Route satisfies the RESTAPIModule interface  func (m *Module) Route(s router.Router) error {  	// serve static files from assets dir at /assets -	assetsGroup := s.AttachGroup(assetsPath) +	assetsGroup := s.AttachGroup(assetsPathPrefix)  	m.mountAssetsFilesystem(assetsGroup)  	s.AttachHandler(http.MethodGet, adminPanelPath, m.AdminPanelHandler)  | 
