Refactor/tidy (#261)

* tidy up streaming

* cut down code duplication

* test get followers/following

* test streaming processor

* fix some test models

* add TimeMustParse

* fix uri / url typo

* make trace logging less verbose

* make logging more consistent

* disable quote on logging

* remove context.Background

* remove many extraneous mastodon references

* regenerate swagger

* don't log query on no rows result

* log latency first for easier reading
This commit is contained in:
tobi 2021-10-04 15:24:19 +02:00 committed by GitHub
commit e04b187702
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
126 changed files with 1192 additions and 955 deletions

View file

@ -139,7 +139,7 @@ func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) {
return
}
l.Tracef("conversion successful, returning OK and mastosensitive account %+v", acctSensitive)
l.Tracef("conversion successful, returning OK and apisensitive account %+v", acctSensitive)
c.JSON(http.StatusOK, acctSensitive)
}

View file

@ -111,14 +111,14 @@ func (m *Module) emojiCreatePOSTHandler(c *gin.Context) {
return
}
mastoEmoji, err := m.processor.AdminEmojiCreate(c.Request.Context(), authed, form)
apiEmoji, err := m.processor.AdminEmojiCreate(c.Request.Context(), authed, form)
if err != nil {
l.Debugf("error creating emoji: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, mastoEmoji)
c.JSON(http.StatusOK, apiEmoji)
}
func validateCreateEmoji(form *model.EmojiCreateRequest) error {

View file

@ -101,12 +101,11 @@ func (m *Module) AppsPOSTHandler(c *gin.Context) {
return
}
mastoApp, err := m.processor.AppCreate(c.Request.Context(), authed, form)
apiApp, err := m.processor.AppCreate(c.Request.Context(), authed, form)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// done, return the new app information per the spec here: https://docs.joinmastodon.org/methods/apps/
c.JSON(http.StatusOK, mastoApp)
c.JSON(http.StatusOK, apiApp)
}

View file

@ -124,7 +124,7 @@ func (suite *AuthTestSuite) SetupTest() {
}
}
suite.oauthServer = oauth.New(suite.db, log)
suite.oauthServer = oauth.New(context.Background(), suite.db, log)
if err := suite.db.Put(context.Background(), suite.testAccount); err != nil {
logrus.Panicf("could not insert test account into db: %s", err)

View file

@ -35,7 +35,7 @@ import (
// AuthorizeGETHandler should be served as GET at https://example.org/oauth/authorize
// The idea here is to present an oauth authorize page to the user, with a button
// that they have to click to accept. See here: https://docs.joinmastodon.org/methods/apps/oauth/#authorize-a-user
// that they have to click to accept.
func (m *Module) AuthorizeGETHandler(c *gin.Context) {
l := m.log.WithField("func", "AuthorizeGETHandler")
s := sessions.Default(c)
@ -122,7 +122,6 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
// AuthorizePOSTHandler should be served as POST at https://example.org/oauth/authorize
// At this point we assume that the user has A) logged in and B) accepted that the app should act for them,
// so we should proceed with the authentication flow and generate an oauth token for them if we can.
// See here: https://docs.joinmastodon.org/methods/apps/oauth/#authorize-a-user
func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "AuthorizePOSTHandler")
s := sessions.Default(c)

View file

@ -36,7 +36,6 @@ type tokenBody struct {
// TokenPOSTHandler should be served as a POST at https://example.org/oauth/token
// The idea here is to serve an oauth access token to a user, which can be used for authorizing against non-public APIs.
// See https://docs.joinmastodon.org/methods/apps/oauth/#obtain-a-token
func (m *Module) TokenPOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "TokenPOSTHandler")
l.Trace("entered TokenPOSTHandler")

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 auth
import (
@ -7,10 +25,6 @@ import (
func (m *Module) clearSession(s sessions.Session) {
s.Clear()
// newOptions := router.SessionOptions(m.config)
// newOptions.MaxAge = -1 // instruct browser to delete cookie immediately
// s.Options(newOptions)
if err := s.Save(); err != nil {
panic(err)
}

View file

@ -108,14 +108,14 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {
}
l.Debug("calling processor media create func")
mastoAttachment, err := m.processor.MediaCreate(c.Request.Context(), authed, form)
apiAttachment, err := m.processor.MediaCreate(c.Request.Context(), authed, form)
if err != nil {
l.Debugf("error creating attachment: %s", err)
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, mastoAttachment)
c.JSON(http.StatusOK, apiAttachment)
}
func validateCreateMedia(form *model.AttachmentRequest, config *config.MediaConfig) error {

View file

@ -87,12 +87,12 @@ func (m *Module) StatusBoostPOSTHandler(c *gin.Context) {
return
}
mastoStatus, errWithCode := m.processor.StatusBoost(c.Request.Context(), authed, targetStatusID)
apiStatus, errWithCode := m.processor.StatusBoost(c.Request.Context(), authed, targetStatusID)
if errWithCode != nil {
l.Debugf("error processing status boost: %s", errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return
}
c.JSON(http.StatusOK, mastoStatus)
c.JSON(http.StatusOK, apiStatus)
}

View file

@ -84,12 +84,12 @@ func (m *Module) StatusBoostedByGETHandler(c *gin.Context) {
return
}
mastoAccounts, err := m.processor.StatusBoostedBy(c.Request.Context(), authed, targetStatusID)
apiAccounts, err := m.processor.StatusBoostedBy(c.Request.Context(), authed, targetStatusID)
if err != nil {
l.Debugf("error processing status boosted by request: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
return
}
c.JSON(http.StatusOK, mastoAccounts)
c.JSON(http.StatusOK, apiAccounts)
}

View file

@ -101,14 +101,14 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
return
}
mastoStatus, err := m.processor.StatusCreate(c.Request.Context(), authed, form)
apiStatus, err := m.processor.StatusCreate(c.Request.Context(), authed, form)
if err != nil {
l.Debugf("error processing status create: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
return
}
c.JSON(http.StatusOK, mastoStatus)
c.JSON(http.StatusOK, apiStatus)
}
func validateCreateStatus(form *model.AdvancedStatusCreateForm, config *config.StatusesConfig) error {

View file

@ -121,7 +121,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {
assert.Equal(suite.T(), "hello hello", statusReply.SpoilerText)
assert.Equal(suite.T(), "<p>this is a brand new status! <a href=\"http://localhost:8080/tags/helloworld\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>helloworld</span></a></p>", statusReply.Content)
assert.True(suite.T(), statusReply.Sensitive)
assert.Equal(suite.T(), model.VisibilityPrivate, statusReply.Visibility) // even though we set this status to mutuals only, it should serialize to private, because masto has no idea about mutuals_only
assert.Equal(suite.T(), model.VisibilityPrivate, statusReply.Visibility) // even though we set this status to mutuals only, it should serialize to private, because the mastodon api has no idea about mutuals_only
assert.Len(suite.T(), statusReply.Tags, 1)
assert.Equal(suite.T(), model.Tag{
Name: "helloworld",
@ -202,12 +202,12 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() {
assert.Equal(suite.T(), "<p>here is a rainbow emoji a few times! :rainbow: :rainbow: :rainbow:<br>here's an emoji that isn't in the db: :test_emoji:</p>", statusReply.Content)
assert.Len(suite.T(), statusReply.Emojis, 1)
mastoEmoji := statusReply.Emojis[0]
apiEmoji := statusReply.Emojis[0]
gtsEmoji := testrig.NewTestEmojis()["rainbow"]
assert.Equal(suite.T(), gtsEmoji.Shortcode, mastoEmoji.Shortcode)
assert.Equal(suite.T(), gtsEmoji.ImageURL, mastoEmoji.URL)
assert.Equal(suite.T(), gtsEmoji.ImageStaticURL, mastoEmoji.StaticURL)
assert.Equal(suite.T(), gtsEmoji.Shortcode, apiEmoji.Shortcode)
assert.Equal(suite.T(), gtsEmoji.ImageURL, apiEmoji.URL)
assert.Equal(suite.T(), gtsEmoji.ImageStaticURL, apiEmoji.StaticURL)
}
// Try to reply to a status that doesn't exist
@ -326,12 +326,12 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() {
gtsAttachment, err := suite.db.GetAttachmentByID(context.Background(), statusResponse.MediaAttachments[0].ID)
assert.NoError(suite.T(), err)
// convert it to a masto attachment
gtsAttachmentAsMasto, err := suite.tc.AttachmentToMasto(context.Background(), gtsAttachment)
// convert it to a api attachment
gtsAttachmentAsapi, err := suite.tc.AttachmentToAPIAttachment(context.Background(), gtsAttachment)
assert.NoError(suite.T(), err)
// compare it with what we have now
assert.EqualValues(suite.T(), statusResponse.MediaAttachments[0], gtsAttachmentAsMasto)
assert.EqualValues(suite.T(), statusResponse.MediaAttachments[0], gtsAttachmentAsapi)
// the status id of the attachment should now be set to the id of the status we just created
assert.Equal(suite.T(), statusResponse.ID, gtsAttachment.StatusID)

View file

@ -86,7 +86,7 @@ func (m *Module) StatusDELETEHandler(c *gin.Context) {
return
}
mastoStatus, err := m.processor.StatusDelete(c.Request.Context(), authed, targetStatusID)
apiStatus, err := m.processor.StatusDelete(c.Request.Context(), authed, targetStatusID)
if err != nil {
l.Debugf("error processing status delete: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
@ -94,10 +94,10 @@ func (m *Module) StatusDELETEHandler(c *gin.Context) {
}
// the status was already gone/never existed
if mastoStatus == nil {
if apiStatus == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
return
}
c.JSON(http.StatusOK, mastoStatus)
c.JSON(http.StatusOK, apiStatus)
}

View file

@ -83,12 +83,12 @@ func (m *Module) StatusFavePOSTHandler(c *gin.Context) {
return
}
mastoStatus, err := m.processor.StatusFave(c.Request.Context(), authed, targetStatusID)
apiStatus, err := m.processor.StatusFave(c.Request.Context(), authed, targetStatusID)
if err != nil {
l.Debugf("error processing status fave: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
return
}
c.JSON(http.StatusOK, mastoStatus)
c.JSON(http.StatusOK, apiStatus)
}

View file

@ -84,12 +84,12 @@ func (m *Module) StatusFavedByGETHandler(c *gin.Context) {
return
}
mastoAccounts, err := m.processor.StatusFavedBy(c.Request.Context(), authed, targetStatusID)
apiAccounts, err := m.processor.StatusFavedBy(c.Request.Context(), authed, targetStatusID)
if err != nil {
l.Debugf("error processing status faved by request: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
return
}
c.JSON(http.StatusOK, mastoAccounts)
c.JSON(http.StatusOK, apiAccounts)
}

View file

@ -83,12 +83,12 @@ func (m *Module) StatusGETHandler(c *gin.Context) {
return
}
mastoStatus, err := m.processor.StatusGet(c.Request.Context(), authed, targetStatusID)
apiStatus, err := m.processor.StatusGet(c.Request.Context(), authed, targetStatusID)
if err != nil {
l.Debugf("error processing status get: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
return
}
c.JSON(http.StatusOK, mastoStatus)
c.JSON(http.StatusOK, apiStatus)
}

View file

@ -57,61 +57,6 @@ func (suite *StatusGetTestSuite) TearDownTest() {
testrig.StandardStorageTeardown(suite.storage)
}
// Post a new status with some custom visibility settings
func (suite *StatusGetTestSuite) TestPostNewStatus() {
// 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 brand new status! #helloworld"},
// "spoiler_text": {"hello hello"},
// "sensitive": {"true"},
// "visibility_advanced": {"mutuals_only"},
// "likeable": {"false"},
// "replyable": {"false"},
// "federated": {"false"},
// }
// suite.statusModule.statusGETHandler(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)
// 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! #helloworld", statusReply.Content)
// assert.True(suite.T(), statusReply.Sensitive)
// assert.Equal(suite.T(), mastotypes.VisibilityPrivate, statusReply.Visibility)
// assert.Len(suite.T(), statusReply.Tags, 1)
// assert.Equal(suite.T(), mastotypes.Tag{
// Name: "helloworld",
// URL: "http://localhost:8080/tags/helloworld",
// }, statusReply.Tags[0])
// gtsTag := &gtsmodel.Tag{}
// err = suite.db.GetWhere("name", "helloworld", gtsTag)
// assert.NoError(suite.T(), err)
// assert.Equal(suite.T(), statusReply.Account.ID, gtsTag.FirstSeenFromAccountID)
}
func TestStatusGetTestSuite(t *testing.T) {
suite.Run(t, new(StatusGetTestSuite))
}

View file

@ -84,12 +84,12 @@ func (m *Module) StatusUnboostPOSTHandler(c *gin.Context) {
return
}
mastoStatus, errWithCode := m.processor.StatusUnboost(c.Request.Context(), authed, targetStatusID)
apiStatus, errWithCode := m.processor.StatusUnboost(c.Request.Context(), authed, targetStatusID)
if errWithCode != nil {
l.Debugf("error processing status unboost: %s", errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return
}
c.JSON(http.StatusOK, mastoStatus)
c.JSON(http.StatusOK, apiStatus)
}

View file

@ -83,12 +83,12 @@ func (m *Module) StatusUnfavePOSTHandler(c *gin.Context) {
return
}
mastoStatus, err := m.processor.StatusUnfave(c.Request.Context(), authed, targetStatusID)
apiStatus, err := m.processor.StatusUnfave(c.Request.Context(), authed, targetStatusID)
if err != nil {
l.Debugf("error processing status unfave: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
return
}
c.JSON(http.StatusOK, mastoStatus)
c.JSON(http.StatusOK, apiStatus)
}

View file

@ -18,7 +18,7 @@
package model
// Conversation represents a conversation with "direct message" visibility. See https://docs.joinmastodon.org/entities/conversation/
// Conversation represents a conversation with "direct message" visibility.
type Conversation struct {
// REQUIRED

View file

@ -1,32 +0,0 @@
/*
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
// Error represents an error message returned from the API. See https://docs.joinmastodon.org/entities/error/
type Error struct {
// REQUIRED
// The error message.
Error string `json:"error"`
// OPTIONAL
// A longer description of the error, mainly provided with the OAuth API.
ErrorDescription string `json:"error_description"`
}

View file

@ -18,7 +18,7 @@
package model
// FeaturedTag represents a hashtag that is featured on a profile. See https://docs.joinmastodon.org/entities/featuredtag/
// FeaturedTag represents a hashtag that is featured on a profile.
type FeaturedTag struct {
// The internal ID of the featured tag in the database.
ID string `json:"id"`

View file

@ -18,7 +18,7 @@
package model
// Filter represents a user-defined filter for determining which statuses should not be shown to the user. See https://docs.joinmastodon.org/entities/filter/
// Filter represents a user-defined filter for determining which statuses should not be shown to the user.
// If whole_word is true , client app should do:
// Define word constituent character for your app. In the official implementation, its [A-Za-z0-9_] in JavaScript, and [[:word:]] in Ruby.
// Ruby uses the POSIX character class (Letter | Mark | Decimal_Number | Connector_Punctuation).

View file

@ -18,7 +18,7 @@
package model
// History represents daily usage history of a hashtag. See https://docs.joinmastodon.org/entities/history/
// History represents daily usage history of a hashtag.
type History struct {
// UNIX timestamp on midnight of the given day (string cast from integer).
Day string `json:"day"`

View file

@ -1,33 +0,0 @@
/*
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
// IdentityProof represents a proof from an external identity provider. See https://docs.joinmastodon.org/entities/identityproof/
type IdentityProof struct {
// The name of the identity provider.
Provider string `json:"provider"`
// The account owner's username on the identity provider's service.
ProviderUsername string `json:"provider_username"`
// The account owner's profile URL on the identity provider.
ProfileURL string `json:"profile_url"`
// A link to a statement of identity proof, hosted by the identity provider.
ProofURL string `json:"proof_url"`
// When the identity proof was last updated.
UpdatedAt string `json:"updated_at"`
}

View file

@ -18,7 +18,7 @@
package model
// List represents a list of some users that the authenticated user follows. See https://docs.joinmastodon.org/entities/list/
// List represents a list of some users that the authenticated user follows.
type List struct {
// The internal database ID of the list.
ID string `json:"id"`

View file

@ -18,7 +18,7 @@
package model
// Marker represents the last read position within a user's timelines. See https://docs.joinmastodon.org/entities/marker/
// Marker represents the last read position within a user's timelines.
type Marker struct {
// Information about the user's position in the home timeline.
Home *TimelineMarker `json:"home"`
@ -26,7 +26,7 @@ type Marker struct {
Notifications *TimelineMarker `json:"notifications"`
}
// TimelineMarker contains information about a user's progress through a specific timeline. See https://docs.joinmastodon.org/entities/marker/
// TimelineMarker contains information about a user's progress through a specific timeline.
type TimelineMarker struct {
// The ID of the most recently viewed entity.
LastReadID string `json:"last_read_id"`

View file

@ -18,7 +18,7 @@
package model
// Notification represents a notification of an event relevant to the user. See https://docs.joinmastodon.org/entities/notification/
// Notification represents a notification of an event relevant to the user.
type Notification struct {
// REQUIRED

View file

@ -19,7 +19,6 @@
package model
// OAuthAuthorize represents a request sent to https://example.org/oauth/authorize
// See here: https://docs.joinmastodon.org/methods/apps/oauth/
type OAuthAuthorize struct {
// Forces the user to re-login, which is necessary for authorizing with multiple accounts from the same instance.
ForceLogin string `form:"force_login" json:"force_login"`

View file

@ -18,7 +18,7 @@
package model
// Preferences represents a user's preferences. See https://docs.joinmastodon.org/entities/preferences/
// Preferences represents a user's preferences.
type Preferences struct {
// Default visibility for new posts.
// public = Public post

View file

@ -18,7 +18,7 @@
package model
// PushSubscription represents a subscription to the push streaming server. See https://docs.joinmastodon.org/entities/pushsubscription/
// PushSubscription represents a subscription to the push streaming server.
type PushSubscription struct {
// The id of the push subscription in the database.
ID string `json:"id"`

View file

@ -18,7 +18,7 @@
package model
// Results represents the results of a search. See https://docs.joinmastodon.org/entities/results/
// Results represents the results of a search.
type Results struct {
// Accounts which match the given query
Accounts []Account `json:"accounts"`

View file

@ -18,7 +18,7 @@
package model
// ScheduledStatus represents a status that will be published at a future scheduled date. See https://docs.joinmastodon.org/entities/scheduledstatus/
// ScheduledStatus represents a status that will be published at a future scheduled date.
type ScheduledStatus struct {
ID string `json:"id"`
ScheduledAt string `json:"scheduled_at"`
@ -26,7 +26,7 @@ type ScheduledStatus struct {
MediaAttachments []Attachment `json:"media_attachments"`
}
// StatusParams represents parameters for a scheduled status. See https://docs.joinmastodon.org/entities/scheduledstatus/
// StatusParams represents parameters for a scheduled status.
type StatusParams struct {
Text string `json:"text"`
InReplyToID string `json:"in_reply_to_id,omitempty"`

View file

@ -20,7 +20,6 @@ package model
// Source represents display or publishing preferences of user's own account.
// Returned as an additional entity when verifying and updated credentials, as an attribute of Account.
// See https://docs.joinmastodon.org/entities/source/
type Source struct {
// The default post privacy to be used for new statuses.
// public = Public post

View file

@ -177,7 +177,7 @@ const (
VisibilityDirect Visibility = "direct"
)
// AdvancedStatusCreateForm wraps the mastodon status create form along with the GTS advanced
// AdvancedStatusCreateForm wraps the mastodon-compatible status create form along with the GTS advanced
// visibility settings.
//
// swagger:model advancedStatusCreateForm

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 model
// StatusTimelineResponse wraps a slice of statuses, ready to be serialized, along with the Link