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/lists | |
| 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/lists')
| -rw-r--r-- | internal/api/client/lists/list.go | 19 | ||||
| -rw-r--r-- | internal/api/client/lists/listaccounts.go | 156 | ||||
| -rw-r--r-- | internal/api/client/lists/listaccountsadd.go | 120 | ||||
| -rw-r--r-- | internal/api/client/lists/listaccountsremove.go | 120 | ||||
| -rw-r--r-- | internal/api/client/lists/listcreate.go | 106 | ||||
| -rw-r--r-- | internal/api/client/lists/listdelete.go | 91 | ||||
| -rw-r--r-- | internal/api/client/lists/listget.go | 95 | ||||
| -rw-r--r-- | internal/api/client/lists/listsget.go (renamed from internal/api/client/lists/listsgets.go) | 46 | ||||
| -rw-r--r-- | internal/api/client/lists/listupdate.go | 152 | 
9 files changed, 900 insertions, 5 deletions
| diff --git a/internal/api/client/lists/list.go b/internal/api/client/lists/list.go index b1c193397..515075271 100644 --- a/internal/api/client/lists/list.go +++ b/internal/api/client/lists/list.go @@ -25,8 +25,15 @@ import (  )  const ( +	IDKey = "id"  	// BasePath is the base path for serving the lists API, minus the 'api' prefix -	BasePath = "/v1/lists" +	BasePath       = "/v1/lists" +	BasePathWithID = BasePath + "/:" + IDKey +	AccountsPath   = BasePathWithID + "/accounts" +	MaxIDKey       = "max_id" +	LimitKey       = "limit" +	SinceIDKey     = "since_id" +	MinIDKey       = "min_id"  )  type Module struct { @@ -40,5 +47,15 @@ func New(processor *processing.Processor) *Module {  }  func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	// create / get / update / delete lists +	attachHandler(http.MethodPost, BasePath, m.ListCreatePOSTHandler)  	attachHandler(http.MethodGet, BasePath, m.ListsGETHandler) +	attachHandler(http.MethodGet, BasePathWithID, m.ListGETHandler) +	attachHandler(http.MethodPut, BasePathWithID, m.ListUpdatePUTHandler) +	attachHandler(http.MethodDelete, BasePathWithID, m.ListDELETEHandler) + +	// get / add / remove list accounts +	attachHandler(http.MethodGet, AccountsPath, m.ListAccountsGETHandler) +	attachHandler(http.MethodPost, AccountsPath, m.ListAccountsPOSTHandler) +	attachHandler(http.MethodDelete, AccountsPath, m.ListAccountsDELETEHandler)  } diff --git a/internal/api/client/lists/listaccounts.go b/internal/api/client/lists/listaccounts.go new file mode 100644 index 000000000..3a24cab27 --- /dev/null +++ b/internal/api/client/lists/listaccounts.go @@ -0,0 +1,156 @@ +// 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 lists + +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" +) + +// ListAccountsGETHandler swagger:operation GET /api/v1/list/{id}/accounts listAccounts +// +// Page through accounts in this list. +// +// 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/list/01H0W619198FX7J54NF7EH1NG2/accounts?limit=20&max_id=01FC3GSQ8A3MMJ43BPZSGEG29M>; rel="next", <https://example.org/api/v1/list/01H0W619198FX7J54NF7EH1NG2/accounts?limit=20&min_id=01FC3KJW2GYXSDDRA6RWNDM46M>; rel="prev" +// ```` +// +//	--- +//	tags: +//	- lists +// +//	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 list entries *OLDER* than the given max ID. +//			The account from the list entry with the specified ID will not be included in the response. +//		in: query +//		required: false +//	- +//		name: since_id +//		type: string +//		description: >- +//			Return only list entries *NEWER* than the given since ID. +//			The account from the list entry with the specified ID will not be included in the response. +//		in: query +//	- +//		name: min_id +//		type: string +//		description: >- +//			Return only list entries *IMMEDIATELY NEWER* than the given min ID. +//			The account from the list entry with the specified ID will not be included in the response. +//		in: query +//		required: false +//	- +//		name: limit +//		type: integer +//		description: Number of accounts to return. +//		default: 20 +//		in: query +//		required: false +// +//	security: +//	- OAuth2 Bearer: +//		- read:lists +// +//	responses: +//		'200': +//			headers: +//				Link: +//					type: string +//					description: Links to the next and previous queries. +//			name: accounts +//			description: Array of accounts. +//			schema: +//				type: array +//				items: +//					"$ref": "#/definitions/account" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) ListAccountsGETHandler(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.List().GetListAccounts( +		c.Request.Context(), +		authed.Account, +		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/lists/listaccountsadd.go b/internal/api/client/lists/listaccountsadd.go new file mode 100644 index 000000000..5cf907b06 --- /dev/null +++ b/internal/api/client/lists/listaccountsadd.go @@ -0,0 +1,120 @@ +// 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 lists + +import ( +	"errors" +	"net/http" + +	"github.com/gin-gonic/gin" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +// ListAccountsPOSTHandler swagger:operation POST /api/v1/list/{id}/accounts addListAccounts +// +// Add one or more accounts to the given list. +// +//	--- +//	tags: +//	- lists +// +//	consumes: +//	- application/json +//	- application/xml +//	- application/x-www-form-urlencoded +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		type: string +//		description: ID of the list +//		in: path +//		required: true +//	- +//		name: account_ids +//		type: array +//		items: +//			type: string +//		description: >- +//			Array of accountIDs to modify. +//			Each accountID must correspond to an account +//			that the requesting account follows. +//		in: formData +//		required: true +// +//	security: +//	- OAuth2 Bearer: +//		- read:lists +// +//	responses: +//		'200': +//			description: list accounts updated +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) ListAccountsPOSTHandler(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 +	} + +	form := &apimodel.ListAccountsChangeRequest{} +	if err := c.ShouldBind(form); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	if len(form.AccountIDs) == 0 { +		err := errors.New("no account IDs given") +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	if errWithCode := m.processor.List().AddToList(c.Request.Context(), authed.Account, targetListID, form.AccountIDs); errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	c.JSON(http.StatusOK, gin.H{}) +} diff --git a/internal/api/client/lists/listaccountsremove.go b/internal/api/client/lists/listaccountsremove.go new file mode 100644 index 000000000..6ce7e3cd3 --- /dev/null +++ b/internal/api/client/lists/listaccountsremove.go @@ -0,0 +1,120 @@ +// 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 lists + +import ( +	"errors" +	"net/http" + +	"github.com/gin-gonic/gin" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +// ListAccountsDELETEHandler swagger:operation DELETE /api/v1/list/{id}/accounts removeListAccounts +// +// Remove one or more accounts from the given list. +// +//	--- +//	tags: +//	- lists +// +//	consumes: +//	- application/json +//	- application/xml +//	- application/x-www-form-urlencoded +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		type: string +//		description: ID of the list +//		in: path +//		required: true +//	- +//		name: account_ids +//		type: array +//		items: +//			type: string +//		description: >- +//			Array of accountIDs to modify. +//			Each accountID must correspond to an account +//			that the requesting account follows. +//		in: formData +//		required: true +// +//	security: +//	- OAuth2 Bearer: +//		- read:lists +// +//	responses: +//		'200': +//			description: list accounts updated +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) ListAccountsDELETEHandler(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 +	} + +	form := &apimodel.ListAccountsChangeRequest{} +	if err := c.ShouldBind(form); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	if len(form.AccountIDs) == 0 { +		err := errors.New("no account IDs given") +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	if errWithCode := m.processor.List().RemoveFromList(c.Request.Context(), authed.Account, targetListID, form.AccountIDs); errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	c.JSON(http.StatusOK, gin.H{}) +} diff --git a/internal/api/client/lists/listcreate.go b/internal/api/client/lists/listcreate.go new file mode 100644 index 000000000..09a654c74 --- /dev/null +++ b/internal/api/client/lists/listcreate.go @@ -0,0 +1,106 @@ +// 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 lists + +import ( +	"net/http" +	"strings" + +	"github.com/gin-gonic/gin" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/oauth" +	"github.com/superseriousbusiness/gotosocial/internal/validate" +) + +// ListCreatePOSTHandler swagger:operation POST /api/v1/list listCreate +// +// Create a new list. +// +//	--- +//	tags: +//	- lists +// +//	consumes: +//	- application/json +//	- application/xml +//	- application/x-www-form-urlencoded +// +//	produces: +//	- application/json +// +//	security: +//	- OAuth2 Bearer: +//		- write:lists +// +//	responses: +//		'200': +//			description: "The newly created list." +//			schema: +//				"$ref": "#/definitions/list" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'403': +//			description: forbidden +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) ListCreatePOSTHandler(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 +	} + +	form := &apimodel.ListCreateRequest{} +	if err := c.ShouldBind(form); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	if err := validate.ListTitle(form.Title); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	repliesPolicy := gtsmodel.RepliesPolicy(strings.ToLower(form.RepliesPolicy)) +	if err := validate.ListRepliesPolicy(repliesPolicy); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	apiList, errWithCode := m.processor.List().Create(c.Request.Context(), authed.Account, form.Title, repliesPolicy) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	c.JSON(http.StatusOK, apiList) +} diff --git a/internal/api/client/lists/listdelete.go b/internal/api/client/lists/listdelete.go new file mode 100644 index 000000000..394ddfb6b --- /dev/null +++ b/internal/api/client/lists/listdelete.go @@ -0,0 +1,91 @@ +// 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 lists + +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" +) + +// ListDELETEHandler swagger:operation DELETE /api/v1/list/{id} listDelete +// +// Delete a single list with the given ID. +// +//	--- +//	tags: +//	- lists +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		type: string +//		description: ID of the list +//		in: path +//		required: true +// +//	security: +//	- OAuth2 Bearer: +//		- write:lists +// +//	responses: +//		'200': +//			description: list deleted +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) ListDELETEHandler(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 +	} + +	if errWithCode := m.processor.List().Delete(c.Request.Context(), authed.Account, targetListID); errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	c.JSON(http.StatusOK, gin.H{}) +} diff --git a/internal/api/client/lists/listget.go b/internal/api/client/lists/listget.go new file mode 100644 index 000000000..3aed594d4 --- /dev/null +++ b/internal/api/client/lists/listget.go @@ -0,0 +1,95 @@ +// 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 lists + +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" +) + +// ListGETHandler swagger:operation GET /api/v1/list/{id} list +// +// Get a single list with the given ID. +// +//	--- +//	tags: +//	- lists +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		type: string +//		description: ID of the list +//		in: path +//		required: true +// +//	security: +//	- OAuth2 Bearer: +//		- read:lists +// +//	responses: +//		'200': +//			name: list +//			description: Requested list. +//			schema: +//				"$ref": "#/definitions/list" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) ListGETHandler(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 +	} + +	resp, errWithCode := m.processor.List().Get(c.Request.Context(), authed.Account, targetListID) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	c.JSON(http.StatusOK, resp) +} diff --git a/internal/api/client/lists/listsgets.go b/internal/api/client/lists/listsget.go index 66b713611..f16152a9d 100644 --- a/internal/api/client/lists/listsgets.go +++ b/internal/api/client/lists/listsget.go @@ -26,9 +26,42 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) -// ListsGETHandler returns a list of lists created by/for the authed account +// ListsGETHandler swagger:operation GET /api/v1/lists lists +// +// Get all lists for owned by authorized user. +// +//	--- +//	tags: +//	- lists +// +//	produces: +//	- application/json +// +//	security: +//	- OAuth2 Bearer: +//		- read:lists +// +//	responses: +//		'200': +//			name: lists +//			description: Array of all lists owned by the requesting user. +//			schema: +//				type: array +//				items: +//					"$ref": "#/definitions/list" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error  func (m *Module) ListsGETHandler(c *gin.Context) { -	if _, err := oauth.Authed(c, true, true, true, true); err != nil { +	authed, err := oauth.Authed(c, true, true, true, true) +	if err != nil {  		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)  		return  	} @@ -38,6 +71,11 @@ func (m *Module) ListsGETHandler(c *gin.Context) {  		return  	} -	// todo: implement this; currently it's a no-op -	c.JSON(http.StatusOK, []string{}) +	lists, errWithCode := m.processor.List().GetAll(c.Request.Context(), authed.Account) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	c.JSON(http.StatusOK, lists)  } diff --git a/internal/api/client/lists/listupdate.go b/internal/api/client/lists/listupdate.go new file mode 100644 index 000000000..80c5a8be3 --- /dev/null +++ b/internal/api/client/lists/listupdate.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 lists + +import ( +	"errors" +	"net/http" +	"strings" + +	"github.com/gin-gonic/gin" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/oauth" +	"github.com/superseriousbusiness/gotosocial/internal/validate" +) + +// ListUpdatePUTHandler swagger:operation PUT /api/v1/list listUpdate +// +// Update an existing list. +// +//	--- +//	tags: +//	- lists +// +//	consumes: +//	- application/json +//	- application/xml +//	- application/x-www-form-urlencoded +// +//	produces: +//	- application/json +// +//	parameters: +//	- +//		name: id +//		type: string +//		description: ID of the list +//		in: path +//		required: true +//	- +//		name: title +//		type: string +//		description: Title of this list. +//		in: formData +//		example: Cool People +//	- +//		name: replies_policy +//		type: string +//		description: |- +//		  RepliesPolicy for this list. +//		  followed = Show replies to any followed user +//		  list = Show replies to members of the list +//		  none = Show replies to no one +//		in: formData +//		example: list +// +//	security: +//	- OAuth2 Bearer: +//		- write:lists +// +//	responses: +//		'200': +//			description: "The newly updated list." +//			schema: +//				"$ref": "#/definitions/list" +//		'400': +//			description: bad request +//		'401': +//			description: unauthorized +//		'403': +//			description: forbidden +//		'404': +//			description: not found +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) ListUpdatePUTHandler(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 +	} + +	form := &apimodel.ListUpdateRequest{} +	if err := c.ShouldBind(form); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	if form.Title != nil { +		if err := validate.ListTitle(*form.Title); err != nil { +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +			return +		} +	} + +	var repliesPolicy *gtsmodel.RepliesPolicy +	if form.RepliesPolicy != nil { +		rp := gtsmodel.RepliesPolicy(strings.ToLower(*form.RepliesPolicy)) + +		if err := validate.ListRepliesPolicy(rp); err != nil { +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +			return +		} + +		repliesPolicy = &rp +	} + +	if form.Title == nil && repliesPolicy == nil { +		err = errors.New("neither title nor replies_policy was set; nothing to update") +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) +		return +	} + +	apiList, errWithCode := m.processor.List().Update(c.Request.Context(), authed.Account, targetListID, form.Title, repliesPolicy) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) +		return +	} + +	c.JSON(http.StatusOK, apiList) +} | 
