diff options
author | 2023-07-31 15:47:35 +0200 | |
---|---|---|
committer | 2023-07-31 15:47:35 +0200 | |
commit | 2796a2e82f16ade9872008878cf88299bd66b4e7 (patch) | |
tree | 76f7b69cc1da57ca10b71c57abf1892575bea100 /internal/api/client/timelines | |
parent | [performance] cache follow, follow request and block ID lists (#2027) (diff) | |
download | gotosocial-2796a2e82f16ade9872008878cf88299bd66b4e7.tar.xz |
[feature] Hashtag federation (in/out), hashtag client API endpoints (#2032)
* update go-fed
* do the things
* remove unused columns from tags
* update to latest lingo from main
* further tag shenanigans
* serve stub page at tag endpoint
* we did it lads
* tests, oh tests, ohhh tests, oh tests (doo doo doo doo)
* swagger docs
* document hashtag usage + federation
* instanceGet
* don't bother parsing tag href
* rename whereStartsWith -> whereStartsLike
* remove GetOrCreateTag
* dont cache status tag timelineability
Diffstat (limited to 'internal/api/client/timelines')
-rw-r--r-- | internal/api/client/timelines/home.go | 6 | ||||
-rw-r--r-- | internal/api/client/timelines/list.go | 15 | ||||
-rw-r--r-- | internal/api/client/timelines/public.go | 6 | ||||
-rw-r--r-- | internal/api/client/timelines/tag.go | 146 | ||||
-rw-r--r-- | internal/api/client/timelines/timeline.go | 23 |
5 files changed, 164 insertions, 32 deletions
diff --git a/internal/api/client/timelines/home.go b/internal/api/client/timelines/home.go index c3f075d5e..963096f59 100644 --- a/internal/api/client/timelines/home.go +++ b/internal/api/client/timelines/home.go @@ -133,9 +133,9 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) { resp, errWithCode := m.processor.Timeline().HomeTimelineGet( c.Request.Context(), authed, - c.Query(MaxIDKey), - c.Query(SinceIDKey), - c.Query(MinIDKey), + c.Query(apiutil.MaxIDKey), + c.Query(apiutil.SinceIDKey), + c.Query(apiutil.MinIDKey), limit, local, ) diff --git a/internal/api/client/timelines/list.go b/internal/api/client/timelines/list.go index 8b4f7fad9..2e13e32cd 100644 --- a/internal/api/client/timelines/list.go +++ b/internal/api/client/timelines/list.go @@ -18,7 +18,6 @@ package timelines import ( - "errors" "net/http" "github.com/gin-gonic/gin" @@ -118,11 +117,9 @@ func (m *Module) ListTimelineGETHandler(c *gin.Context) { return } - targetListID := c.Param(IDKey) - if targetListID == "" { - err := errors.New("no list id specified") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) - return + targetListID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) } limit, errWithCode := apiutil.ParseLimit(c.Query(apiutil.LimitKey), 20, 40, 1) @@ -135,9 +132,9 @@ func (m *Module) ListTimelineGETHandler(c *gin.Context) { c.Request.Context(), authed, targetListID, - c.Query(MaxIDKey), - c.Query(SinceIDKey), - c.Query(MinIDKey), + c.Query(apiutil.MaxIDKey), + c.Query(apiutil.SinceIDKey), + c.Query(apiutil.MinIDKey), limit, ) if errWithCode != nil { diff --git a/internal/api/client/timelines/public.go b/internal/api/client/timelines/public.go index 96958e6a4..7b8acf1ca 100644 --- a/internal/api/client/timelines/public.go +++ b/internal/api/client/timelines/public.go @@ -144,9 +144,9 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) { resp, errWithCode := m.processor.Timeline().PublicTimelineGet( c.Request.Context(), authed, - c.Query(MaxIDKey), - c.Query(SinceIDKey), - c.Query(MinIDKey), + c.Query(apiutil.MaxIDKey), + c.Query(apiutil.SinceIDKey), + c.Query(apiutil.MinIDKey), limit, local, ) diff --git a/internal/api/client/timelines/tag.go b/internal/api/client/timelines/tag.go new file mode 100644 index 000000000..58754705b --- /dev/null +++ b/internal/api/client/timelines/tag.go @@ -0,0 +1,146 @@ +// 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 timelines + +import ( + "net/http" + + "github.com/gin-gonic/gin" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +// HomeTimelineGETHandler swagger:operation GET /api/v1/timelines/tag/{tag_name} tagTimeline +// +// See public statuses that use the given hashtag (case insensitive). +// +// The statuses will be returned in descending chronological order (newest first), with sequential IDs (bigger = newer). +// +// The returned Link header can be used to generate the previous and next queries when scrolling up or down a timeline. +// +// Example: +// +// ``` +// <https://example.org/api/v1/timelines/tag/example?limit=20&max_id=01FC3GSQ8A3MMJ43BPZSGEG29M>; rel="next", <https://example.org/api/v1/timelines/tag/example?limit=20&min_id=01FC3KJW2GYXSDDRA6RWNDM46M>; rel="prev" +// ```` +// +// --- +// tags: +// - timelines +// +// 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 since status 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 statuses to return. +// default: 20 +// minimum: 1 +// maximum: 40 +// in: query +// required: false +// +// security: +// - OAuth2 Bearer: +// - read:statuses +// +// responses: +// '200': +// name: statuses +// description: Array of statuses. +// schema: +// type: array +// items: +// "$ref": "#/definitions/status" +// headers: +// Link: +// type: string +// description: Links to the next and previous queries. +// '401': +// description: unauthorized +// '400': +// description: bad request +func (m *Module) TagTimelineGETHandler(c *gin.Context) { + authed, err := oauth.Authed(c, true, true, true, true) + if err != nil { + apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), 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 + } + + limit, errWithCode := apiutil.ParseLimit(c.Query(apiutil.LimitKey), 20, 40, 1) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + tagName, errWithCode := apiutil.ParseTagName(c.Param(apiutil.TagNameKey)) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + resp, errWithCode := m.processor.Timeline().TagTimelineGet( + c.Request.Context(), + authed.Account, + tagName, + c.Query(apiutil.MaxIDKey), + c.Query(apiutil.SinceIDKey), + c.Query(apiutil.MinIDKey), + limit, + ) + if errWithCode != nil { + apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) + return + } + + if resp.LinkHeader != "" { + c.Header("Link", resp.LinkHeader) + } + c.JSON(http.StatusOK, resp.Items) +} diff --git a/internal/api/client/timelines/timeline.go b/internal/api/client/timelines/timeline.go index 2580333d9..2362ca47e 100644 --- a/internal/api/client/timelines/timeline.go +++ b/internal/api/client/timelines/timeline.go @@ -21,28 +21,16 @@ import ( "net/http" "github.com/gin-gonic/gin" + apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/processing" ) const ( - // BasePath is the base URI path for serving timelines, minus the 'api' prefix. - BasePath = "/v1/timelines" - IDKey = "id" - // HomeTimeline is the path for the home timeline - HomeTimeline = BasePath + "/home" - // PublicTimeline is the path for the public (and public local) timeline + BasePath = "/v1/timelines" + HomeTimeline = BasePath + "/home" PublicTimeline = BasePath + "/public" - ListTimeline = BasePath + "/list/:" + IDKey - // MaxIDKey is the url query for setting a max status ID to return - MaxIDKey = "max_id" - // SinceIDKey is the url query for returning results newer than the given ID - SinceIDKey = "since_id" - // MinIDKey is the url query for returning results immediately newer than the given ID - MinIDKey = "min_id" - // LimitKey is for specifying maximum number of results to return. - LimitKey = "limit" - // LocalKey is for specifying whether only local statuses should be returned - LocalKey = "local" + ListTimeline = BasePath + "/list/:" + apiutil.IDKey + TagTimeline = BasePath + "/tag/:" + apiutil.TagNameKey ) type Module struct { @@ -59,4 +47,5 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H attachHandler(http.MethodGet, HomeTimeline, m.HomeTimelineGETHandler) attachHandler(http.MethodGet, PublicTimeline, m.PublicTimelineGETHandler) attachHandler(http.MethodGet, ListTimeline, m.ListTimelineGETHandler) + attachHandler(http.MethodGet, TagTimeline, m.TagTimelineGETHandler) } |