additional work on statuses
This commit is contained in:
parent
6705326752
commit
0b0f3d9e9a
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
@ -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"`
|
||||
}
|
||||
|
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…
Reference in New Issue
Block a user