mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 14:12:26 -05:00 
			
		
		
		
	moar work
This commit is contained in:
		
					parent
					
						
							
								1710158b39
							
						
					
				
			
			
				commit
				
					
						d1ca4a1219
					
				
			
		
					 5 changed files with 120 additions and 40 deletions
				
			
		|  | @ -87,6 +87,9 @@ func (m *statusModule) CreateTables(db db.DB) error { | |||
| 		&model.Application{}, | ||||
| 		&model.EmailDomainBlock{}, | ||||
| 		&model.MediaAttachment{}, | ||||
| 		&model.Emoji{}, | ||||
| 		&model.Tag{}, | ||||
| 		&model.Mention{}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, m := range models { | ||||
|  |  | |||
|  | @ -27,7 +27,6 @@ import ( | |||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/model" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/distributor" | ||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||
|  | @ -62,13 +61,15 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { | |||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// check this user/account is permitted to post new statuses | ||||
| 	// First check this user/account is permitted to post new statuses. | ||||
| 	// There's no point continuing otherwise. | ||||
| 	if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() { | ||||
| 		l.Debugf("couldn't auth: %s", err) | ||||
| 		c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// Give the fields on the request form a first pass to make sure the request is superficially valid. | ||||
| 	l.Trace("parsing request form") | ||||
| 	form := &advancedStatusCreateForm{} | ||||
| 	if err := c.ShouldBind(form); err != nil || form == nil { | ||||
|  | @ -76,24 +77,70 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { | |||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "missing one or more required form values"}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	l.Tracef("validating form %+v", form) | ||||
| 	if err := validateCreateStatus(form, m.config.StatusesConfig, authed.Account.ID, m.db); err != nil { | ||||
| 	if err := validateCreateStatus(form, m.config.StatusesConfig); err != nil { | ||||
| 		l.Debugf("error validating form: %s", err) | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// At this point we know the account is permitted to post, and we know the request form | ||||
| 	// is valid (at least according to the API specifications and the instance configuration). | ||||
| 	// So now we can start digging a bit deeper into the status itself. | ||||
| 
 | ||||
| 	// If this status is a reply to another status, we need to do a bit of work to establish whether or not this status can be posted: | ||||
| 	// | ||||
| 	// 1. Does the replied status exist in the database? | ||||
| 	// 2. Is the replied status marked as replyable? | ||||
| 	// 3. Does a block exist between either the current account or the account that posted the status it's replying to? | ||||
| 	// | ||||
| 	// If this is all OK, then we fetch the repliedStatus and the repliedAccount for later processing. | ||||
| 	repliedStatus := &model.Status{} | ||||
| 	repliedAccount := &model.Account{} | ||||
| 	if form.InReplyToID != "" { | ||||
| 		// check replied status exists + is replyable | ||||
| 		if err := m.db.GetByID(form.InReplyToID, repliedStatus); err != nil || !repliedStatus.VisibilityAdvanced.Replyable { | ||||
| 			l.Debugf("status id %s cannot be retrieved from the db: %s", form.InReplyToID, err) | ||||
| 			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("status with id %s not replyable", form.InReplyToID)}) | ||||
| 			return | ||||
| 		} | ||||
| 		// check replied account is known to us | ||||
| 		if err := m.db.GetByID(repliedStatus.AccountID, repliedAccount); err != nil { | ||||
| 			l.Debugf("error getting account with id %s from the database: %s", repliedStatus.AccountID, err) | ||||
| 			c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("status with id %s not replyable", form.InReplyToID)}) | ||||
| 			return | ||||
| 		} | ||||
| 		// check if a block exists | ||||
| 		if blocked, err := m.db.Blocked(authed.Account.ID, repliedAccount.ID); err != nil || blocked { | ||||
| 			c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("status with id %s not replyable", form.InReplyToID)}) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	attachments := []*model.MediaAttachment{} | ||||
| 	for _, mediaID := range form.MediaIDs { | ||||
| 		// check these attachments exist | ||||
| 		a := &model.MediaAttachment{} | ||||
| 		if err := m.db.GetByID(mediaID, a); err != nil { | ||||
| 			l.Debugf("invalid media type or media not found for media id %s: %s", m, err) | ||||
| 			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid media type or media not found for media id %s", mediaID)}) | ||||
| 			return | ||||
| 		} | ||||
| 		// check they belong to the requesting account id | ||||
| 		if a.AccountID != authed.Account.ID { | ||||
| 			l.Debugf("media attachment %s does not belong to account id %s", m, authed.Account.ID) | ||||
| 			c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("media with id %s does not belong to account %s", mediaID, authed.Account.ID)}) | ||||
| 			return | ||||
| 		} | ||||
| 		attachments = append(attachments, a) | ||||
| 	} | ||||
| 
 | ||||
| 	// here we check if any advanced visibility flags have been set and fiddle with them if so | ||||
| 	l.Trace("deriving visibility") | ||||
| 	basicVis, advancedVis, err := deriveTotalVisibility(form.Visibility, form.AdvancedVisibility, authed.Account.Privacy) | ||||
| 
 | ||||
| 	clientIP := c.ClientIP() | ||||
| 	l.Tracef("attempting to parse client ip address %s", clientIP) | ||||
| 	signUpIP := net.ParseIP(clientIP) | ||||
| 	if signUpIP == nil { | ||||
| 		l.Debugf("error validating client ip address %s", clientIP) | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "ip address could not be parsed from request"}) | ||||
| 	if err != nil { | ||||
| 		l.Debugf("error parsing visibility: %s", err) | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
|  | @ -148,11 +195,25 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { | |||
| 	} | ||||
| 
 | ||||
| 	// return populated status to submitter | ||||
| 	// mastoStatus := &mastotypes.Status{ | ||||
| 	// 	ID:                 newStatus.ID, | ||||
| 	// 	CreatedAt:          time.Now().Format(time.RFC3339), | ||||
| 	// 	InReplyToID:        newStatus.InReplyToID, | ||||
| 	// 	InReplyToAccountID: newStatus.InReplyToAccountID, | ||||
| 	// } | ||||
| 
 | ||||
| 	clientIP := c.ClientIP() | ||||
| 	l.Tracef("attempting to parse client ip address %s", clientIP) | ||||
| 	signUpIP := net.ParseIP(clientIP) | ||||
| 	if signUpIP == nil { | ||||
| 		l.Debugf("error validating client ip address %s", clientIP) | ||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "ip address could not be parsed from request"}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func validateCreateStatus(form *advancedStatusCreateForm, config *config.StatusesConfig, accountID string, db db.DB) error { | ||||
| func validateCreateStatus(form *advancedStatusCreateForm, config *config.StatusesConfig) 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") | ||||
|  | @ -174,18 +235,6 @@ func validateCreateStatus(form *advancedStatusCreateForm, config *config.Statuse | |||
| 		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 { | ||||
|  | @ -201,17 +250,6 @@ func validateCreateStatus(form *advancedStatusCreateForm, config *config.Statuse | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// 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) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// validate spoiler text/cw | ||||
| 	if form.SpoilerText != "" { | ||||
| 		if len(form.SpoilerText) > config.CWMaxChars { | ||||
|  |  | |||
|  | @ -174,6 +174,10 @@ type DB interface { | |||
| 	// The passed mediaAttachment pointer will be populated with the value of the header, if it exists. | ||||
| 	GetHeaderForAccountID(header *model.MediaAttachment, accountID string) error | ||||
| 
 | ||||
| 	// Blocked checks whether a block exists in eiher direction between two accounts. | ||||
| 	// That is, it returns true if account1 blocks account2, OR if account2 blocks account1. | ||||
| 	Blocked(account1 string, account2 string) (bool, error) | ||||
| 
 | ||||
| 	/* | ||||
| 		USEFUL CONVERSION FUNCTIONS | ||||
| 	*/ | ||||
|  |  | |||
							
								
								
									
										19
									
								
								internal/db/model/block.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								internal/db/model/block.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,19 @@ | |||
| package model | ||||
| 
 | ||||
| import "time" | ||||
| 
 | ||||
| // Block refers to the blocking of one account by another. | ||||
| type Block struct { | ||||
| 	// id of this block in the database | ||||
| 	ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull"` | ||||
| 	// When was this block created | ||||
| 	CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` | ||||
| 	// When was this block updated | ||||
| 	UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` | ||||
| 	// Who created this block? | ||||
| 	AccountID string `pg:",notnull"` | ||||
| 	// Who is targeted by this block? | ||||
| 	TargetAccountID string `pg:",notnull"` | ||||
| 	// Activitypub URI for this block | ||||
| 	URI string | ||||
| } | ||||
|  | @ -511,6 +511,22 @@ func (ps *postgresService) GetAvatarForAccountID(avatar *model.MediaAttachment, | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) { | ||||
| 	var blocked bool | ||||
| 	if err := ps.conn.Model(&model.Block{}). | ||||
| 		Where("account_id = ?", account1).Where("target_account_id = ?", account2). | ||||
| 		WhereOr("target_account_id = ?", account1).Where("account_id = ?", account2). | ||||
| 		Select(); err != nil { | ||||
| 		if err == pg.ErrNoRows { | ||||
| 			blocked = false | ||||
| 		} else { | ||||
| 			return blocked, err | ||||
| 		} | ||||
| 	} | ||||
| 	blocked = true | ||||
| 	return blocked, nil | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| 	CONVERSION FUNCTIONS | ||||
| */ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue