diff --git a/internal/apimodule/status/statuscreate.go b/internal/apimodule/status/statuscreate.go
index 587f4ed57..ad6c61999 100644
--- a/internal/apimodule/status/statuscreate.go
+++ b/internal/apimodule/status/statuscreate.go
@@ -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
}
}
diff --git a/internal/config/config.go b/internal/config/config.go
index 59023d391..4cb2b901f 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -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",
}
}
diff --git a/internal/config/statuses.go b/internal/config/statuses.go
index bdc50d50d..fbb5225b4 100644
--- a/internal/config/statuses.go
+++ b/internal/config/statuses.go
@@ -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"`
}
diff --git a/internal/db/model/mention.go b/internal/db/model/mention.go
new file mode 100644
index 000000000..74dd0011c
--- /dev/null
+++ b/internal/db/model/mention.go
@@ -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 .
+*/
+
+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
+}
diff --git a/internal/db/model/status.go b/internal/db/model/status.go
index d51ae28f6..31f5623e5 100644
--- a/internal/db/model/status.go
+++ b/internal/db/model/status.go
@@ -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"`
}
diff --git a/internal/util/parse.go b/internal/util/parse.go
index 375ab97f2..fec88e784 100644
--- a/internal/util/parse.go
+++ b/internal/util/parse.go
@@ -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 .
+*/
+
package util
import "fmt"
diff --git a/pkg/mastotypes/status.go b/pkg/mastotypes/status.go
index d88e7df18..5f2c73fc8 100644
--- a/pkg/mastotypes/status.go
+++ b/pkg/mastotypes/status.go
@@ -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"
+)