fixin' bugs and takin' names
This commit is contained in:
parent
9eb8878e94
commit
1bf56e0a52
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db/model"
|
"github.com/superseriousbusiness/gotosocial/internal/db/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/distributor"
|
"github.com/superseriousbusiness/gotosocial/internal/distributor"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
|
@ -36,12 +37,12 @@ import (
|
||||||
|
|
||||||
type advancedStatusCreateForm struct {
|
type advancedStatusCreateForm struct {
|
||||||
mastotypes.StatusCreateRequest
|
mastotypes.StatusCreateRequest
|
||||||
AdvancedVisibility *advancedVisibilityFlagsForm `form:"visibility_advanced"`
|
advancedVisibilityFlagsForm
|
||||||
}
|
}
|
||||||
|
|
||||||
type advancedVisibilityFlagsForm struct {
|
type advancedVisibilityFlagsForm struct {
|
||||||
// The gotosocial visibility model
|
// The gotosocial visibility model
|
||||||
Visibility *model.Visibility
|
VisibilityAdvanced *model.Visibility `form:"visibility_advanced"`
|
||||||
// This status will be federated beyond the local timeline(s)
|
// This status will be federated beyond the local timeline(s)
|
||||||
Federated *bool `form:"federated"`
|
Federated *bool `form:"federated"`
|
||||||
// This status can be boosted/reblogged
|
// This status can be boosted/reblogged
|
||||||
|
@ -70,7 +71,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// extract the status create form from the request context
|
// extract the status create form from the request context
|
||||||
l.Trace("parsing request form")
|
l.Tracef("parsing request form: %s", c.Request.Form)
|
||||||
form := &advancedStatusCreateForm{}
|
form := &advancedStatusCreateForm{}
|
||||||
if err := c.ShouldBind(form); err != nil || form == nil {
|
if err := c.ShouldBind(form); err != nil || form == nil {
|
||||||
l.Debugf("could not parse form from request: %s", err)
|
l.Debugf("could not parse form from request: %s", err)
|
||||||
|
@ -128,6 +129,12 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle language settings
|
||||||
|
if err := parseLanguage(form, authed.Account.Language, newStatus); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// convert mentions to *model.Mention
|
// convert mentions to *model.Mention
|
||||||
menchies, err := m.db.MentionStringsToMentions(util.DeriveMentions(form.Status), authed.Account.ID, thisStatusID)
|
menchies, err := m.db.MentionStringsToMentions(util.DeriveMentions(form.Status), authed.Account.ID, thisStatusID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -186,8 +193,9 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
|
||||||
URI: newStatus.URI,
|
URI: newStatus.URI,
|
||||||
URL: newStatus.URL,
|
URL: newStatus.URL,
|
||||||
Content: newStatus.Content,
|
Content: newStatus.Content,
|
||||||
Application: authed.Application.ToMasto(),
|
Application: authed.Application.ToMastoPublic(),
|
||||||
Account: mastoAccount,
|
Account: mastoAccount,
|
||||||
|
// MediaAttachments: ,
|
||||||
Text: form.Status,
|
Text: form.Status,
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, mastoStatus)
|
c.JSON(http.StatusOK, mastoStatus)
|
||||||
|
@ -260,8 +268,8 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis model.Vis
|
||||||
// Advanced takes priority if it's set.
|
// Advanced takes priority if it's set.
|
||||||
// If it's not set, take whatever masto visibility is set.
|
// If it's not set, take whatever masto visibility is set.
|
||||||
// If *that's* not set either, then just take the account default.
|
// If *that's* not set either, then just take the account default.
|
||||||
if form.AdvancedVisibility != nil && form.AdvancedVisibility.Visibility != nil {
|
if form.VisibilityAdvanced != nil {
|
||||||
gtsBasicVis = *form.AdvancedVisibility.Visibility
|
gtsBasicVis = *form.VisibilityAdvanced
|
||||||
} else if form.Visibility != "" {
|
} else if form.Visibility != "" {
|
||||||
gtsBasicVis = util.ParseGTSVisFromMastoVis(form.Visibility)
|
gtsBasicVis = util.ParseGTSVisFromMastoVis(form.Visibility)
|
||||||
} else {
|
} else {
|
||||||
|
@ -274,40 +282,38 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis model.Vis
|
||||||
break
|
break
|
||||||
case model.VisibilityUnlocked:
|
case model.VisibilityUnlocked:
|
||||||
// for unlocked the user can set any combination of flags they like so look at them all to see if they're set and then apply them
|
// for unlocked the user can set any combination of flags they like so look at them all to see if they're set and then apply them
|
||||||
if form.AdvancedVisibility != nil {
|
if form.Federated != nil {
|
||||||
if form.AdvancedVisibility.Federated != nil {
|
gtsAdvancedVis.Federated = *form.Federated
|
||||||
gtsAdvancedVis.Federated = *form.AdvancedVisibility.Federated
|
|
||||||
}
|
|
||||||
|
|
||||||
if form.AdvancedVisibility.Boostable != nil {
|
|
||||||
gtsAdvancedVis.Boostable = *form.AdvancedVisibility.Boostable
|
|
||||||
}
|
|
||||||
|
|
||||||
if form.AdvancedVisibility.Replyable != nil {
|
|
||||||
gtsAdvancedVis.Replyable = *form.AdvancedVisibility.Replyable
|
|
||||||
}
|
|
||||||
|
|
||||||
if form.AdvancedVisibility.Likeable != nil {
|
|
||||||
gtsAdvancedVis.Likeable = *form.AdvancedVisibility.Likeable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if form.Boostable != nil {
|
||||||
|
gtsAdvancedVis.Boostable = *form.Boostable
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.Replyable != nil {
|
||||||
|
gtsAdvancedVis.Replyable = *form.Replyable
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.Likeable != nil {
|
||||||
|
gtsAdvancedVis.Likeable = *form.Likeable
|
||||||
|
}
|
||||||
|
|
||||||
case model.VisibilityFollowersOnly, model.VisibilityMutualsOnly:
|
case model.VisibilityFollowersOnly, model.VisibilityMutualsOnly:
|
||||||
// for followers or mutuals only, boostable will *always* be false, but the other fields can be set so check and apply them
|
// for followers or mutuals only, boostable will *always* be false, but the other fields can be set so check and apply them
|
||||||
gtsAdvancedVis.Boostable = false
|
gtsAdvancedVis.Boostable = false
|
||||||
|
|
||||||
if form.AdvancedVisibility != nil {
|
if form.Federated != nil {
|
||||||
if form.AdvancedVisibility.Federated != nil {
|
gtsAdvancedVis.Federated = *form.Federated
|
||||||
gtsAdvancedVis.Federated = *form.AdvancedVisibility.Federated
|
|
||||||
}
|
|
||||||
|
|
||||||
if form.AdvancedVisibility.Replyable != nil {
|
|
||||||
gtsAdvancedVis.Replyable = *form.AdvancedVisibility.Replyable
|
|
||||||
}
|
|
||||||
|
|
||||||
if form.AdvancedVisibility.Likeable != nil {
|
|
||||||
gtsAdvancedVis.Likeable = *form.AdvancedVisibility.Likeable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if form.Replyable != nil {
|
||||||
|
gtsAdvancedVis.Replyable = *form.Replyable
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.Likeable != nil {
|
||||||
|
gtsAdvancedVis.Likeable = *form.Likeable
|
||||||
|
}
|
||||||
|
|
||||||
case model.VisibilityDirect:
|
case model.VisibilityDirect:
|
||||||
// direct is pretty easy: there's only one possible setting so return it
|
// direct is pretty easy: there's only one possible setting so return it
|
||||||
gtsAdvancedVis.Federated = true
|
gtsAdvancedVis.Federated = true
|
||||||
|
@ -336,9 +342,18 @@ func (m *statusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccoun
|
||||||
repliedStatus := &model.Status{}
|
repliedStatus := &model.Status{}
|
||||||
repliedAccount := &model.Account{}
|
repliedAccount := &model.Account{}
|
||||||
// check replied status exists + is replyable
|
// check replied status exists + is replyable
|
||||||
if err := m.db.GetByID(form.InReplyToID, repliedStatus); err != nil || !repliedStatus.VisibilityAdvanced.Replyable {
|
if err := m.db.GetByID(form.InReplyToID, repliedStatus); err != nil {
|
||||||
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
|
if _, ok := err.(db.ErrNoEntries); ok {
|
||||||
|
return fmt.Errorf("status with id %s not replyable because it doesn't exist", form.InReplyToID)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !repliedStatus.VisibilityAdvanced.Replyable {
|
||||||
|
return fmt.Errorf("status with id %s is marked as not replyable", form.InReplyToID)
|
||||||
|
}
|
||||||
|
|
||||||
// check replied account is known to us
|
// check replied account is known to us
|
||||||
if err := m.db.GetByID(repliedStatus.AccountID, repliedAccount); err != nil {
|
if err := m.db.GetByID(repliedStatus.AccountID, repliedAccount); err != nil {
|
||||||
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
|
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
|
||||||
|
@ -373,3 +388,15 @@ func (m *statusModule) parseMediaIDs(form *advancedStatusCreateForm, thisAccount
|
||||||
status.Attachments = attachments
|
status.Attachments = attachments
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseLanguage(form *advancedStatusCreateForm, accountDefaultLanguage string, status *model.Status) error {
|
||||||
|
if form.Language != "" {
|
||||||
|
status.Language = form.Language
|
||||||
|
} else {
|
||||||
|
status.Language = accountDefaultLanguage
|
||||||
|
}
|
||||||
|
if status.Language == "" {
|
||||||
|
return errors.New("no language given either in status create form or account default")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -165,27 +165,13 @@ func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerSuccessful() {
|
||||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||||
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
|
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
|
||||||
ctx.Request.Form = url.Values{
|
ctx.Request.Form = url.Values{
|
||||||
"status": {"this is a brand new status!"},
|
"status": {"this is a brand new status!"},
|
||||||
"spoiler_text": {"hello hello"},
|
"spoiler_text": {"hello hello"},
|
||||||
"sensitive": {"true"},
|
"sensitive": {"true"},
|
||||||
"visibility": {"public"},
|
"visibility_advanced": {"mutuals_only"},
|
||||||
// Status string `form:"status"`
|
"likeable": {"false"},
|
||||||
// // Array of Attachment ids to be attached as media. If provided, status becomes optional, and poll cannot be used.
|
"replyable": {"false"},
|
||||||
// MediaIDs []string `form:"media_ids"`
|
"federated": {"false"},
|
||||||
// // Poll to include with this status.
|
|
||||||
// Poll *PollRequest `form:"poll"`
|
|
||||||
// // ID of the status being replied to, if status is a reply
|
|
||||||
// InReplyToID string `form:"in_reply_to_id"`
|
|
||||||
// // Mark status and attached media as sensitive?
|
|
||||||
// Sensitive bool `form:"sensitive"`
|
|
||||||
// // 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 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"`
|
|
||||||
}
|
}
|
||||||
suite.statusModule.statusCreatePOSTHandler(ctx)
|
suite.statusModule.statusCreatePOSTHandler(ctx)
|
||||||
|
|
||||||
|
@ -198,6 +184,7 @@ func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerSuccessful() {
|
||||||
defer result.Body.Close()
|
defer result.Body.Close()
|
||||||
b, err := ioutil.ReadAll(result.Body)
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
assert.NoError(suite.T(), err)
|
assert.NoError(suite.T(), err)
|
||||||
|
fmt.Println(string(b))
|
||||||
|
|
||||||
statusReply := &mastotypes.Status{}
|
statusReply := &mastotypes.Status{}
|
||||||
err = json.Unmarshal(b, statusReply)
|
err = json.Unmarshal(b, statusReply)
|
||||||
|
@ -206,7 +193,47 @@ func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerSuccessful() {
|
||||||
assert.Equal(suite.T(), "hello hello", statusReply.SpoilerText)
|
assert.Equal(suite.T(), "hello hello", statusReply.SpoilerText)
|
||||||
assert.Equal(suite.T(), "this is a brand new status!", statusReply.Content)
|
assert.Equal(suite.T(), "this is a brand new status!", statusReply.Content)
|
||||||
assert.True(suite.T(), statusReply.Sensitive)
|
assert.True(suite.T(), statusReply.Sensitive)
|
||||||
assert.Equal(suite.T(), mastotypes.VisibilityPublic, statusReply.Visibility)
|
assert.Equal(suite.T(), mastotypes.VisibilityPrivate, statusReply.Visibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerReplyToFail() {
|
||||||
|
t := suite.testTokens["local_account_1"]
|
||||||
|
oauthToken := oauth.PGTokenToOauthToken(t)
|
||||||
|
|
||||||
|
// setup
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
ctx, _ := gin.CreateTestContext(recorder)
|
||||||
|
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||||
|
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
||||||
|
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||||
|
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||||
|
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
|
||||||
|
ctx.Request.Form = url.Values{
|
||||||
|
"status": {"this is a reply to a status that doesn't exist"},
|
||||||
|
"spoiler_text": {"don't open cuz it won't work"},
|
||||||
|
"in_reply_to_id": {"3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50"},
|
||||||
|
}
|
||||||
|
suite.statusModule.statusCreatePOSTHandler(ctx)
|
||||||
|
|
||||||
|
// check response
|
||||||
|
|
||||||
|
// 1. we should have OK from our call to the function
|
||||||
|
suite.EqualValues(http.StatusOK, recorder.Code)
|
||||||
|
|
||||||
|
result := recorder.Result()
|
||||||
|
defer result.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(result.Body)
|
||||||
|
assert.NoError(suite.T(), err)
|
||||||
|
fmt.Println(string(b))
|
||||||
|
|
||||||
|
statusReply := &mastotypes.Status{}
|
||||||
|
err = json.Unmarshal(b, statusReply)
|
||||||
|
assert.NoError(suite.T(), err)
|
||||||
|
|
||||||
|
assert.Equal(suite.T(), "hello hello", statusReply.SpoilerText)
|
||||||
|
assert.Equal(suite.T(), "this is a brand new status!", statusReply.Content)
|
||||||
|
assert.True(suite.T(), statusReply.Sensitive)
|
||||||
|
assert.Equal(suite.T(), mastotypes.VisibilityPrivate, statusReply.Visibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStatusCreateTestSuite(t *testing.T) {
|
func TestStatusCreateTestSuite(t *testing.T) {
|
||||||
|
|
|
@ -63,12 +63,36 @@ func GenerateURIs(username string, protocol string, host string) *URIs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseGTSVisFromMastoVis converts a mastodon visibility into its gts equivalent.
|
||||||
func ParseGTSVisFromMastoVis(m mastotypes.Visibility) model.Visibility {
|
func ParseGTSVisFromMastoVis(m mastotypes.Visibility) model.Visibility {
|
||||||
// TODO: convert a masto vis into a gts vis
|
switch m {
|
||||||
|
case mastotypes.VisibilityPublic:
|
||||||
|
return model.VisibilityPublic
|
||||||
|
case mastotypes.VisibilityUnlisted:
|
||||||
|
return model.VisibilityUnlocked
|
||||||
|
case mastotypes.VisibilityPrivate:
|
||||||
|
return model.VisibilityFollowersOnly
|
||||||
|
case mastotypes.VisibilityDirect:
|
||||||
|
return model.VisibilityDirect
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseMastoVisFromGTSVis converts a gts visibility into its mastodon equivalent
|
||||||
func ParseMastoVisFromGTSVis(m model.Visibility) mastotypes.Visibility {
|
func ParseMastoVisFromGTSVis(m model.Visibility) mastotypes.Visibility {
|
||||||
// TODO: convert a gts vis into a masto vis
|
switch m {
|
||||||
|
case model.VisibilityPublic:
|
||||||
|
return mastotypes.VisibilityPublic
|
||||||
|
case model.VisibilityUnlocked:
|
||||||
|
return mastotypes.VisibilityUnlisted
|
||||||
|
case model.VisibilityFollowersOnly, model.VisibilityMutualsOnly:
|
||||||
|
return mastotypes.VisibilityPrivate
|
||||||
|
case model.VisibilityDirect:
|
||||||
|
return mastotypes.VisibilityDirect
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue