additional work on statuses

This commit is contained in:
tsmethurst 2021-04-02 19:20:41 +02:00
parent 6705326752
commit 0b0f3d9e9a
7 changed files with 178 additions and 49 deletions

View File

@ -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
}
}

View File

@ -233,11 +233,11 @@ type Flags struct {
StorageServeHost string
StorageServeBasePath string
StatusesMaxChars string
StatusesCWMaxChars string
StatusesPollMaxOptions string
StatusesMaxChars string
StatusesCWMaxChars string
StatusesPollMaxOptions string
StatusesPollOptionMaxChars string
StatusesMaxMediaFiles string
StatusesMaxMediaFiles string
}
// GetFlagNames returns a struct containing the names of the various flags used for
@ -271,11 +271,11 @@ func GetFlagNames() Flags {
StorageServeHost: "storage-serve-host",
StorageServeBasePath: "storage-serve-base-path",
StatusesMaxChars: "statuses-max-chars",
StatusesCWMaxChars: "statuses-cw-max-chars",
StatusesPollMaxOptions: "statuses-poll-max-options",
StatusesMaxChars: "statuses-max-chars",
StatusesCWMaxChars: "statuses-cw-max-chars",
StatusesPollMaxOptions: "statuses-poll-max-options",
StatusesPollOptionMaxChars: "statuses-poll-option-max-chars",
StatusesMaxMediaFiles: "statuses-max-media-files",
StatusesMaxMediaFiles: "statuses-max-media-files",
}
}
@ -310,10 +310,10 @@ func GetEnvNames() Flags {
StorageServeHost: "GTS_STORAGE_SERVE_HOST",
StorageServeBasePath: "GTS_STORAGE_SERVE_BASE_PATH",
StatusesMaxChars: "GTS_STATUSES_MAX_CHARS",
StatusesCWMaxChars: "GTS_STATUSES_CW_MAX_CHARS",
StatusesPollMaxOptions: "GTS_STATUSES_POLL_MAX_OPTIONS",
StatusesMaxChars: "GTS_STATUSES_MAX_CHARS",
StatusesCWMaxChars: "GTS_STATUSES_CW_MAX_CHARS",
StatusesPollMaxOptions: "GTS_STATUSES_POLL_MAX_OPTIONS",
StatusesPollOptionMaxChars: "GTS_STATUSES_POLL_OPTION_MAX_CHARS",
StatusesMaxMediaFiles: "GTS_STATUSES_MAX_MEDIA_FILES",
StatusesMaxMediaFiles: "GTS_STATUSES_MAX_MEDIA_FILES",
}
}

View File

@ -21,13 +21,13 @@ package config
// StatusesConfig pertains to posting/deleting/interacting with statuses
type StatusesConfig struct {
// Maximum amount of characters allowed in a status, excluding CW
MaxChars int `yaml:"max_chars"`
MaxChars int `yaml:"max_chars"`
// Maximum amount of characters allowed in a content-warning/spoiler field
CWMaxChars int `yaml:"cw_max_chars"`
CWMaxChars int `yaml:"cw_max_chars"`
// Maximum number of options allowed in a poll
PollMaxOptions int `yaml:"poll_max_options"`
PollMaxOptions int `yaml:"poll_max_options"`
// Maximum characters allowed per poll option
PollOptionMaxChars int `yaml:"poll_option_max_chars"`
// Maximum amount of media files allowed to be attached to one status
MaxMediaFiles int `yaml:"max_media_files"`
MaxMediaFiles int `yaml:"max_media_files"`
}

View 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
}

View File

@ -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"`
}

View File

@ -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"

View File

@ -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"
)