mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 11:52:24 -05:00 
			
		
		
		
	[feature] Support setting private notes on accounts (#1982)
* Support setting private notes on accounts * Reformat comment whitespace * Add missing license headers * Use apiutil.ParseID * Rename Note model and cache to AccountNote * Update golden cache config in test/envparsing.sh * Rename gtsmodel/note.go to gtsmodel/accountnote.go * Update AccountNote uniqueness constraint name Now has same prefix as other indexes on this table. --------- Co-authored-by: tobi <31960611+tsmethurst@users.noreply.github.com>
This commit is contained in:
		
					parent
					
						
							
								5f3e095717
							
						
					
				
			
			
				commit
				
					
						22ac4607a1
					
				
			
		
					 19 changed files with 597 additions and 2 deletions
				
			
		|  | @ -2944,6 +2944,45 @@ paths: | ||||||
|             summary: See all lists of yours that contain requested account. |             summary: See all lists of yours that contain requested account. | ||||||
|             tags: |             tags: | ||||||
|                 - accounts |                 - accounts | ||||||
|  |     /api/v1/accounts/{id}/note: | ||||||
|  |         post: | ||||||
|  |             consumes: | ||||||
|  |                 - multipart/form-data | ||||||
|  |             operationId: accountNote | ||||||
|  |             parameters: | ||||||
|  |                 - description: The id of the account for which to set a note. | ||||||
|  |                   in: path | ||||||
|  |                   name: id | ||||||
|  |                   required: true | ||||||
|  |                   type: string | ||||||
|  |                 - default: "" | ||||||
|  |                   description: The text of the note. Omit this parameter or send an empty string to clear the note. | ||||||
|  |                   in: formData | ||||||
|  |                   name: comment | ||||||
|  |                   type: string | ||||||
|  |             produces: | ||||||
|  |                 - application/json | ||||||
|  |             responses: | ||||||
|  |                 "200": | ||||||
|  |                     description: Your relationship to the account. | ||||||
|  |                     schema: | ||||||
|  |                         $ref: '#/definitions/accountRelationship' | ||||||
|  |                 "400": | ||||||
|  |                     description: bad request | ||||||
|  |                 "401": | ||||||
|  |                     description: unauthorized | ||||||
|  |                 "404": | ||||||
|  |                     description: not found | ||||||
|  |                 "406": | ||||||
|  |                     description: not acceptable | ||||||
|  |                 "500": | ||||||
|  |                     description: internal server error | ||||||
|  |             security: | ||||||
|  |                 - OAuth2 Bearer: | ||||||
|  |                     - write:accounts | ||||||
|  |             summary: Set a private note for an account with the given id. | ||||||
|  |             tags: | ||||||
|  |                 - accounts | ||||||
|     /api/v1/accounts/{id}/statuses: |     /api/v1/accounts/{id}/statuses: | ||||||
|         get: |         get: | ||||||
|             description: The statuses will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer). |             description: The statuses will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer). | ||||||
|  |  | ||||||
|  | @ -45,6 +45,7 @@ const ( | ||||||
| 	FollowPath        = BasePathWithID + "/follow" | 	FollowPath        = BasePathWithID + "/follow" | ||||||
| 	ListsPath         = BasePathWithID + "/lists" | 	ListsPath         = BasePathWithID + "/lists" | ||||||
| 	LookupPath        = BasePath + "/lookup" | 	LookupPath        = BasePath + "/lookup" | ||||||
|  | 	NotePath          = BasePathWithID + "/note" | ||||||
| 	RelationshipsPath = BasePath + "/relationships" | 	RelationshipsPath = BasePath + "/relationships" | ||||||
| 	SearchPath        = BasePath + "/search" | 	SearchPath        = BasePath + "/search" | ||||||
| 	StatusesPath      = BasePathWithID + "/statuses" | 	StatusesPath      = BasePathWithID + "/statuses" | ||||||
|  | @ -101,6 +102,9 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H | ||||||
| 	// account lists | 	// account lists | ||||||
| 	attachHandler(http.MethodGet, ListsPath, m.AccountListsGETHandler) | 	attachHandler(http.MethodGet, ListsPath, m.AccountListsGETHandler) | ||||||
| 
 | 
 | ||||||
|  | 	// account note | ||||||
|  | 	attachHandler(http.MethodPost, NotePath, m.AccountNotePOSTHandler) | ||||||
|  | 
 | ||||||
| 	// search for accounts | 	// search for accounts | ||||||
| 	attachHandler(http.MethodGet, SearchPath, m.AccountSearchGETHandler) | 	attachHandler(http.MethodGet, SearchPath, m.AccountSearchGETHandler) | ||||||
| 	attachHandler(http.MethodGet, LookupPath, m.AccountLookupGETHandler) | 	attachHandler(http.MethodGet, LookupPath, m.AccountLookupGETHandler) | ||||||
|  |  | ||||||
							
								
								
									
										108
									
								
								internal/api/client/accounts/note.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								internal/api/client/accounts/note.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // 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 accounts | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 
 | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
|  | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
|  | 	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // AccountNotePOSTHandler swagger:operation POST /api/v1/accounts/{id}/note accountNote | ||||||
|  | // | ||||||
|  | // Set a private note for an account with the given id. | ||||||
|  | // | ||||||
|  | //	--- | ||||||
|  | //	tags: | ||||||
|  | //	- accounts | ||||||
|  | // | ||||||
|  | //	consumes: | ||||||
|  | //	- multipart/form-data | ||||||
|  | // | ||||||
|  | //	produces: | ||||||
|  | //	- application/json | ||||||
|  | // | ||||||
|  | //	parameters: | ||||||
|  | //	- | ||||||
|  | //		name: id | ||||||
|  | //		type: string | ||||||
|  | //		description: The id of the account for which to set a note. | ||||||
|  | //		in: path | ||||||
|  | //		required: true | ||||||
|  | //	- | ||||||
|  | //		name: comment | ||||||
|  | //		type: string | ||||||
|  | //		description: The text of the note. Omit this parameter or send an empty string to clear the note. | ||||||
|  | //		in: formData | ||||||
|  | //		default: "" | ||||||
|  | // | ||||||
|  | //	security: | ||||||
|  | //	- OAuth2 Bearer: | ||||||
|  | //		- write:accounts | ||||||
|  | // | ||||||
|  | //	responses: | ||||||
|  | //		'200': | ||||||
|  | //			description: Your relationship to the account. | ||||||
|  | //			schema: | ||||||
|  | //				"$ref": "#/definitions/accountRelationship" | ||||||
|  | //		'400': | ||||||
|  | //			description: bad request | ||||||
|  | //		'401': | ||||||
|  | //			description: unauthorized | ||||||
|  | //		'404': | ||||||
|  | //			description: not found | ||||||
|  | //		'406': | ||||||
|  | //			description: not acceptable | ||||||
|  | //		'500': | ||||||
|  | //			description: internal server error | ||||||
|  | func (m *Module) AccountNotePOSTHandler(c *gin.Context) { | ||||||
|  | 	authed, err := oauth.Authed(c, true, true, true, true) | ||||||
|  | 	if err != nil { | ||||||
|  | 		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { | ||||||
|  | 		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	targetAcctID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) | ||||||
|  | 	if errWithCode != nil { | ||||||
|  | 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	form := &apimodel.AccountNoteRequest{} | ||||||
|  | 	if err := c.ShouldBind(form); err != nil { | ||||||
|  | 		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	relationship, errWithCode := m.processor.Account().PutNote(c.Request.Context(), authed.Account, targetAcctID, form.Comment) | ||||||
|  | 	if errWithCode != nil { | ||||||
|  | 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	c.JSON(http.StatusOK, relationship) | ||||||
|  | } | ||||||
|  | @ -231,3 +231,11 @@ const ( | ||||||
| 	AccountRoleAdmin     AccountRoleName = "admin"     // Instance admin | 	AccountRoleAdmin     AccountRoleName = "admin"     // Instance admin | ||||||
| 	AccountRoleUnknown   AccountRoleName = ""          // We don't know / remote account | 	AccountRoleUnknown   AccountRoleName = ""          // We don't know / remote account | ||||||
| ) | ) | ||||||
|  | 
 | ||||||
|  | // AccountNoteRequest models a request to update the private note for an account. | ||||||
|  | // | ||||||
|  | // swagger:ignore | ||||||
|  | type AccountNoteRequest struct { | ||||||
|  | 	// Comment to use for the note text. | ||||||
|  | 	Comment string `form:"comment" json:"comment" xml:"comment"` | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								internal/cache/gts.go
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								internal/cache/gts.go
									
										
									
									
										vendored
									
									
								
							|  | @ -27,6 +27,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| type GTSCaches struct { | type GTSCaches struct { | ||||||
| 	account     *result.Cache[*gtsmodel.Account] | 	account     *result.Cache[*gtsmodel.Account] | ||||||
|  | 	accountNote *result.Cache[*gtsmodel.AccountNote] | ||||||
| 	block       *result.Cache[*gtsmodel.Block] | 	block       *result.Cache[*gtsmodel.Block] | ||||||
| 	// TODO: maybe should be moved out of here since it's | 	// TODO: maybe should be moved out of here since it's | ||||||
| 	// not actually doing anything with gtsmodel.DomainBlock. | 	// not actually doing anything with gtsmodel.DomainBlock. | ||||||
|  | @ -54,6 +55,7 @@ type GTSCaches struct { | ||||||
| // NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe. | // NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe. | ||||||
| func (c *GTSCaches) Init() { | func (c *GTSCaches) Init() { | ||||||
| 	c.initAccount() | 	c.initAccount() | ||||||
|  | 	c.initAccountNote() | ||||||
| 	c.initBlock() | 	c.initBlock() | ||||||
| 	c.initDomainBlock() | 	c.initDomainBlock() | ||||||
| 	c.initEmoji() | 	c.initEmoji() | ||||||
|  | @ -77,6 +79,7 @@ func (c *GTSCaches) Init() { | ||||||
| // Start will attempt to start all of the gtsmodel caches, or panic. | // Start will attempt to start all of the gtsmodel caches, or panic. | ||||||
| func (c *GTSCaches) Start() { | func (c *GTSCaches) Start() { | ||||||
| 	tryStart(c.account, config.GetCacheGTSAccountSweepFreq()) | 	tryStart(c.account, config.GetCacheGTSAccountSweepFreq()) | ||||||
|  | 	tryStart(c.accountNote, config.GetCacheGTSAccountNoteSweepFreq()) | ||||||
| 	tryStart(c.block, config.GetCacheGTSBlockSweepFreq()) | 	tryStart(c.block, config.GetCacheGTSBlockSweepFreq()) | ||||||
| 	tryStart(c.emoji, config.GetCacheGTSEmojiSweepFreq()) | 	tryStart(c.emoji, config.GetCacheGTSEmojiSweepFreq()) | ||||||
| 	tryStart(c.emojiCategory, config.GetCacheGTSEmojiCategorySweepFreq()) | 	tryStart(c.emojiCategory, config.GetCacheGTSEmojiCategorySweepFreq()) | ||||||
|  | @ -104,6 +107,7 @@ func (c *GTSCaches) Start() { | ||||||
| // Stop will attempt to stop all of the gtsmodel caches, or panic. | // Stop will attempt to stop all of the gtsmodel caches, or panic. | ||||||
| func (c *GTSCaches) Stop() { | func (c *GTSCaches) Stop() { | ||||||
| 	tryStop(c.account, config.GetCacheGTSAccountSweepFreq()) | 	tryStop(c.account, config.GetCacheGTSAccountSweepFreq()) | ||||||
|  | 	tryStop(c.accountNote, config.GetCacheGTSAccountNoteSweepFreq()) | ||||||
| 	tryStop(c.block, config.GetCacheGTSBlockSweepFreq()) | 	tryStop(c.block, config.GetCacheGTSBlockSweepFreq()) | ||||||
| 	tryStop(c.emoji, config.GetCacheGTSEmojiSweepFreq()) | 	tryStop(c.emoji, config.GetCacheGTSEmojiSweepFreq()) | ||||||
| 	tryStop(c.emojiCategory, config.GetCacheGTSEmojiCategorySweepFreq()) | 	tryStop(c.emojiCategory, config.GetCacheGTSEmojiCategorySweepFreq()) | ||||||
|  | @ -128,6 +132,11 @@ func (c *GTSCaches) Account() *result.Cache[*gtsmodel.Account] { | ||||||
| 	return c.account | 	return c.account | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // AccountNote provides access to the gtsmodel Note database cache. | ||||||
|  | func (c *GTSCaches) AccountNote() *result.Cache[*gtsmodel.AccountNote] { | ||||||
|  | 	return c.accountNote | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Block provides access to the gtsmodel Block (account) database cache. | // Block provides access to the gtsmodel Block (account) database cache. | ||||||
| func (c *GTSCaches) Block() *result.Cache[*gtsmodel.Block] { | func (c *GTSCaches) Block() *result.Cache[*gtsmodel.Block] { | ||||||
| 	return c.block | 	return c.block | ||||||
|  | @ -238,6 +247,19 @@ func (c *GTSCaches) initAccount() { | ||||||
| 	c.account.IgnoreErrors(ignoreErrors) | 	c.account.IgnoreErrors(ignoreErrors) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (c *GTSCaches) initAccountNote() { | ||||||
|  | 	c.accountNote = result.New([]result.Lookup{ | ||||||
|  | 		{Name: "ID"}, | ||||||
|  | 		{Name: "AccountID.TargetAccountID"}, | ||||||
|  | 	}, func(n1 *gtsmodel.AccountNote) *gtsmodel.AccountNote { | ||||||
|  | 		n2 := new(gtsmodel.AccountNote) | ||||||
|  | 		*n2 = *n1 | ||||||
|  | 		return n2 | ||||||
|  | 	}, config.GetCacheGTSAccountNoteMaxSize()) | ||||||
|  | 	c.accountNote.SetTTL(config.GetCacheGTSAccountNoteTTL(), true) | ||||||
|  | 	c.accountNote.IgnoreErrors(ignoreErrors) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (c *GTSCaches) initBlock() { | func (c *GTSCaches) initBlock() { | ||||||
| 	c.block = result.New([]result.Lookup{ | 	c.block = result.New([]result.Lookup{ | ||||||
| 		{Name: "ID"}, | 		{Name: "ID"}, | ||||||
|  |  | ||||||
|  | @ -186,6 +186,10 @@ type GTSCacheConfiguration struct { | ||||||
| 	AccountTTL       time.Duration `name:"account-ttl"` | 	AccountTTL       time.Duration `name:"account-ttl"` | ||||||
| 	AccountSweepFreq time.Duration `name:"account-sweep-freq"` | 	AccountSweepFreq time.Duration `name:"account-sweep-freq"` | ||||||
| 
 | 
 | ||||||
|  | 	AccountNoteMaxSize   int           `name:"account-note-max-size"` | ||||||
|  | 	AccountNoteTTL       time.Duration `name:"account-note-ttl"` | ||||||
|  | 	AccountNoteSweepFreq time.Duration `name:"account-note-sweep-freq"` | ||||||
|  | 
 | ||||||
| 	BlockMaxSize   int           `name:"block-max-size"` | 	BlockMaxSize   int           `name:"block-max-size"` | ||||||
| 	BlockTTL       time.Duration `name:"block-ttl"` | 	BlockTTL       time.Duration `name:"block-ttl"` | ||||||
| 	BlockSweepFreq time.Duration `name:"block-sweep-freq"` | 	BlockSweepFreq time.Duration `name:"block-sweep-freq"` | ||||||
|  |  | ||||||
|  | @ -131,6 +131,10 @@ var Defaults = Configuration{ | ||||||
| 			AccountTTL:       time.Minute * 30, | 			AccountTTL:       time.Minute * 30, | ||||||
| 			AccountSweepFreq: time.Minute, | 			AccountSweepFreq: time.Minute, | ||||||
| 
 | 
 | ||||||
|  | 			AccountNoteMaxSize:   1000, | ||||||
|  | 			AccountNoteTTL:       time.Minute * 30, | ||||||
|  | 			AccountNoteSweepFreq: time.Minute, | ||||||
|  | 
 | ||||||
| 			BlockMaxSize:   1000, | 			BlockMaxSize:   1000, | ||||||
| 			BlockTTL:       time.Minute * 30, | 			BlockTTL:       time.Minute * 30, | ||||||
| 			BlockSweepFreq: time.Minute, | 			BlockSweepFreq: time.Minute, | ||||||
|  |  | ||||||
|  | @ -2474,6 +2474,81 @@ func GetCacheGTSAccountSweepFreq() time.Duration { return global.GetCacheGTSAcco | ||||||
| // SetCacheGTSAccountSweepFreq safely sets the value for global configuration 'Cache.GTS.AccountSweepFreq' field | // SetCacheGTSAccountSweepFreq safely sets the value for global configuration 'Cache.GTS.AccountSweepFreq' field | ||||||
| func SetCacheGTSAccountSweepFreq(v time.Duration) { global.SetCacheGTSAccountSweepFreq(v) } | func SetCacheGTSAccountSweepFreq(v time.Duration) { global.SetCacheGTSAccountSweepFreq(v) } | ||||||
| 
 | 
 | ||||||
|  | // GetCacheGTSAccountNoteMaxSize safely fetches the Configuration value for state's 'Cache.GTS.AccountNoteMaxSize' field | ||||||
|  | func (st *ConfigState) GetCacheGTSAccountNoteMaxSize() (v int) { | ||||||
|  | 	st.mutex.RLock() | ||||||
|  | 	v = st.config.Cache.GTS.AccountNoteMaxSize | ||||||
|  | 	st.mutex.RUnlock() | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetCacheGTSAccountNoteMaxSize safely sets the Configuration value for state's 'Cache.GTS.AccountNoteMaxSize' field | ||||||
|  | func (st *ConfigState) SetCacheGTSAccountNoteMaxSize(v int) { | ||||||
|  | 	st.mutex.Lock() | ||||||
|  | 	defer st.mutex.Unlock() | ||||||
|  | 	st.config.Cache.GTS.AccountNoteMaxSize = v | ||||||
|  | 	st.reloadToViper() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CacheGTSAccountNoteMaxSizeFlag returns the flag name for the 'Cache.GTS.AccountNoteMaxSize' field | ||||||
|  | func CacheGTSAccountNoteMaxSizeFlag() string { return "cache-gts-account-note-max-size" } | ||||||
|  | 
 | ||||||
|  | // GetCacheGTSAccountNoteMaxSize safely fetches the value for global configuration 'Cache.GTS.AccountNoteMaxSize' field | ||||||
|  | func GetCacheGTSAccountNoteMaxSize() int { return global.GetCacheGTSAccountNoteMaxSize() } | ||||||
|  | 
 | ||||||
|  | // SetCacheGTSAccountNoteMaxSize safely sets the value for global configuration 'Cache.GTS.AccountNoteMaxSize' field | ||||||
|  | func SetCacheGTSAccountNoteMaxSize(v int) { global.SetCacheGTSAccountNoteMaxSize(v) } | ||||||
|  | 
 | ||||||
|  | // GetCacheGTSAccountNoteTTL safely fetches the Configuration value for state's 'Cache.GTS.AccountNoteTTL' field | ||||||
|  | func (st *ConfigState) GetCacheGTSAccountNoteTTL() (v time.Duration) { | ||||||
|  | 	st.mutex.RLock() | ||||||
|  | 	v = st.config.Cache.GTS.AccountNoteTTL | ||||||
|  | 	st.mutex.RUnlock() | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetCacheGTSAccountNoteTTL safely sets the Configuration value for state's 'Cache.GTS.AccountNoteTTL' field | ||||||
|  | func (st *ConfigState) SetCacheGTSAccountNoteTTL(v time.Duration) { | ||||||
|  | 	st.mutex.Lock() | ||||||
|  | 	defer st.mutex.Unlock() | ||||||
|  | 	st.config.Cache.GTS.AccountNoteTTL = v | ||||||
|  | 	st.reloadToViper() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CacheGTSAccountNoteTTLFlag returns the flag name for the 'Cache.GTS.AccountNoteTTL' field | ||||||
|  | func CacheGTSAccountNoteTTLFlag() string { return "cache-gts-account-note-ttl" } | ||||||
|  | 
 | ||||||
|  | // GetCacheGTSAccountNoteTTL safely fetches the value for global configuration 'Cache.GTS.AccountNoteTTL' field | ||||||
|  | func GetCacheGTSAccountNoteTTL() time.Duration { return global.GetCacheGTSAccountNoteTTL() } | ||||||
|  | 
 | ||||||
|  | // SetCacheGTSAccountNoteTTL safely sets the value for global configuration 'Cache.GTS.AccountNoteTTL' field | ||||||
|  | func SetCacheGTSAccountNoteTTL(v time.Duration) { global.SetCacheGTSAccountNoteTTL(v) } | ||||||
|  | 
 | ||||||
|  | // GetCacheGTSAccountNoteSweepFreq safely fetches the Configuration value for state's 'Cache.GTS.AccountNoteSweepFreq' field | ||||||
|  | func (st *ConfigState) GetCacheGTSAccountNoteSweepFreq() (v time.Duration) { | ||||||
|  | 	st.mutex.RLock() | ||||||
|  | 	v = st.config.Cache.GTS.AccountNoteSweepFreq | ||||||
|  | 	st.mutex.RUnlock() | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetCacheGTSAccountNoteSweepFreq safely sets the Configuration value for state's 'Cache.GTS.AccountNoteSweepFreq' field | ||||||
|  | func (st *ConfigState) SetCacheGTSAccountNoteSweepFreq(v time.Duration) { | ||||||
|  | 	st.mutex.Lock() | ||||||
|  | 	defer st.mutex.Unlock() | ||||||
|  | 	st.config.Cache.GTS.AccountNoteSweepFreq = v | ||||||
|  | 	st.reloadToViper() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CacheGTSAccountNoteSweepFreqFlag returns the flag name for the 'Cache.GTS.AccountNoteSweepFreq' field | ||||||
|  | func CacheGTSAccountNoteSweepFreqFlag() string { return "cache-gts-account-note-sweep-freq" } | ||||||
|  | 
 | ||||||
|  | // GetCacheGTSAccountNoteSweepFreq safely fetches the value for global configuration 'Cache.GTS.AccountNoteSweepFreq' field | ||||||
|  | func GetCacheGTSAccountNoteSweepFreq() time.Duration { return global.GetCacheGTSAccountNoteSweepFreq() } | ||||||
|  | 
 | ||||||
|  | // SetCacheGTSAccountNoteSweepFreq safely sets the value for global configuration 'Cache.GTS.AccountNoteSweepFreq' field | ||||||
|  | func SetCacheGTSAccountNoteSweepFreq(v time.Duration) { global.SetCacheGTSAccountNoteSweepFreq(v) } | ||||||
|  | 
 | ||||||
| // GetCacheGTSBlockMaxSize safely fetches the Configuration value for state's 'Cache.GTS.BlockMaxSize' field | // GetCacheGTSBlockMaxSize safely fetches the Configuration value for state's 'Cache.GTS.BlockMaxSize' field | ||||||
| func (st *ConfigState) GetCacheGTSBlockMaxSize() (v int) { | func (st *ConfigState) GetCacheGTSBlockMaxSize() (v int) { | ||||||
| 	st.mutex.RLock() | 	st.mutex.RLock() | ||||||
|  |  | ||||||
|  | @ -49,6 +49,7 @@ type BunDBStandardTestSuite struct { | ||||||
| 	testFaves        map[string]*gtsmodel.StatusFave | 	testFaves        map[string]*gtsmodel.StatusFave | ||||||
| 	testLists        map[string]*gtsmodel.List | 	testLists        map[string]*gtsmodel.List | ||||||
| 	testListEntries  map[string]*gtsmodel.ListEntry | 	testListEntries  map[string]*gtsmodel.ListEntry | ||||||
|  | 	testAccountNotes map[string]*gtsmodel.AccountNote | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *BunDBStandardTestSuite) SetupSuite() { | func (suite *BunDBStandardTestSuite) SetupSuite() { | ||||||
|  | @ -68,6 +69,7 @@ func (suite *BunDBStandardTestSuite) SetupSuite() { | ||||||
| 	suite.testFaves = testrig.NewTestFaves() | 	suite.testFaves = testrig.NewTestFaves() | ||||||
| 	suite.testLists = testrig.NewTestLists() | 	suite.testLists = testrig.NewTestLists() | ||||||
| 	suite.testListEntries = testrig.NewTestListEntries() | 	suite.testListEntries = testrig.NewTestListEntries() | ||||||
|  | 	suite.testAccountNotes = testrig.NewTestAccountNotes() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *BunDBStandardTestSuite) SetupTest() { | func (suite *BunDBStandardTestSuite) SetupTest() { | ||||||
|  |  | ||||||
							
								
								
									
										62
									
								
								internal/db/bundb/migrations/20230711214815_account_notes.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								internal/db/bundb/migrations/20230711214815_account_notes.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // 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 migrations | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/uptrace/bun" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	up := func(ctx context.Context, db *bun.DB) error { | ||||||
|  | 		return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | ||||||
|  | 			// Account note table. | ||||||
|  | 			if _, err := tx. | ||||||
|  | 				NewCreateTable(). | ||||||
|  | 				Model(>smodel.AccountNote{}). | ||||||
|  | 				IfNotExists(). | ||||||
|  | 				Exec(ctx); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Add IDs index to the account note table. | ||||||
|  | 			if _, err := tx. | ||||||
|  | 				NewCreateIndex(). | ||||||
|  | 				Model(>smodel.AccountNote{}). | ||||||
|  | 				Index("account_notes_account_id_target_account_id_idx"). | ||||||
|  | 				Column("account_id", "target_account_id"). | ||||||
|  | 				Exec(ctx); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return nil | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	down := func(ctx context.Context, db *bun.DB) error { | ||||||
|  | 		return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | ||||||
|  | 			return nil | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := Migrations.Register(up, down); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -85,6 +85,19 @@ func (r *relationshipDB) GetRelationship(ctx context.Context, requestingAccount | ||||||
| 		return nil, fmt.Errorf("GetRelationship: error checking blockedBy: %w", err) | 		return nil, fmt.Errorf("GetRelationship: error checking blockedBy: %w", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// retrieve a note by the requesting account on the target account, if there is one | ||||||
|  | 	note, err := r.GetNote( | ||||||
|  | 		gtscontext.SetBarebones(ctx), | ||||||
|  | 		requestingAccount, | ||||||
|  | 		targetAccount, | ||||||
|  | 	) | ||||||
|  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | ||||||
|  | 		return nil, fmt.Errorf("GetRelationship: error fetching note: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if note != nil { | ||||||
|  | 		rel.Note = note.Comment | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return &rel, nil | 	return &rel, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										99
									
								
								internal/db/bundb/relationship_note.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								internal/db/bundb/relationship_note.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,99 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // 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 bundb | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/uptrace/bun" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func (r *relationshipDB) GetNote(ctx context.Context, sourceAccountID string, targetAccountID string) (*gtsmodel.AccountNote, error) { | ||||||
|  | 	return r.getNote( | ||||||
|  | 		ctx, | ||||||
|  | 		"AccountID.TargetAccountID", | ||||||
|  | 		func(note *gtsmodel.AccountNote) error { | ||||||
|  | 			return r.conn.NewSelect().Model(note). | ||||||
|  | 				Where("? = ?", bun.Ident("account_id"), sourceAccountID). | ||||||
|  | 				Where("? = ?", bun.Ident("target_account_id"), targetAccountID). | ||||||
|  | 				Scan(ctx) | ||||||
|  | 		}, | ||||||
|  | 		sourceAccountID, | ||||||
|  | 		targetAccountID, | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *relationshipDB) getNote(ctx context.Context, lookup string, dbQuery func(*gtsmodel.AccountNote) error, keyParts ...any) (*gtsmodel.AccountNote, error) { | ||||||
|  | 	// Fetch note from cache with loader callback | ||||||
|  | 	note, err := r.state.Caches.GTS.AccountNote().Load(lookup, func() (*gtsmodel.AccountNote, error) { | ||||||
|  | 		var note gtsmodel.AccountNote | ||||||
|  | 
 | ||||||
|  | 		// Not cached! Perform database query | ||||||
|  | 		if err := dbQuery(¬e); err != nil { | ||||||
|  | 			return nil, r.conn.ProcessError(err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return ¬e, nil | ||||||
|  | 	}, keyParts...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// already processed | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if gtscontext.Barebones(ctx) { | ||||||
|  | 		// Only a barebones model was requested. | ||||||
|  | 		return note, nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Set the note source account | ||||||
|  | 	note.Account, err = r.state.DB.GetAccountByID( | ||||||
|  | 		gtscontext.SetBarebones(ctx), | ||||||
|  | 		note.AccountID, | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error getting note source account: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Set the note target account | ||||||
|  | 	note.TargetAccount, err = r.state.DB.GetAccountByID( | ||||||
|  | 		gtscontext.SetBarebones(ctx), | ||||||
|  | 		note.TargetAccountID, | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("error getting note target account: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return note, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (r *relationshipDB) PutNote(ctx context.Context, note *gtsmodel.AccountNote) error { | ||||||
|  | 	note.UpdatedAt = time.Now() | ||||||
|  | 	return r.state.Caches.GTS.AccountNote().Store(note, func() error { | ||||||
|  | 		_, err := r.conn. | ||||||
|  | 			NewInsert(). | ||||||
|  | 			Model(note). | ||||||
|  | 			On("CONFLICT (?, ?) DO UPDATE", bun.Ident("account_id"), bun.Ident("target_account_id")). | ||||||
|  | 			Set("? = ?, ? = ?", bun.Ident("updated_at"), note.UpdatedAt, bun.Ident("comment"), note.Comment). | ||||||
|  | 			Exec(ctx) | ||||||
|  | 		return r.conn.ProcessError(err) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | @ -912,6 +912,53 @@ func (suite *RelationshipTestSuite) TestUpdateFollow() { | ||||||
| 	suite.True(relationship.Notifying) | 	suite.True(relationship.Notifying) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (suite *RelationshipTestSuite) TestGetNote() { | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 
 | ||||||
|  | 	// Retrieve a fixture note | ||||||
|  | 	account1 := suite.testAccounts["local_account_1"].ID | ||||||
|  | 	account2 := suite.testAccounts["local_account_2"].ID | ||||||
|  | 	expectedNote := suite.testAccountNotes["local_account_2_note_on_1"] | ||||||
|  | 	note, err := suite.db.GetNote(ctx, account2, account1) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotNil(note) | ||||||
|  | 	suite.Equal(expectedNote.ID, note.ID) | ||||||
|  | 	suite.Equal(expectedNote.Comment, note.Comment) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (suite *RelationshipTestSuite) TestPutNote() { | ||||||
|  | 	ctx := context.Background() | ||||||
|  | 
 | ||||||
|  | 	// put a note in | ||||||
|  | 	account1 := suite.testAccounts["local_account_1"].ID | ||||||
|  | 	account2 := suite.testAccounts["local_account_2"].ID | ||||||
|  | 	err := suite.db.PutNote(ctx, >smodel.AccountNote{ | ||||||
|  | 		ID:              "01H539R2NA0M83JX15Y5RWKE97", | ||||||
|  | 		AccountID:       account1, | ||||||
|  | 		TargetAccountID: account2, | ||||||
|  | 		Comment:         "foo", | ||||||
|  | 	}) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 
 | ||||||
|  | 	// make sure the note is in the db | ||||||
|  | 	note, err := suite.db.GetNote(ctx, account1, account2) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotNil(note) | ||||||
|  | 	suite.Equal("01H539R2NA0M83JX15Y5RWKE97", note.ID) | ||||||
|  | 	suite.Equal("foo", note.Comment) | ||||||
|  | 
 | ||||||
|  | 	// update the note | ||||||
|  | 	note.Comment = "bar" | ||||||
|  | 	err = suite.db.PutNote(ctx, note) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 
 | ||||||
|  | 	// make sure the comment changes | ||||||
|  | 	note, err = suite.db.GetNote(ctx, account1, account2) | ||||||
|  | 	suite.NoError(err) | ||||||
|  | 	suite.NotNil(note) | ||||||
|  | 	suite.Equal("bar", note.Comment) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestRelationshipTestSuite(t *testing.T) { | func TestRelationshipTestSuite(t *testing.T) { | ||||||
| 	suite.Run(t, new(RelationshipTestSuite)) | 	suite.Run(t, new(RelationshipTestSuite)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -165,4 +165,10 @@ type Relationship interface { | ||||||
| 
 | 
 | ||||||
| 	// CountAccountFollowerRequests returns number of follow requests originating from the given account. | 	// CountAccountFollowerRequests returns number of follow requests originating from the given account. | ||||||
| 	CountAccountFollowRequesting(ctx context.Context, accountID string) (int, error) | 	CountAccountFollowRequesting(ctx context.Context, accountID string) (int, error) | ||||||
|  | 
 | ||||||
|  | 	// GetNote gets a private note from a source account on a target account, if it exists. | ||||||
|  | 	GetNote(ctx context.Context, sourceAccountID string, targetAccountID string) (*gtsmodel.AccountNote, error) | ||||||
|  | 
 | ||||||
|  | 	// PutNote creates or updates a private note. | ||||||
|  | 	PutNote(ctx context.Context, note *gtsmodel.AccountNote) error | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										32
									
								
								internal/gtsmodel/accountnote.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								internal/gtsmodel/accountnote.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // 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 gtsmodel | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | // AccountNote stores a private note from a local account related to any account. | ||||||
|  | type AccountNote struct { | ||||||
|  | 	ID              string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                                              // id of this item in the database | ||||||
|  | 	CreatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                                       // when was item created | ||||||
|  | 	UpdatedAt       time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`                                       // when was item last updated | ||||||
|  | 	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),unique:account_notes_account_id_target_account_id_uniq,notnull,nullzero"` // ID of the local account that created the note | ||||||
|  | 	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                                                                    // Account corresponding to accountID | ||||||
|  | 	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),unique:account_notes_account_id_target_account_id_uniq,notnull,nullzero"` // Who is the target of this note? | ||||||
|  | 	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                                                                    // Account corresponding to targetAccountID | ||||||
|  | 	Comment         string    `validate:"-" bun:""`                                                                                                  // The text of the note. | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								internal/processing/account/note.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								internal/processing/account/note.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | ||||||
|  | // GoToSocial | ||||||
|  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | ||||||
|  | // SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  | // | ||||||
|  | // 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 account | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // PutNote updates the requesting account's private note on the target account. | ||||||
|  | func (p *Processor) PutNote(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string, comment string) (*apimodel.Relationship, gtserror.WithCode) { | ||||||
|  | 	targetAccount, errWithCode := p.Get(ctx, requestingAccount, targetAccountID) | ||||||
|  | 	if errWithCode != nil { | ||||||
|  | 		return nil, errWithCode | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	note := >smodel.AccountNote{ | ||||||
|  | 		ID:              id.NewULID(), | ||||||
|  | 		AccountID:       requestingAccount.ID, | ||||||
|  | 		TargetAccountID: targetAccount.ID, | ||||||
|  | 		Comment:         comment, | ||||||
|  | 	} | ||||||
|  | 	err := p.state.DB.PutNote(ctx, note) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, gtserror.NewErrorInternalError(err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return p.RelationshipGet(ctx, requestingAccount, targetAccount.ID) | ||||||
|  | } | ||||||
|  | @ -20,6 +20,9 @@ EXPECT=$(cat <<"EOF" | ||||||
|     "cache": { |     "cache": { | ||||||
|         "gts": { |         "gts": { | ||||||
|             "account-max-size": 99, |             "account-max-size": 99, | ||||||
|  |             "account-note-max-size": 1000, | ||||||
|  |             "account-note-sweep-freq": 60000000000, | ||||||
|  |             "account-note-ttl": 1800000000000, | ||||||
|             "account-sweep-freq": 1000000000, |             "account-sweep-freq": 1000000000, | ||||||
|             "account-ttl": 10800000000000, |             "account-ttl": 10800000000000, | ||||||
|             "block-max-size": 1000, |             "block-max-size": 1000, | ||||||
|  |  | ||||||
|  | @ -60,6 +60,7 @@ var testModels = []interface{}{ | ||||||
| 	>smodel.EmojiCategory{}, | 	>smodel.EmojiCategory{}, | ||||||
| 	>smodel.Tombstone{}, | 	>smodel.Tombstone{}, | ||||||
| 	>smodel.Report{}, | 	>smodel.Report{}, | ||||||
|  | 	>smodel.AccountNote{}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewTestDB returns a new initialized, empty database for testing. | // NewTestDB returns a new initialized, empty database for testing. | ||||||
|  | @ -280,6 +281,12 @@ func StandardDBSetup(db db.DB, accounts map[string]*gtsmodel.Account) { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	for _, v := range NewTestAccountNotes() { | ||||||
|  | 		if err := db.Put(ctx, v); err != nil { | ||||||
|  | 			log.Panic(nil, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if err := db.CreateInstanceAccount(ctx); err != nil { | 	if err := db.CreateInstanceAccount(ctx); err != nil { | ||||||
| 		log.Panic(nil, err) | 		log.Panic(nil, err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -1893,6 +1893,18 @@ func NewTestFaves() map[string]*gtsmodel.StatusFave { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // NewTestAccountNotes returns some account notes for use in testing. | ||||||
|  | func NewTestAccountNotes() map[string]*gtsmodel.AccountNote { | ||||||
|  | 	return map[string]*gtsmodel.AccountNote{ | ||||||
|  | 		"local_account_2_note_on_1": { | ||||||
|  | 			ID:              "01H53TM628GNC4ZDNRGQGPK8S0", | ||||||
|  | 			AccountID:       "01F8MH5NBDF2MV7CTC4Q5128HF", | ||||||
|  | 			TargetAccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", | ||||||
|  | 			Comment:         "extremely average poster", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // NewTestNotifications returns some notifications for use in testing. | // NewTestNotifications returns some notifications for use in testing. | ||||||
| func NewTestNotifications() map[string]*gtsmodel.Notification { | func NewTestNotifications() map[string]*gtsmodel.Notification { | ||||||
| 	return map[string]*gtsmodel.Notification{ | 	return map[string]*gtsmodel.Notification{ | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue