diff options
Diffstat (limited to 'internal/api')
| -rw-r--r-- | internal/api/client.go | 4 | ||||
| -rw-r--r-- | internal/api/client/scheduledstatuses/scheduledstatus.go | 52 | ||||
| -rw-r--r-- | internal/api/client/scheduledstatuses/scheduledstatusdelete.go | 97 | ||||
| -rw-r--r-- | internal/api/client/scheduledstatuses/scheduledstatusesget.go | 136 | ||||
| -rw-r--r-- | internal/api/client/scheduledstatuses/scheduledstatusget.go | 98 | ||||
| -rw-r--r-- | internal/api/client/scheduledstatuses/scheduledstatusput.go | 131 | ||||
| -rw-r--r-- | internal/api/client/statuses/statuscreate.go | 5 | ||||
| -rw-r--r-- | internal/api/client/statuses/statuscreate_test.go | 19 | ||||
| -rw-r--r-- | internal/api/model/scheduledstatus.go | 50 | ||||
| -rw-r--r-- | internal/api/model/status.go | 1 |
10 files changed, 574 insertions, 19 deletions
diff --git a/internal/api/client.go b/internal/api/client.go index 0b977e59f..829c9326d 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -48,6 +48,7 @@ import ( "code.superseriousbusiness.org/gotosocial/internal/api/client/preferences" "code.superseriousbusiness.org/gotosocial/internal/api/client/push" "code.superseriousbusiness.org/gotosocial/internal/api/client/reports" + "code.superseriousbusiness.org/gotosocial/internal/api/client/scheduledstatuses" "code.superseriousbusiness.org/gotosocial/internal/api/client/search" "code.superseriousbusiness.org/gotosocial/internal/api/client/statuses" "code.superseriousbusiness.org/gotosocial/internal/api/client/streaming" @@ -95,6 +96,7 @@ type Client struct { preferences *preferences.Module // api/v1/preferences push *push.Module // api/v1/push reports *reports.Module // api/v1/reports + scheduledStatuses *scheduledstatuses.Module // api/v1/scheduled_statuses search *search.Module // api/v1/search, api/v2/search statuses *statuses.Module // api/v1/statuses streaming *streaming.Module // api/v1/streaming @@ -149,6 +151,7 @@ func (c *Client) Route(r *router.Router, m ...gin.HandlerFunc) { c.preferences.Route(h) c.push.Route(h) c.reports.Route(h) + c.scheduledStatuses.Route(h) c.search.Route(h) c.statuses.Route(h) c.streaming.Route(h) @@ -191,6 +194,7 @@ func NewClient(state *state.State, p *processing.Processor) *Client { preferences: preferences.New(p), push: push.New(p), reports: reports.New(p), + scheduledStatuses: scheduledstatuses.New(p), search: search.New(p), statuses: statuses.New(p), streaming: streaming.New(p, time.Second*30, 4096), diff --git a/internal/api/client/scheduledstatuses/scheduledstatus.go b/internal/api/client/scheduledstatuses/scheduledstatus.go new file mode 100644 index 000000000..4710640ff --- /dev/null +++ b/internal/api/client/scheduledstatuses/scheduledstatus.go @@ -0,0 +1,52 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 scheduledstatuses + +import ( + "net/http" + + "code.superseriousbusiness.org/gotosocial/internal/processing" + "github.com/gin-gonic/gin" +) + +const ( + // IDKey is for status UUIDs + IDKey = "id" + // BasePath is the base path for serving the scheduled statuses API, minus the 'api' prefix + BasePath = "/v1/scheduled_statuses" + // BasePathWithID is just the base path with the ID key in it. + // Use this anywhere you need to know the ID of the scheduled status being queried. + BasePathWithID = BasePath + "/:" + IDKey +) + +type Module struct { + processor *processing.Processor +} + +func New(processor *processing.Processor) *Module { + return &Module{ + processor: processor, + } +} + +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { + attachHandler(http.MethodGet, BasePath, m.ScheduledStatusesGETHandler) + attachHandler(http.MethodGet, BasePathWithID, m.ScheduledStatusGETHandler) + attachHandler(http.MethodPut, BasePathWithID, m.ScheduledStatusPUTHandler) + attachHandler(http.MethodDelete, BasePathWithID, m.ScheduledStatusDELETEHandler) +} diff --git a/internal/api/client/scheduledstatuses/scheduledstatusdelete.go b/internal/api/client/scheduledstatuses/scheduledstatusdelete.go new file mode 100644 index 000000000..efd645e2a --- /dev/null +++ b/internal/api/client/scheduledstatuses/scheduledstatusdelete.go @@ -0,0 +1,97 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 scheduledstatuses + +import ( + "net/http" + + apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util" + "code.superseriousbusiness.org/gotosocial/internal/gtserror" + + "github.com/gin-gonic/gin" +) + +// ScheduledStatusDELETEHandler swagger:operation DELETE /api/v1/scheduled_statuses/{id} deleteScheduledStatus +// +// Cancel a scheduled status with the given id. +// +// --- +// tags: +// - scheduled_statuses +// +// produces: +// - application/json +// +// parameters: +// - +// name: id +// type: string +// description: ID of the status +// in: path +// required: true +// +// security: +// - OAuth2 Bearer: +// - write:statuses +// +// responses: +// '200': +// description: status canceled +// '400': +// description: bad request +// '401': +// description: unauthorized +// '404': +// description: not found +// '406': +// description: not acceptable +// '500': +// description: internal server error +func (m *Module) ScheduledStatusDELETEHandler(c *gin.Context) { + authed, errWithCode := apiutil.TokenAuth(c, + true, true, true, true, + apiutil.ScopeWriteStatuses, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) + return + } + + targetScheduledStatusID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + errWithCode = m.processor.Status().ScheduledStatusesDelete( + c.Request.Context(), + authed.Account, + targetScheduledStatusID, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + c.JSON(http.StatusOK, apiutil.EmptyJSONObject) +} diff --git a/internal/api/client/scheduledstatuses/scheduledstatusesget.go b/internal/api/client/scheduledstatuses/scheduledstatusesget.go new file mode 100644 index 000000000..6d08ff18b --- /dev/null +++ b/internal/api/client/scheduledstatuses/scheduledstatusesget.go @@ -0,0 +1,136 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 scheduledstatuses + +import ( + "net/http" + + apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util" + "code.superseriousbusiness.org/gotosocial/internal/gtserror" + "code.superseriousbusiness.org/gotosocial/internal/paging" + "github.com/gin-gonic/gin" +) + +// ScheduledStatusesGETHandler swagger:operation GET /api/v1/scheduled_statuses getScheduledStatuses +// +// Get an array of statuses scheduled by authorized user. +// +// --- +// tags: +// - scheduled_statuses +// +// produces: +// - application/json +// +// parameters: +// - +// name: max_id +// type: string +// description: >- +// Return only statuses *OLDER* than the given max status ID. +// The status with the specified ID will not be included in the response. +// in: query +// required: false +// - +// name: since_id +// type: string +// description: >- +// Return only statuses *newer* than the given since status ID. +// The status with the specified ID will not be included in the response. +// in: query +// - +// name: min_id +// type: string +// description: >- +// Return only statuses *immediately newer* than the given min ID. +// The status with the specified ID will not be included in the response. +// in: query +// required: false +// - +// name: limit +// type: integer +// description: Number of scheduled statuses to return. +// default: 20 +// in: query +// required: false +// +// security: +// - OAuth2 Bearer: +// - read:statuses +// +// responses: +// '200': +// headers: +// Link: +// type: string +// description: Links to the next and previous queries. +// schema: +// type: array +// items: +// "$ref": "#/definitions/scheduledStatus" +// '400': +// description: bad request +// '401': +// description: unauthorized +// '404': +// description: not found +// '406': +// description: not acceptable +// '500': +// description: internal server error +func (m *Module) ScheduledStatusesGETHandler(c *gin.Context) { + authed, errWithCode := apiutil.TokenAuth(c, + true, true, true, true, + apiutil.ScopeReadStatuses, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) + return + } + + page, errWithCode := paging.ParseIDPage(c, + 1, // min limit + 80, // max limit + 20, // default limit + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + resp, errWithCode := m.processor.Status().ScheduledStatusesGetPage( + c.Request.Context(), + authed.Account, + page, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + if resp.LinkHeader != "" { + c.Header("Link", resp.LinkHeader) + } + + apiutil.JSON(c, http.StatusOK, resp.Items) +} diff --git a/internal/api/client/scheduledstatuses/scheduledstatusget.go b/internal/api/client/scheduledstatuses/scheduledstatusget.go new file mode 100644 index 000000000..893c44938 --- /dev/null +++ b/internal/api/client/scheduledstatuses/scheduledstatusget.go @@ -0,0 +1,98 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 scheduledstatuses + +import ( + "net/http" + + apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util" + "code.superseriousbusiness.org/gotosocial/internal/gtserror" + + "github.com/gin-gonic/gin" +) + +// ScheduledStatusGETHandler swagger:operation GET /api/v1/scheduled_statuses/{id} getScheduledStatus +// +// Get a scheduled status with the given id. +// +// --- +// tags: +// - scheduled_statuses +// +// produces: +// - application/json +// +// parameters: +// - +// name: id +// type: string +// description: ID of the status +// in: path +// required: true +// +// security: +// - OAuth2 Bearer: +// - read:statuses +// +// responses: +// '200': +// schema: +// "$ref": "#/definitions/scheduledStatus" +// '400': +// description: bad request +// '401': +// description: unauthorized +// '404': +// description: not found +// '406': +// description: not acceptable +// '500': +// description: internal server error +func (m *Module) ScheduledStatusGETHandler(c *gin.Context) { + authed, errWithCode := apiutil.TokenAuth(c, + true, true, true, true, + apiutil.ScopeReadStatuses, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) + return + } + + targetScheduledStatusID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + scheduledStatus, errWithCode := m.processor.Status().ScheduledStatusesGetOne( + c.Request.Context(), + authed.Account, + targetScheduledStatusID, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + apiutil.JSON(c, http.StatusOK, scheduledStatus) +} diff --git a/internal/api/client/scheduledstatuses/scheduledstatusput.go b/internal/api/client/scheduledstatuses/scheduledstatusput.go new file mode 100644 index 000000000..f037716e7 --- /dev/null +++ b/internal/api/client/scheduledstatuses/scheduledstatusput.go @@ -0,0 +1,131 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 scheduledstatuses + +import ( + "net/http" + "time" + + apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model" + apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util" + "code.superseriousbusiness.org/gotosocial/internal/gtserror" + + "github.com/gin-gonic/gin" +) + +// ScheduledStatusPUTHandler swagger:operation PUT /api/v1/scheduled_statuses/{id} updateScheduledStatus +// +// Update a scheduled status's publishing date +// +// --- +// tags: +// - scheduled_statuses +// +// produces: +// - application/json +// +// parameters: +// - +// name: id +// type: string +// description: ID of the status +// in: path +// required: true +// - +// name: scheduled_at +// x-go-name: ScheduledAt +// description: |- +// ISO 8601 Datetime at which to schedule a status. +// +// Must be at least 5 minutes in the future. +// type: string +// format: date-time +// in: formData +// +// security: +// - OAuth2 Bearer: +// - write:statuses +// +// responses: +// '200': +// schema: +// "$ref": "#/definitions/scheduledStatus" +// '400': +// description: bad request +// '401': +// description: unauthorized +// '404': +// description: not found +// '406': +// description: not acceptable +// '422': +// description: unprocessable content +// '500': +// description: internal server error +func (m *Module) ScheduledStatusPUTHandler(c *gin.Context) { + authed, errWithCode := apiutil.TokenAuth(c, + true, true, true, true, + apiutil.ScopeWriteStatuses, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + if authed.Account.IsMoving() { + apiutil.ForbiddenAfterMove(c) + return + } + + if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) + return + } + + targetScheduledStatusID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + form := &apimodel.ScheduledStatusUpdateRequest{} + if err := c.ShouldBind(form); err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + return + } + + now := time.Now() + if !now.Add(5 * time.Minute).Before(*form.ScheduledAt) { + const errText = "scheduled_at must be at least 5 minutes in the future" + apiutil.ErrorHandler(c, gtserror.NewErrorUnprocessableEntity(gtserror.New(errText), errText), m.processor.InstanceGetV1) + return + } + + scheduledStatus, errWithCode := m.processor.Status().ScheduledStatusesUpdate( + c.Request.Context(), + authed.Account, + targetScheduledStatusID, + form.ScheduledAt, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + apiutil.JSON(c, http.StatusOK, scheduledStatus) +} diff --git a/internal/api/client/statuses/statuscreate.go b/internal/api/client/statuses/statuscreate.go index 048acd421..167b59b23 100644 --- a/internal/api/client/statuses/statuscreate.go +++ b/internal/api/client/statuses/statuscreate.go @@ -181,7 +181,6 @@ import ( // // Providing this parameter with a *future* time will cause ScheduledStatus to be returned instead of Status. // Must be at least 5 minutes in the future. -// This feature isn't implemented yet. // // Providing this parameter with a *past* time will cause the status to be backdated, // and will not push it to the user's followers. This is intended for importing old statuses. @@ -256,6 +255,8 @@ import ( // description: not found // '406': // description: not acceptable +// '422': +// description: unprocessable content // '500': // description: internal server error // '501': @@ -300,7 +301,9 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) { authed.Account, authed.Application, form, + nil, ) + if errWithCode != nil { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return diff --git a/internal/api/client/statuses/statuscreate_test.go b/internal/api/client/statuses/statuscreate_test.go index 090dae593..84a6622a2 100644 --- a/internal/api/client/statuses/statuscreate_test.go +++ b/internal/api/client/statuses/statuscreate_test.go @@ -476,13 +476,24 @@ func (suite *StatusCreateTestSuite) TestPostNewScheduledStatus() { "scheduled_at": {"2080-10-04T15:32:02.018Z"}, }, "") - // We should have 501 from + // We should have OK from // our call to the function. - suite.Equal(http.StatusNotImplemented, recorder.Code) + suite.Equal(http.StatusOK, recorder.Code) - // We should have a helpful error message. + // A scheduled status with scheduled_at and status params should be returned. suite.Equal(`{ - "error": "Not Implemented: scheduled statuses are not yet supported" + "id": "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", + "media_attachments": [], + "params": { + "application_id": "01F8MGY43H3N2C8EWPR2FPYEXG", + "language": "", + "scheduled_at": null, + "sensitive": true, + "spoiler_text": "hello hello", + "text": "this is a brand new status! #helloworld", + "visibility": "private" + }, + "scheduled_at": "2080-10-04T15:32:02.018Z" }`, out) } diff --git a/internal/api/model/scheduledstatus.go b/internal/api/model/scheduledstatus.go index 1b074ab55..4acfde697 100644 --- a/internal/api/model/scheduledstatus.go +++ b/internal/api/model/scheduledstatus.go @@ -17,22 +17,46 @@ package model +import "time" + // ScheduledStatus represents a status that will be published at a future scheduled date. +// +// swagger:model scheduledStatus type ScheduledStatus struct { - ID string `json:"id"` - ScheduledAt string `json:"scheduled_at"` - Params *StatusParams `json:"params"` - MediaAttachments []Attachment `json:"media_attachments"` + ID string `json:"id"` + ScheduledAt string `json:"scheduled_at"` + Params *ScheduledStatusParams `json:"params"` + MediaAttachments []*Attachment `json:"media_attachments"` } // StatusParams represents parameters for a scheduled status. -type StatusParams struct { - Text string `json:"text"` - InReplyToID string `json:"in_reply_to_id,omitempty"` - MediaIDs []string `json:"media_ids,omitempty"` - Sensitive bool `json:"sensitive,omitempty"` - SpoilerText string `json:"spoiler_text,omitempty"` - Visibility string `json:"visibility"` - ScheduledAt string `json:"scheduled_at,omitempty"` - ApplicationID string `json:"application_id"` +type ScheduledStatusParams struct { + Text string `json:"text"` + MediaIDs []string `json:"media_ids,omitempty"` + Sensitive bool `json:"sensitive,omitempty"` + Poll *ScheduledStatusParamsPoll `json:"poll,omitempty"` + SpoilerText string `json:"spoiler_text,omitempty"` + Visibility Visibility `json:"visibility"` + InReplyToID string `json:"in_reply_to_id,omitempty"` + Language string `json:"language"` + ApplicationID string `json:"application_id"` + LocalOnly bool `json:"local_only,omitempty"` + ContentType StatusContentType `json:"content_type,omitempty"` + InteractionPolicy *InteractionPolicy `json:"interaction_policy,omitempty"` + ScheduledAt *string `json:"scheduled_at"` +} + +type ScheduledStatusParamsPoll struct { + Options []string `json:"options"` + ExpiresIn int `json:"expires_in"` + Multiple bool `json:"multiple"` + HideTotals bool `json:"hide_totals"` +} + +// ScheduledStatusUpdateRequest models a request to update the scheduled status publication date. +// +// swagger:ignore +type ScheduledStatusUpdateRequest struct { + // ISO 8601 Datetime at which to schedule a status. + ScheduledAt *time.Time `form:"scheduled_at" json:"scheduled_at"` } diff --git a/internal/api/model/status.go b/internal/api/model/status.go index 4561996ad..3edb4be5f 100644 --- a/internal/api/model/status.go +++ b/internal/api/model/status.go @@ -252,7 +252,6 @@ type StatusCreateRequest struct { // // Providing this parameter with a *future* time will cause ScheduledStatus to be returned instead of Status. // Must be at least 5 minutes in the future. - // This feature isn't implemented yet. // // Providing this parameter with a *past* time will cause the status to be backdated, // and will not push it to the user's followers. This is intended for importing old statuses. |
