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/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
|
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
|
||||||
@ -57,7 +58,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
l.Tracef("validating form %+v", form)
|
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)
|
l.Debugf("error validating form: %s", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
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"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "ip address could not be parsed from request"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newStatus := &model.Status{
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCreateStatus(form *mastotypes.StatusCreateRequest, config *config.StatusesConfig, db db.DB) error {
|
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.Language != "" {
|
|
||||||
if err := util.ValidateLanguage(form.Language); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if form.Status == "" && form.MediaIDs == nil && form.Poll == nil {
|
if form.Status == "" && form.MediaIDs == nil && form.Poll == nil {
|
||||||
return errors.New("no status, media, or poll provided")
|
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")
|
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 != nil {
|
||||||
if form.Poll.Options == nil {
|
if form.Poll.Options == nil {
|
||||||
return errors.New("poll with no options")
|
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 {
|
// validate reply-to status exists and is reply-able
|
||||||
return fmt.Errorf("too many media files attached to status, %d attached but limit is %d", len(form.MediaIDs), config.MaxMediaFiles)
|
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 != "" {
|
// validate spoiler text/cw
|
||||||
if len(form.Status) > config.MaxChars {
|
if form.SpoilerText != "" {
|
||||||
return fmt.Errorf("status too long, %d characters provided but limit is %d", len(form.Status), config.MaxChars)
|
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
|
StorageServeHost string
|
||||||
StorageServeBasePath string
|
StorageServeBasePath string
|
||||||
|
|
||||||
StatusesMaxChars string
|
StatusesMaxChars string
|
||||||
StatusesCWMaxChars string
|
StatusesCWMaxChars string
|
||||||
StatusesPollMaxOptions string
|
StatusesPollMaxOptions string
|
||||||
StatusesPollOptionMaxChars string
|
StatusesPollOptionMaxChars string
|
||||||
StatusesMaxMediaFiles string
|
StatusesMaxMediaFiles string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFlagNames returns a struct containing the names of the various flags used for
|
// GetFlagNames returns a struct containing the names of the various flags used for
|
||||||
@ -271,11 +271,11 @@ func GetFlagNames() Flags {
|
|||||||
StorageServeHost: "storage-serve-host",
|
StorageServeHost: "storage-serve-host",
|
||||||
StorageServeBasePath: "storage-serve-base-path",
|
StorageServeBasePath: "storage-serve-base-path",
|
||||||
|
|
||||||
StatusesMaxChars: "statuses-max-chars",
|
StatusesMaxChars: "statuses-max-chars",
|
||||||
StatusesCWMaxChars: "statuses-cw-max-chars",
|
StatusesCWMaxChars: "statuses-cw-max-chars",
|
||||||
StatusesPollMaxOptions: "statuses-poll-max-options",
|
StatusesPollMaxOptions: "statuses-poll-max-options",
|
||||||
StatusesPollOptionMaxChars: "statuses-poll-option-max-chars",
|
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",
|
StorageServeHost: "GTS_STORAGE_SERVE_HOST",
|
||||||
StorageServeBasePath: "GTS_STORAGE_SERVE_BASE_PATH",
|
StorageServeBasePath: "GTS_STORAGE_SERVE_BASE_PATH",
|
||||||
|
|
||||||
StatusesMaxChars: "GTS_STATUSES_MAX_CHARS",
|
StatusesMaxChars: "GTS_STATUSES_MAX_CHARS",
|
||||||
StatusesCWMaxChars: "GTS_STATUSES_CW_MAX_CHARS",
|
StatusesCWMaxChars: "GTS_STATUSES_CW_MAX_CHARS",
|
||||||
StatusesPollMaxOptions: "GTS_STATUSES_POLL_MAX_OPTIONS",
|
StatusesPollMaxOptions: "GTS_STATUSES_POLL_MAX_OPTIONS",
|
||||||
StatusesPollOptionMaxChars: "GTS_STATUSES_POLL_OPTION_MAX_CHARS",
|
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
|
// StatusesConfig pertains to posting/deleting/interacting with statuses
|
||||||
type StatusesConfig struct {
|
type StatusesConfig struct {
|
||||||
// Maximum amount of characters allowed in a status, excluding CW
|
// 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
|
// 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
|
// 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
|
// Maximum characters allowed per poll option
|
||||||
PollOptionMaxChars int `yaml:"poll_option_max_chars"`
|
PollOptionMaxChars int `yaml:"poll_option_max_chars"`
|
||||||
// Maximum amount of media files allowed to be attached to one status
|
// 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
|
// cw string for this status
|
||||||
ContentWarning string
|
ContentWarning string
|
||||||
// visibility entry for this status
|
// 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
|
// 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.
|
// Will probably almost always be a note.
|
||||||
ActivityStreamsType string
|
ActivityStreamsType string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visibility represents the visibility granularity of a status. It is a combination of flags.
|
// Visibility represents the visibility granularity of a status.
|
||||||
type Visibility struct {
|
type Visibility string
|
||||||
// Is this status viewable as a direct message?
|
|
||||||
Direct bool
|
const (
|
||||||
// Is this status viewable to followers?
|
// This status will be visible to everyone on all timelines.
|
||||||
Followers bool
|
VisibilityPublic Visibility = "public"
|
||||||
// Is this status viewable on the local timeline?
|
// This status will be visible to everyone, but will only show on home timeline to followers, and in lists.
|
||||||
Local bool
|
VisibilityUnlocked Visibility = "unlocked"
|
||||||
// Is this status boostable but not shown on public timelines?
|
// This status is viewable to followers only.
|
||||||
Unlisted bool
|
VisibilityFollowersOnly Visibility = "followers_only"
|
||||||
// Is this status shown on public and federated timelines?
|
// This status is visible to mutual followers only.
|
||||||
Public bool
|
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
|
package util
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
@ -33,11 +33,7 @@ type Status struct {
|
|||||||
// Subject or summary line, below which status content is collapsed until expanded.
|
// Subject or summary line, below which status content is collapsed until expanded.
|
||||||
SpoilerText string `json:"spoiler_text"`
|
SpoilerText string `json:"spoiler_text"`
|
||||||
// Visibility of this status.
|
// Visibility of this status.
|
||||||
// public = Visible to everyone, shown in public timelines.
|
Visibility Visibility `json:"visibility"`
|
||||||
// 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"`
|
|
||||||
// Primary language of this status. (ISO 639 Part 1 two-letter language code)
|
// Primary language of this status. (ISO 639 Part 1 two-letter language code)
|
||||||
Language string `json:"language"`
|
Language string `json:"language"`
|
||||||
// URI of the status used for federation.
|
// 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.
|
// 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"`
|
SpoilerText string `form:"spoiler_text"`
|
||||||
// Visibility of the posted status. Enumerable oneOf public, unlisted, private, direct.
|
// 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.
|
// 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"`
|
ScheduledAt string `form:"scheduled_at"`
|
||||||
// ISO 639 language code for this status.
|
// ISO 639 language code for this status.
|
||||||
Language string `form:"language"`
|
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