moar work
This commit is contained in:
parent
1710158b39
commit
d1ca4a1219
@ -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
|
||||
}
|
||||
|
||||
@ -142,17 +189,31 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
|
||||
|
||||
// take care of side effects -- federation, mentions, updating metadata, etc, etc
|
||||
m.distributor.FromClientAPI() <- distributor.FromClientAPI{
|
||||
APObjectType: model.ActivityStreamsNote,
|
||||
APObjectType: model.ActivityStreamsNote,
|
||||
APActivityType: model.ActivityStreamsCreate,
|
||||
Activity: newStatus,
|
||||
Activity: newStatus,
|
||||
}
|
||||
|
||||
// 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
|
||||
*/
|
||||
@ -712,7 +728,7 @@ func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, ori
|
||||
|
||||
// id, createdAt and updatedAt will be populated by the db, so we have everything we need!
|
||||
menchies = append(menchies, &model.Mention{
|
||||
StatusID: statusID,
|
||||
StatusID: statusID,
|
||||
OriginAccountID: originAccountID,
|
||||
TargetAccountID: mentionedAccount.ID,
|
||||
})
|
||||
@ -745,7 +761,7 @@ func (ps *postgresService) EmojiStringsToEmojis(emojis []string, originAccountID
|
||||
continue
|
||||
}
|
||||
// a serious error has happened so bail
|
||||
return nil, fmt.Errorf("error getting emoji with shortcode %s: %s",e, err)
|
||||
return nil, fmt.Errorf("error getting emoji with shortcode %s: %s", e, err)
|
||||
}
|
||||
newEmojis = append(newEmojis, emoji)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user