mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 22:12:25 -05:00 
			
		
		
		
	additional work on statuses
This commit is contained in:
		
					parent
					
						
							
								6705326752
							
						
					
				
			
			
				commit
				
					
						0b0f3d9e9a
					
				
			
		
					 7 changed files with 178 additions and 49 deletions
				
			
		|  | @ -27,6 +27,7 @@ import ( | |||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||
| 	"github.com/superseriousbusiness/gotosocial/pkg/mastotypes" | ||||
|  | @ -57,7 +58,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { | |||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("validating form %+v", form) | ||||
| 	if err := validateCreateStatus(form, m.config.StatusesConfig, m.db); err != nil { | ||||
| 	if err := validateCreateStatus(form, m.config.StatusesConfig, authed.Account.ID, m.db); err != nil { | ||||
| 		l.Debugf("error validating form: %s", err) | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||||
| 		return | ||||
|  | @ -71,16 +72,15 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { | |||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "ip address could not be parsed from request"}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// newStatus := &model.Status{ | ||||
| 
 | ||||
| 	// } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func validateCreateStatus(form *mastotypes.StatusCreateRequest, config *config.StatusesConfig, db db.DB) error { | ||||
| 
 | ||||
| 	if form.Language != "" { | ||||
| 		if err := util.ValidateLanguage(form.Language); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| func validateCreateStatus(form *mastotypes.StatusCreateRequest, config *config.StatusesConfig, accountID string, db db.DB) error { | ||||
| 	// validate that, structurally, we have a valid status/post | ||||
| 	if form.Status == "" && form.MediaIDs == nil && form.Poll == nil { | ||||
| 		return errors.New("no status, media, or poll provided") | ||||
| 	} | ||||
|  | @ -89,6 +89,31 @@ func validateCreateStatus(form *mastotypes.StatusCreateRequest, config *config.S | |||
| 		return errors.New("can't post media + poll in same status") | ||||
| 	} | ||||
| 
 | ||||
| 	// validate status | ||||
| 	if form.Status != "" { | ||||
| 		if len(form.Status) > config.MaxChars { | ||||
| 			return fmt.Errorf("status too long, %d characters provided but limit is %d", len(form.Status), config.MaxChars) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// validate media attachments | ||||
| 	if len(form.MediaIDs) > config.MaxMediaFiles { | ||||
| 		return fmt.Errorf("too many media files attached to status, %d attached but limit is %d", len(form.MediaIDs), config.MaxMediaFiles) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, m := range form.MediaIDs { | ||||
| 		// check these attachments exist | ||||
| 		a := &model.MediaAttachment{} | ||||
| 		if err := db.GetByID(m, a); err != nil { | ||||
| 			return fmt.Errorf("invalid media type or media not found for media id %s: %s", m, err) | ||||
| 		} | ||||
| 		// check they belong to the requesting account id | ||||
| 		if a.AccountID != accountID { | ||||
| 			return fmt.Errorf("media attachment %s does not belong to account id %s", m, accountID) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// validate poll | ||||
| 	if form.Poll != nil { | ||||
| 		if form.Poll.Options == nil { | ||||
| 			return errors.New("poll with no options") | ||||
|  | @ -103,13 +128,28 @@ func validateCreateStatus(form *mastotypes.StatusCreateRequest, config *config.S | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if len(form.MediaIDs) > config.MaxMediaFiles { | ||||
| 		return fmt.Errorf("too many media files attached to status, %d attached but limit is %d", len(form.MediaIDs), config.MaxMediaFiles) | ||||
| 	// validate reply-to status exists and is reply-able | ||||
| 	if form.InReplyToID != "" { | ||||
| 		s := &model.Status{} | ||||
| 		if err := db.GetByID(form.InReplyToID, s); err != nil { | ||||
| 			return fmt.Errorf("status id %s cannot be retrieved from the db: %s", form.InReplyToID, err) | ||||
| 		} | ||||
| 		if !*s.VisibilityAdvanced.Replyable { | ||||
| 			return fmt.Errorf("status with id %s is not replyable", form.InReplyToID) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if form.Status != "" { | ||||
| 		if len(form.Status) > config.MaxChars { | ||||
| 			return fmt.Errorf("status too long, %d characters provided but limit is %d", len(form.Status), config.MaxChars) | ||||
| 	// validate spoiler text/cw | ||||
| 	if form.SpoilerText != "" { | ||||
| 		if len(form.SpoilerText) > config.CWMaxChars { | ||||
| 			return fmt.Errorf("content-warning/spoilertext too long, %d characters provided but limit is %d", len(form.SpoilerText), config.CWMaxChars) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// validate post language | ||||
| 	if form.Language != "" { | ||||
| 		if err := util.ValidateLanguage(form.Language); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										39
									
								
								internal/db/model/mention.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								internal/db/model/mention.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| /* | ||||
|    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 model | ||||
| 
 | ||||
| import "time" | ||||
| 
 | ||||
| // Mention refers to the 'tagging' or 'mention' of a user within a status. | ||||
| type Mention struct { | ||||
| 	// ID of this mention in the database | ||||
| 	ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull,unique"` | ||||
| 	// ID of the status this mention originates from | ||||
| 	StatusID string | ||||
| 	// When was this mention created? | ||||
| 	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? | ||||
| 	OriginAccountID string | ||||
| 	// Who does this mention target? | ||||
| 	TargetAccountID string | ||||
| 	// Prevent this mention from generating a notification? | ||||
| 	Silent bool | ||||
| } | ||||
|  | @ -45,22 +45,45 @@ type Status struct { | |||
| 	// cw string for this status | ||||
| 	ContentWarning string | ||||
| 	// visibility entry for this status | ||||
| 	Visibility          *Visibility | ||||
| 	Visibility Visibility | ||||
| 	// advanced visibility for this status | ||||
| 	VisibilityAdvanced VisibilityAdvanced | ||||
| 	// What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types | ||||
| 	// Will probably almost always be a note. | ||||
| 	ActivityStreamsType string | ||||
| } | ||||
| 
 | ||||
| // Visibility represents the visibility granularity of a status. It is a combination of flags. | ||||
| type Visibility struct { | ||||
| 	// Is this status viewable as a direct message? | ||||
| 	Direct bool | ||||
| 	// Is this status viewable to followers? | ||||
| 	Followers bool | ||||
| 	// Is this status viewable on the local timeline? | ||||
| 	Local bool | ||||
| 	// Is this status boostable but not shown on public timelines? | ||||
| 	Unlisted bool | ||||
| 	// Is this status shown on public and federated timelines? | ||||
| 	Public bool | ||||
| // Visibility represents the visibility granularity of a status. | ||||
| type Visibility string | ||||
| 
 | ||||
| const ( | ||||
| 	// This status will be visible to everyone on all timelines. | ||||
| 	VisibilityPublic Visibility = "public" | ||||
| 	// This status will be visible to everyone, but will only show on home timeline to followers, and in lists. | ||||
| 	VisibilityUnlocked Visibility = "unlocked" | ||||
| 	// This status is viewable to followers only. | ||||
| 	VisibilityFollowersOnly Visibility = "followers_only" | ||||
| 	// This status is visible to mutual followers only. | ||||
| 	VisibilityMutualsOnly Visibility = "mutuals_only" | ||||
| 	// This status is visible only to mentioned recipients | ||||
| 	VisibilityDirect Visibility = "direct" | ||||
| ) | ||||
| 
 | ||||
| type VisibilityAdvanced struct { | ||||
| 	/* | ||||
| 		ADVANCED SETTINGS -- These should all default to TRUE. | ||||
| 
 | ||||
| 		If PUBLIC is selected, they will all be overwritten to TRUE regardless of what is selected. | ||||
| 		If UNLOCKED is selected, any of them can be turned on or off in any combination. | ||||
| 		If FOLLOWERS-ONLY or MUTUALS-ONLY are selected, boostable will always be FALSE. The others can be turned on or off as desired. | ||||
| 		If DIRECT is selected, boostable will be FALSE, and all other flags will be TRUE. | ||||
| 	*/ | ||||
| 	// This status will be federated beyond the local timeline(s) | ||||
| 	Federated *bool `pg:"default:true"` | ||||
| 	// This status can be boosted/reblogged | ||||
| 	Boostable *bool `pg:"default:true"` | ||||
| 	// This status can be replied to | ||||
| 	Replyable *bool `pg:"default:true"` | ||||
| 	// This status can be liked/faved | ||||
| 	Likeable *bool `pg:"default:true"` | ||||
| } | ||||
|  |  | |||
|  | @ -1,3 +1,21 @@ | |||
| /* | ||||
|    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 util | ||||
| 
 | ||||
| import "fmt" | ||||
|  |  | |||
|  | @ -33,11 +33,7 @@ type Status struct { | |||
| 	// Subject or summary line, below which status content is collapsed until expanded. | ||||
| 	SpoilerText string `json:"spoiler_text"` | ||||
| 	// Visibility of this status. | ||||
| 	// 	public = Visible to everyone, shown in public timelines. | ||||
| 	// 	unlisted = Visible to public, but not included in public timelines. | ||||
| 	// 	private = Visible to followers only, and to any mentioned users. | ||||
| 	// 	direct = Visible only to mentioned users. | ||||
| 	Visibility string `json:"visibility"` | ||||
| 	Visibility Visibility `json:"visibility"` | ||||
| 	// Primary language of this status. (ISO 639 Part 1 two-letter language code) | ||||
| 	Language string `json:"language"` | ||||
| 	// URI of the status used for federation. | ||||
|  | @ -102,9 +98,22 @@ type StatusCreateRequest struct { | |||
| 	// Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field. | ||||
| 	SpoilerText string `form:"spoiler_text"` | ||||
| 	// Visibility of the posted status. Enumerable oneOf public, unlisted, private, direct. | ||||
| 	Visibility string `form:"visibility"` | ||||
| 	Visibility Visibility `form:"visibility"` | ||||
| 	// ISO 8601 Datetime at which to schedule a status. Providing this paramter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future. | ||||
| 	ScheduledAt string `form:"scheduled_at"` | ||||
| 	// ISO 639 language code for this status. | ||||
| 	Language string `form:"language"` | ||||
| } | ||||
| 
 | ||||
| type Visibility string | ||||
| 
 | ||||
| const ( | ||||
| 	// visible to everyone | ||||
| 	VisibilityPublic Visibility = "public" | ||||
| 	// visible to everyone but only on home timelines or in lists | ||||
| 	VisibilityUnlisted Visibility = "unlisted" | ||||
| 	// visible to followers only | ||||
| 	VisibilityPrivate Visibility = "private" | ||||
| 	// visible only to tagged recipients | ||||
| 	VisibilityDirect Visibility = "direct" | ||||
| ) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue