diff options
| author | 2023-05-25 10:37:38 +0200 | |
|---|---|---|
| committer | 2023-05-25 10:37:38 +0200 | |
| commit | f5c004d67d4ed66b6c6df100afec47174aa14ae0 (patch) | |
| tree | 45b72a6e90450d711e10571d844138186fe023c9 /internal/api/client/timelines | |
| parent | [docs] local docs hacking howto (#1816) (diff) | |
| download | gotosocial-f5c004d67d4ed66b6c6df100afec47174aa14ae0.tar.xz | |
[feature] Add List functionality (#1802)
* start working on lists
* further list work
* test list db functions nicely
* more work on lists
* peepoopeepoo
* poke
* start list timeline func
* we're getting there lads
* couldn't be me working on stuff... could it?
* hook up handlers
* fiddling
* weeee
* woah
* screaming, pissing
* fix streaming being a whiny baby
* lint, small test fix, swagger
* tidying up, testing
* fucked! by the linter
* move timelines to state like a boss
* add timeline start to tests using state
* invalidate lists
Diffstat (limited to 'internal/api/client/timelines')
| -rw-r--r-- | internal/api/client/timelines/home.go | 58 | ||||
| -rw-r--r-- | internal/api/client/timelines/list.go | 152 | ||||
| -rw-r--r-- | internal/api/client/timelines/public.go | 58 | ||||
| -rw-r--r-- | internal/api/client/timelines/timeline.go | 3 | 
4 files changed, 189 insertions, 82 deletions
| diff --git a/internal/api/client/timelines/home.go b/internal/api/client/timelines/home.go index f63d14fd3..f64d61287 100644 --- a/internal/api/client/timelines/home.go +++ b/internal/api/client/timelines/home.go @@ -18,9 +18,7 @@  package timelines  import ( -	"fmt"  	"net/http" -	"strconv"  	"github.com/gin-gonic/gin"  	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" @@ -120,49 +118,27 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) {  		return  	} -	maxID := "" -	maxIDString := c.Query(MaxIDKey) -	if maxIDString != "" { -		maxID = maxIDString -	} - -	sinceID := "" -	sinceIDString := c.Query(SinceIDKey) -	if sinceIDString != "" { -		sinceID = sinceIDString -	} - -	minID := "" -	minIDString := c.Query(MinIDKey) -	if minIDString != "" { -		minID = minIDString -	} - -	limit := 20 -	limitString := c.Query(LimitKey) -	if limitString != "" { -		i, err := strconv.ParseInt(limitString, 10, 32) -		if err != nil { -			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) -			return -		} -		limit = int(i) +	limit, errWithCode := apiutil.ParseLimit(c.Query(apiutil.LimitKey), 20) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return  	} -	local := false -	localString := c.Query(LocalKey) -	if localString != "" { -		i, err := strconv.ParseBool(localString) -		if err != nil { -			err := fmt.Errorf("error parsing %s: %s", LocalKey, err) -			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) -			return -		} -		local = i +	local, errWithCode := apiutil.ParseLocal(c.Query(apiutil.LocalKey), false) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return  	} -	resp, errWithCode := m.processor.HomeTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local) +	resp, errWithCode := m.processor.Timeline().HomeTimelineGet( +		c.Request.Context(), +		authed, +		c.Query(MaxIDKey), +		c.Query(SinceIDKey), +		c.Query(MinIDKey), +		limit, +		local, +	)  	if errWithCode != nil {  		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)  		return diff --git a/internal/api/client/timelines/list.go b/internal/api/client/timelines/list.go new file mode 100644 index 000000000..4f5232d8b --- /dev/null +++ b/internal/api/client/timelines/list.go @@ -0,0 +1,152 @@ +// 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 ( +	"errors" +	"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" +) + +// ListTimelineGETHandler swagger:operation GET /api/v1/timelines/list/{id} listTimeline +// +// See statuses/posts from the given list timeline. +// +// 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/list/01H0W619198FX7J54NF7EH1NG2?limit=20&max_id=01FC3GSQ8A3MMJ43BPZSGEG29M>; rel="next", <https://example.org/api/v1/timelines/list/01H0W619198FX7J54NF7EH1NG2?limit=20&min_id=01FC3KJW2GYXSDDRA6RWNDM46M>; rel="prev" +// ```` +// +//	--- +//	tags: +//	- timelines +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		type: string +//		description: ID of the list +//		in: path +//		required: true +//	- +//		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 *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 +//		in: query +//		required: false +// +//	security: +//	- OAuth2 Bearer: +//		- read:lists +// +//	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) ListTimelineGETHandler(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 +	} + +	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 +	} + +	limit, errWithCode := apiutil.ParseLimit(c.Query(apiutil.LimitKey), 20) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	resp, errWithCode := m.processor.Timeline().ListTimelineGet( +		c.Request.Context(), +		authed, +		targetListID, +		c.Query(MaxIDKey), +		c.Query(SinceIDKey), +		c.Query(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/public.go b/internal/api/client/timelines/public.go index a8a61c398..5be9fcaa8 100644 --- a/internal/api/client/timelines/public.go +++ b/internal/api/client/timelines/public.go @@ -18,9 +18,7 @@  package timelines  import ( -	"fmt"  	"net/http" -	"strconv"  	"github.com/gin-gonic/gin"  	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" @@ -131,49 +129,27 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {  		return  	} -	maxID := "" -	maxIDString := c.Query(MaxIDKey) -	if maxIDString != "" { -		maxID = maxIDString -	} - -	sinceID := "" -	sinceIDString := c.Query(SinceIDKey) -	if sinceIDString != "" { -		sinceID = sinceIDString -	} - -	minID := "" -	minIDString := c.Query(MinIDKey) -	if minIDString != "" { -		minID = minIDString -	} - -	limit := 20 -	limitString := c.Query(LimitKey) -	if limitString != "" { -		i, err := strconv.ParseInt(limitString, 10, 32) -		if err != nil { -			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) -			return -		} -		limit = int(i) +	limit, errWithCode := apiutil.ParseLimit(c.Query(apiutil.LimitKey), 20) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return  	} -	local := false -	localString := c.Query(LocalKey) -	if localString != "" { -		i, err := strconv.ParseBool(localString) -		if err != nil { -			err := fmt.Errorf("error parsing %s: %s", LocalKey, err) -			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) -			return -		} -		local = i +	local, errWithCode := apiutil.ParseLocal(c.Query(apiutil.LocalKey), false) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return  	} -	resp, errWithCode := m.processor.PublicTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local) +	resp, errWithCode := m.processor.Timeline().PublicTimelineGet( +		c.Request.Context(), +		authed, +		c.Query(MaxIDKey), +		c.Query(SinceIDKey), +		c.Query(MinIDKey), +		limit, +		local, +	)  	if errWithCode != nil {  		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)  		return diff --git a/internal/api/client/timelines/timeline.go b/internal/api/client/timelines/timeline.go index bf8ef1e2e..2580333d9 100644 --- a/internal/api/client/timelines/timeline.go +++ b/internal/api/client/timelines/timeline.go @@ -27,10 +27,12 @@ import (  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  	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 @@ -56,4 +58,5 @@ func New(processor *processing.Processor) *Module {  func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) {  	attachHandler(http.MethodGet, HomeTimeline, m.HomeTimelineGETHandler)  	attachHandler(http.MethodGet, PublicTimeline, m.PublicTimelineGETHandler) +	attachHandler(http.MethodGet, ListTimeline, m.ListTimelineGETHandler)  } | 
