diff options
Diffstat (limited to 'internal/api')
| -rw-r--r-- | internal/api/activitypub.go | 66 | ||||
| -rw-r--r-- | internal/api/activitypub/emoji/emoji.go (renamed from internal/api/client/emoji/emoji.go) | 19 | ||||
| -rw-r--r-- | internal/api/activitypub/emoji/emojiget.go (renamed from internal/api/s2s/emoji/emojiget.go) | 29 | ||||
| -rw-r--r-- | internal/api/activitypub/emoji/emojiget_test.go (renamed from internal/api/s2s/emoji/emojiget_test.go) | 31 | ||||
| -rw-r--r-- | internal/api/activitypub/users/common.go (renamed from internal/api/s2s/user/common.go) | 26 | ||||
| -rw-r--r-- | internal/api/activitypub/users/followers.go (renamed from internal/api/s2s/user/followers.go) | 18 | ||||
| -rw-r--r-- | internal/api/activitypub/users/following.go (renamed from internal/api/s2s/user/following.go) | 18 | ||||
| -rw-r--r-- | internal/api/activitypub/users/inboxpost.go (renamed from internal/api/s2s/user/inboxpost.go) | 14 | ||||
| -rw-r--r-- | internal/api/activitypub/users/inboxpost_test.go (renamed from internal/api/s2s/user/inboxpost_test.go) | 28 | ||||
| -rw-r--r-- | internal/api/activitypub/users/outboxget.go (renamed from internal/api/s2s/user/outboxget.go) | 20 | ||||
| -rw-r--r-- | internal/api/activitypub/users/outboxget_test.go (renamed from internal/api/s2s/user/outboxget_test.go) | 22 | ||||
| -rw-r--r-- | internal/api/activitypub/users/publickeyget.go (renamed from internal/api/s2s/user/publickeyget.go) | 18 | ||||
| -rw-r--r-- | internal/api/activitypub/users/repliesget.go (renamed from internal/api/s2s/user/repliesget.go) | 24 | ||||
| -rw-r--r-- | internal/api/activitypub/users/repliesget_test.go (renamed from internal/api/s2s/user/repliesget_test.go) | 26 | ||||
| -rw-r--r-- | internal/api/activitypub/users/statusget.go (renamed from internal/api/s2s/user/statusget.go) | 20 | ||||
| -rw-r--r-- | internal/api/activitypub/users/statusget_test.go (renamed from internal/api/s2s/user/statusget_test.go) | 16 | ||||
| -rw-r--r-- | internal/api/activitypub/users/user.go | 80 | ||||
| -rw-r--r-- | internal/api/activitypub/users/user_test.go (renamed from internal/api/s2s/user/user_test.go) | 34 | ||||
| -rw-r--r-- | internal/api/activitypub/users/userget.go (renamed from internal/api/s2s/user/userget.go) | 18 | ||||
| -rw-r--r-- | internal/api/activitypub/users/userget_test.go (renamed from internal/api/s2s/user/userget_test.go) | 14 | ||||
| -rw-r--r-- | internal/api/apimodule.go | 37 | ||||
| -rw-r--r-- | internal/api/auth.go | 64 | ||||
| -rw-r--r-- | internal/api/auth/auth.go | 117 | ||||
| -rw-r--r-- | internal/api/auth/auth_test.go (renamed from internal/api/client/auth/auth_test.go) | 24 | ||||
| -rw-r--r-- | internal/api/auth/authorize.go (renamed from internal/api/client/auth/authorize.go) | 46 | ||||
| -rw-r--r-- | internal/api/auth/authorize_test.go (renamed from internal/api/client/auth/authorize_test.go) | 10 | ||||
| -rw-r--r-- | internal/api/auth/callback.go (renamed from internal/api/client/auth/callback.go) | 48 | ||||
| -rw-r--r-- | internal/api/auth/oob.go (renamed from internal/api/client/auth/oob.go) | 18 | ||||
| -rw-r--r-- | internal/api/auth/signin.go (renamed from internal/api/client/auth/signin.go) | 20 | ||||
| -rw-r--r-- | internal/api/auth/token.go (renamed from internal/api/client/auth/token.go) | 12 | ||||
| -rw-r--r-- | internal/api/auth/token_test.go (renamed from internal/api/client/auth/token_test.go) | 0 | ||||
| -rw-r--r-- | internal/api/client.go | 129 | ||||
| -rw-r--r-- | internal/api/client/accounts/account_test.go (renamed from internal/api/client/account/account_test.go) | 8 | ||||
| -rw-r--r-- | internal/api/client/accounts/accountcreate.go (renamed from internal/api/client/account/accountcreate.go) | 24 | ||||
| -rw-r--r-- | internal/api/client/accounts/accountcreate_test.go (renamed from internal/api/client/account/accountcreate_test.go) | 2 | ||||
| -rw-r--r-- | internal/api/client/accounts/accountdelete.go (renamed from internal/api/client/account/accountdelete.go) | 16 | ||||
| -rw-r--r-- | internal/api/client/accounts/accountdelete_test.go (renamed from internal/api/client/account/accountdelete_test.go) | 16 | ||||
| -rw-r--r-- | internal/api/client/accounts/accountget.go (renamed from internal/api/client/account/accountget.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/accounts/accounts.go (renamed from internal/api/client/account/account.go) | 64 | ||||
| -rw-r--r-- | internal/api/client/accounts/accountupdate.go (renamed from internal/api/client/account/accountupdate.go) | 22 | ||||
| -rw-r--r-- | internal/api/client/accounts/accountupdate_test.go (renamed from internal/api/client/account/accountupdate_test.go) | 44 | ||||
| -rw-r--r-- | internal/api/client/accounts/accountverify.go (renamed from internal/api/client/account/accountverify.go) | 12 | ||||
| -rw-r--r-- | internal/api/client/accounts/accountverify_test.go (renamed from internal/api/client/account/accountverify_test.go) | 8 | ||||
| -rw-r--r-- | internal/api/client/accounts/block.go (renamed from internal/api/client/account/block.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/accounts/block_test.go (renamed from internal/api/client/account/block_test.go) | 10 | ||||
| -rw-r--r-- | internal/api/client/accounts/follow.go (renamed from internal/api/client/account/follow.go) | 20 | ||||
| -rw-r--r-- | internal/api/client/accounts/follow_test.go (renamed from internal/api/client/account/follow_test.go) | 10 | ||||
| -rw-r--r-- | internal/api/client/accounts/followers.go (renamed from internal/api/client/account/followers.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/accounts/following.go (renamed from internal/api/client/account/following.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/accounts/relationships.go (renamed from internal/api/client/account/relationships.go) | 18 | ||||
| -rw-r--r-- | internal/api/client/accounts/statuses.go (renamed from internal/api/client/account/statuses.go) | 26 | ||||
| -rw-r--r-- | internal/api/client/accounts/statuses_test.go (renamed from internal/api/client/account/statuses_test.go) | 12 | ||||
| -rw-r--r-- | internal/api/client/accounts/unblock.go (renamed from internal/api/client/account/unblock.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/accounts/unfollow.go (renamed from internal/api/client/account/unfollow.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/admin/accountaction.go | 18 | ||||
| -rw-r--r-- | internal/api/client/admin/admin.go | 41 | ||||
| -rw-r--r-- | internal/api/client/admin/admin_test.go | 2 | ||||
| -rw-r--r-- | internal/api/client/admin/domainblockcreate.go | 26 | ||||
| -rw-r--r-- | internal/api/client/admin/domainblockdelete.go | 14 | ||||
| -rw-r--r-- | internal/api/client/admin/domainblockget.go | 16 | ||||
| -rw-r--r-- | internal/api/client/admin/domainblocksget.go | 14 | ||||
| -rw-r--r-- | internal/api/client/admin/emojicategoriesget.go | 12 | ||||
| -rw-r--r-- | internal/api/client/admin/emojicreate.go | 22 | ||||
| -rw-r--r-- | internal/api/client/admin/emojidelete.go | 14 | ||||
| -rw-r--r-- | internal/api/client/admin/emojiget.go | 14 | ||||
| -rw-r--r-- | internal/api/client/admin/emojisget.go | 16 | ||||
| -rw-r--r-- | internal/api/client/admin/emojiupdate.go | 36 | ||||
| -rw-r--r-- | internal/api/client/admin/mediacleanup.go | 14 | ||||
| -rw-r--r-- | internal/api/client/admin/mediarefetch.go | 8 | ||||
| -rw-r--r-- | internal/api/client/app/app_test.go | 21 | ||||
| -rw-r--r-- | internal/api/client/apps/appcreate.go (renamed from internal/api/client/app/appcreate.go) | 26 | ||||
| -rw-r--r-- | internal/api/client/apps/apps.go (renamed from internal/api/security/useragentblock.go) | 24 | ||||
| -rw-r--r-- | internal/api/client/auth/auth.go | 105 | ||||
| -rw-r--r-- | internal/api/client/blocks/blocks.go | 17 | ||||
| -rw-r--r-- | internal/api/client/blocks/blocksget.go | 12 | ||||
| -rw-r--r-- | internal/api/client/bookmarks/bookmarks.go | 13 | ||||
| -rw-r--r-- | internal/api/client/bookmarks/bookmarks_test.go | 10 | ||||
| -rw-r--r-- | internal/api/client/bookmarks/bookmarksget.go | 14 | ||||
| -rw-r--r-- | internal/api/client/customemojis/customemojis.go (renamed from internal/api/client/app/app.go) | 21 | ||||
| -rw-r--r-- | internal/api/client/customemojis/customemojisget.go | 76 | ||||
| -rw-r--r-- | internal/api/client/emoji/emojisget.go | 58 | ||||
| -rw-r--r-- | internal/api/client/favourites/favourites.go | 17 | ||||
| -rw-r--r-- | internal/api/client/favourites/favourites_test.go | 2 | ||||
| -rw-r--r-- | internal/api/client/favourites/favouritesget.go | 12 | ||||
| -rw-r--r-- | internal/api/client/filters/filter.go (renamed from internal/api/client/filter/filter.go) | 17 | ||||
| -rw-r--r-- | internal/api/client/filters/filtersget.go (renamed from internal/api/client/filter/filtersget.go) | 8 | ||||
| -rw-r--r-- | internal/api/client/followrequests/authorize.go (renamed from internal/api/client/followrequest/authorize.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/followrequests/authorize_test.go (renamed from internal/api/client/followrequest/authorize_test.go) | 8 | ||||
| -rw-r--r-- | internal/api/client/followrequests/followrequest.go (renamed from internal/api/client/followrequest/followrequest.go) | 23 | ||||
| -rw-r--r-- | internal/api/client/followrequests/followrequest_test.go (renamed from internal/api/client/followrequest/followrequest_test.go) | 8 | ||||
| -rw-r--r-- | internal/api/client/followrequests/get.go (renamed from internal/api/client/followrequest/get.go) | 12 | ||||
| -rw-r--r-- | internal/api/client/followrequests/get_test.go (renamed from internal/api/client/followrequest/get_test.go) | 2 | ||||
| -rw-r--r-- | internal/api/client/followrequests/reject.go (renamed from internal/api/client/followrequest/reject.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/followrequests/reject_test.go (renamed from internal/api/client/followrequest/reject_test.go) | 6 | ||||
| -rw-r--r-- | internal/api/client/instance/instance.go | 21 | ||||
| -rw-r--r-- | internal/api/client/instance/instance_test.go | 2 | ||||
| -rw-r--r-- | internal/api/client/instance/instanceget.go | 8 | ||||
| -rw-r--r-- | internal/api/client/instance/instancepatch.go | 22 | ||||
| -rw-r--r-- | internal/api/client/instance/instancepeersget.go | 12 | ||||
| -rw-r--r-- | internal/api/client/list/listsgets.go | 25 | ||||
| -rw-r--r-- | internal/api/client/lists/list.go (renamed from internal/api/client/list/list.go) | 19 | ||||
| -rw-r--r-- | internal/api/client/lists/listsgets.go (renamed from internal/api/security/robots.go) | 49 | ||||
| -rw-r--r-- | internal/api/client/media/media.go | 27 | ||||
| -rw-r--r-- | internal/api/client/media/mediacreate.go | 34 | ||||
| -rw-r--r-- | internal/api/client/media/mediacreate_test.go | 53 | ||||
| -rw-r--r-- | internal/api/client/media/mediaget.go | 18 | ||||
| -rw-r--r-- | internal/api/client/media/mediaupdate.go | 28 | ||||
| -rw-r--r-- | internal/api/client/media/mediaupdate_test.go | 31 | ||||
| -rw-r--r-- | internal/api/client/notifications/notifications.go (renamed from internal/api/client/notification/notification.go) | 21 | ||||
| -rw-r--r-- | internal/api/client/notifications/notificationsclear.go (renamed from internal/api/client/notification/notificationsclear.go) | 12 | ||||
| -rw-r--r-- | internal/api/client/notifications/notificationsget.go (renamed from internal/api/client/notification/notificationsget.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/search/search.go | 23 | ||||
| -rw-r--r-- | internal/api/client/search/search_test.go | 2 | ||||
| -rw-r--r-- | internal/api/client/search/searchget.go | 26 | ||||
| -rw-r--r-- | internal/api/client/statuses/status.go (renamed from internal/api/client/status/status.go) | 77 | ||||
| -rw-r--r-- | internal/api/client/statuses/status_test.go (renamed from internal/api/client/status/status_test.go) | 8 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusbookmark.go (renamed from internal/api/client/status/statusbookmark.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusbookmark_test.go (renamed from internal/api/client/status/statusbookmark_test.go) | 8 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusboost.go (renamed from internal/api/client/status/statusboost.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusboost_test.go (renamed from internal/api/client/status/statusboost_test.go) | 28 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusboostedby.go (renamed from internal/api/client/status/statusboostedby.go) | 10 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusboostedby_test.go (renamed from internal/api/client/status/statusboostedby_test.go) | 8 | ||||
| -rw-r--r-- | internal/api/client/statuses/statuscontext.go (renamed from internal/api/client/status/statuscontext.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statuscreate.go (renamed from internal/api/client/status/statuscreate.go) | 22 | ||||
| -rw-r--r-- | internal/api/client/statuses/statuscreate_test.go (renamed from internal/api/client/status/statuscreate_test.go) | 52 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusdelete.go (renamed from internal/api/client/status/statusdelete.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusdelete_test.go (renamed from internal/api/client/status/statusdelete_test.go) | 12 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusfave.go (renamed from internal/api/client/status/statusfave.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusfave_test.go (renamed from internal/api/client/status/statusfave_test.go) | 19 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusfavedby.go (renamed from internal/api/client/status/statusfavedby.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusfavedby_test.go (renamed from internal/api/client/status/statusfavedby_test.go) | 12 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusget.go (renamed from internal/api/client/status/statusget.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusget_test.go (renamed from internal/api/client/status/statusget_test.go) | 2 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusunbookmark.go (renamed from internal/api/client/status/statusunbookmark.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusunbookmark_test.go (renamed from internal/api/client/status/statusunbookmark_test.go) | 8 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusunboost.go (renamed from internal/api/client/status/statusunboost.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusunfave.go (renamed from internal/api/client/status/statusunfave.go) | 14 | ||||
| -rw-r--r-- | internal/api/client/statuses/statusunfave_test.go (renamed from internal/api/client/status/statusunfave_test.go) | 22 | ||||
| -rw-r--r-- | internal/api/client/streaming/stream.go | 60 | ||||
| -rw-r--r-- | internal/api/client/streaming/streaming.go | 19 | ||||
| -rw-r--r-- | internal/api/client/streaming/streaming_test.go | 2 | ||||
| -rw-r--r-- | internal/api/client/timelines/home.go (renamed from internal/api/client/timeline/home.go) | 16 | ||||
| -rw-r--r-- | internal/api/client/timelines/public.go (renamed from internal/api/client/timeline/public.go) | 16 | ||||
| -rw-r--r-- | internal/api/client/timelines/timeline.go (renamed from internal/api/client/timeline/timeline.go) | 21 | ||||
| -rw-r--r-- | internal/api/client/user/passwordchange.go | 20 | ||||
| -rw-r--r-- | internal/api/client/user/user.go | 17 | ||||
| -rw-r--r-- | internal/api/client/user/user_test.go | 2 | ||||
| -rw-r--r-- | internal/api/fileserver.go | 55 | ||||
| -rw-r--r-- | internal/api/fileserver/fileserver.go (renamed from internal/api/client/fileserver/fileserver.go) | 28 | ||||
| -rw-r--r-- | internal/api/fileserver/fileserver_test.go (renamed from internal/api/client/fileserver/fileserver_test.go) | 12 | ||||
| -rw-r--r-- | internal/api/fileserver/servefile.go (renamed from internal/api/client/fileserver/servefile.go) | 33 | ||||
| -rw-r--r-- | internal/api/fileserver/servefile_test.go (renamed from internal/api/client/fileserver/servefile_test.go) | 2 | ||||
| -rw-r--r-- | internal/api/nodeinfo.go | 50 | ||||
| -rw-r--r-- | internal/api/nodeinfo/nodeinfo.go | 46 | ||||
| -rw-r--r-- | internal/api/nodeinfo/nodeinfoget.go (renamed from internal/api/s2s/nodeinfo/nodeinfoget.go) | 20 | ||||
| -rw-r--r-- | internal/api/s2s/emoji/emoji.go | 53 | ||||
| -rw-r--r-- | internal/api/s2s/nodeinfo/nodeinfo.go | 53 | ||||
| -rw-r--r-- | internal/api/s2s/user/user.go | 89 | ||||
| -rw-r--r-- | internal/api/security/extraheaders.go | 26 | ||||
| -rw-r--r-- | internal/api/security/flocblock.go | 31 | ||||
| -rw-r--r-- | internal/api/security/ratelimit.go | 70 | ||||
| -rw-r--r-- | internal/api/security/security.go | 65 | ||||
| -rw-r--r-- | internal/api/security/signaturecheck.go | 51 | ||||
| -rw-r--r-- | internal/api/security/tokencheck.go | 120 | ||||
| -rw-r--r-- | internal/api/util/errorhandling.go (renamed from internal/api/errorhandling.go) | 2 | ||||
| -rw-r--r-- | internal/api/util/mime.go (renamed from internal/api/mime.go) | 2 | ||||
| -rw-r--r-- | internal/api/util/negotiate.go (renamed from internal/api/negotiate.go) | 2 | ||||
| -rw-r--r-- | internal/api/util/signaturectx.go (renamed from internal/api/client/auth/util.go) | 22 | ||||
| -rw-r--r-- | internal/api/wellknown.go | 55 | ||||
| -rw-r--r-- | internal/api/wellknown/nodeinfo/nodeinfo.go | 47 | ||||
| -rw-r--r-- | internal/api/wellknown/nodeinfo/nodeinfoget.go (renamed from internal/api/s2s/nodeinfo/wellknownget.go) | 16 | ||||
| -rw-r--r-- | internal/api/wellknown/webfinger/webfinger.go (renamed from internal/api/s2s/webfinger/webfinger.go) | 18 | ||||
| -rw-r--r-- | internal/api/wellknown/webfinger/webfinger_test.go (renamed from internal/api/s2s/webfinger/webfinger_test.go) | 23 | ||||
| -rw-r--r-- | internal/api/wellknown/webfinger/webfingerget.go (renamed from internal/api/s2s/webfinger/webfingerget.go) | 45 | ||||
| -rw-r--r-- | internal/api/wellknown/webfinger/webfingerget_test.go (renamed from internal/api/s2s/webfinger/webfingerget_test.go) | 6 | 
175 files changed, 2080 insertions, 2279 deletions
diff --git a/internal/api/activitypub.go b/internal/api/activitypub.go new file mode 100644 index 000000000..68c3b81e0 --- /dev/null +++ b/internal/api/activitypub.go @@ -0,0 +1,66 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 api + +import ( +	"context" +	"net/url" + +	"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/emoji" +	"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users" +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/middleware" +	"github.com/superseriousbusiness/gotosocial/internal/processing" +	"github.com/superseriousbusiness/gotosocial/internal/router" +) + +type ActivityPub struct { +	emoji *emoji.Module +	users *users.Module + +	isURIBlocked func(context.Context, *url.URL) (bool, db.Error) +} + +func (a *ActivityPub) Route(r router.Router) { +	// create groupings for the 'emoji' and 'users' prefixes +	emojiGroup := r.AttachGroup("emoji") +	usersGroup := r.AttachGroup("users") + +	// instantiate + attach shared, non-global middlewares to both of these groups +	var ( +		rateLimitMiddleware      = middleware.RateLimit() // nolint:contextcheck +		signatureCheckMiddleware = middleware.SignatureCheck(a.isURIBlocked) +		gzipMiddleware           = middleware.Gzip() +		cacheControlMiddleware   = middleware.CacheControl("no-store") +	) +	emojiGroup.Use(rateLimitMiddleware, signatureCheckMiddleware, gzipMiddleware, cacheControlMiddleware) +	usersGroup.Use(rateLimitMiddleware, signatureCheckMiddleware, gzipMiddleware, cacheControlMiddleware) + +	a.emoji.Route(emojiGroup.Handle) +	a.users.Route(usersGroup.Handle) +} + +func NewActivityPub(db db.DB, p processing.Processor) *ActivityPub { +	return &ActivityPub{ +		emoji: emoji.New(p), +		users: users.New(p), + +		isURIBlocked: db.IsURIBlocked, +	} +} diff --git a/internal/api/client/emoji/emoji.go b/internal/api/activitypub/emoji/emoji.go index 871a12854..f0eab0f02 100644 --- a/internal/api/client/emoji/emoji.go +++ b/internal/api/activitypub/emoji/emoji.go @@ -21,30 +21,27 @@ package emoji  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// BasePath is the base path for serving the emoji API -	BasePath = "/api/v1/custom_emojis" +	// EmojiIDKey is for emoji IDs +	EmojiIDKey = "id" +	// EmojiBasePath is the base path for serving AP Emojis, minus the "emoji" prefix +	EmojiWithIDPath = "/:" + EmojiIDKey  ) -// Module implements the ClientAPIModule interface for everything related to emoji  type Module struct {  	processor processing.Processor  } -// New returns a new emoji module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, BasePath, m.EmojisGETHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, EmojiWithIDPath, m.EmojiGetHandler)  } diff --git a/internal/api/s2s/emoji/emojiget.go b/internal/api/activitypub/emoji/emojiget.go index 28a737f9a..4e5ca25f4 100644 --- a/internal/api/s2s/emoji/emojiget.go +++ b/internal/api/activitypub/emoji/emojiget.go @@ -19,54 +19,39 @@  package emoji  import ( -	"context"  	"encoding/json"  	"errors"  	"net/http"  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/ap" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  ) -// EmojiGetHandler  func (m *Module) EmojiGetHandler(c *gin.Context) { -	// usernames on our instance are always lowercase  	requestedEmojiID := strings.ToUpper(c.Param(EmojiIDKey))  	if requestedEmojiID == "" {  		err := errors.New("no emoji id specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	format, err := api.NegotiateAccept(c, api.ActivityPubAcceptHeaders...) +	format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubAcceptHeaders...)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	ctx := c.Request.Context() -	verifier, signed := c.Get(string(ap.ContextRequestingPublicKeyVerifier)) -	if signed { -		ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeyVerifier, verifier) -	} - -	signature, signed := c.Get(string(ap.ContextRequestingPublicKeySignature)) -	if signed { -		ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeySignature, signature) -	} - -	resp, errWithCode := m.processor.GetFediEmoji(ctx, requestedEmojiID, c.Request.URL) +	resp, errWithCode := m.processor.GetFediEmoji(apiutil.TransferSignatureContext(c), requestedEmojiID, c.Request.URL)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	b, err := json.Marshal(resp)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} diff --git a/internal/api/s2s/emoji/emojiget_test.go b/internal/api/activitypub/emoji/emojiget_test.go index 16bc9dc1a..5b06f7a33 100644 --- a/internal/api/s2s/emoji/emojiget_test.go +++ b/internal/api/activitypub/emoji/emojiget_test.go @@ -26,8 +26,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/s2s/emoji" -	"github.com/superseriousbusiness/gotosocial/internal/api/security" +	"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/emoji"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/email" @@ -35,7 +34,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/media"  	"github.com/superseriousbusiness/gotosocial/internal/messages" -	"github.com/superseriousbusiness/gotosocial/internal/oauth" +	"github.com/superseriousbusiness/gotosocial/internal/middleware"  	"github.com/superseriousbusiness/gotosocial/internal/processing"  	"github.com/superseriousbusiness/gotosocial/internal/storage"  	"github.com/superseriousbusiness/gotosocial/internal/typeutils" @@ -44,20 +43,20 @@ import (  type EmojiGetTestSuite struct {  	suite.Suite -	db             db.DB -	tc             typeutils.TypeConverter -	mediaManager   media.Manager -	federator      federation.Federator -	emailSender    email.Sender -	processor      processing.Processor -	storage        *storage.Driver -	oauthServer    oauth.Server -	securityModule *security.Module +	db           db.DB +	tc           typeutils.TypeConverter +	mediaManager media.Manager +	federator    federation.Federator +	emailSender  email.Sender +	processor    processing.Processor +	storage      *storage.Driver  	testEmojis   map[string]*gtsmodel.Emoji  	testAccounts map[string]*gtsmodel.Account  	emojiModule *emoji.Module + +	signatureCheck gin.HandlerFunc  }  func (suite *EmojiGetTestSuite) SetupSuite() { @@ -79,12 +78,12 @@ func (suite *EmojiGetTestSuite) SetupTest() {  	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.emojiModule = emoji.New(suite.processor).(*emoji.Module) -	suite.oauthServer = testrig.NewTestOauthServer(suite.db) -	suite.securityModule = security.New(suite.db, suite.oauthServer).(*security.Module) +	suite.emojiModule = emoji.New(suite.processor)  	testrig.StandardDBSetup(suite.db, suite.testAccounts)  	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") +	suite.signatureCheck = middleware.SignatureCheck(suite.db.IsURIBlocked) +  	suite.NoError(suite.processor.Start())  } @@ -108,7 +107,7 @@ func (suite *EmojiGetTestSuite) TestGetEmoji() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually. diff --git a/internal/api/s2s/user/common.go b/internal/api/activitypub/users/common.go index 0a215ebbd..bf7623774 100644 --- a/internal/api/s2s/user/common.go +++ b/internal/api/activitypub/users/common.go @@ -16,31 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user - -import ( -	"context" - -	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/ap" -) - -// transferContext transfers the signature verifier and signature from the gin context to the request context -func transferContext(c *gin.Context) context.Context { -	ctx := c.Request.Context() - -	verifier, signed := c.Get(string(ap.ContextRequestingPublicKeyVerifier)) -	if signed { -		ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeyVerifier, verifier) -	} - -	signature, signed := c.Get(string(ap.ContextRequestingPublicKeySignature)) -	if signed { -		ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeySignature, signature) -	} - -	return ctx -} +package users  // SwaggerCollection represents an activitypub collection.  // swagger:model swaggerCollection diff --git a/internal/api/s2s/user/followers.go b/internal/api/activitypub/users/followers.go index 675688311..69b9f52fd 100644 --- a/internal/api/s2s/user/followers.go +++ b/internal/api/activitypub/users/followers.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user +package users  import (  	"encoding/json" @@ -25,7 +25,7 @@ import (  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  ) @@ -35,31 +35,31 @@ func (m *Module) FollowersGETHandler(c *gin.Context) {  	requestedUsername := strings.ToLower(c.Param(UsernameKey))  	if requestedUsername == "" {  		err := errors.New("no username specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...) +	format, err := apiutil.NegotiateAccept(c, apiutil.HTMLOrActivityPubHeaders...)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if format == string(api.TextHTML) { +	if format == string(apiutil.TextHTML) {  		// redirect to the user's profile  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)  		return  	} -	resp, errWithCode := m.processor.GetFediFollowers(transferContext(c), requestedUsername, c.Request.URL) +	resp, errWithCode := m.processor.GetFediFollowers(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	b, err := json.Marshal(resp)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} diff --git a/internal/api/s2s/user/following.go b/internal/api/activitypub/users/following.go index d4404ea08..64337d4cd 100644 --- a/internal/api/s2s/user/following.go +++ b/internal/api/activitypub/users/following.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user +package users  import (  	"encoding/json" @@ -25,7 +25,7 @@ import (  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  ) @@ -35,31 +35,31 @@ func (m *Module) FollowingGETHandler(c *gin.Context) {  	requestedUsername := strings.ToLower(c.Param(UsernameKey))  	if requestedUsername == "" {  		err := errors.New("no username specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...) +	format, err := apiutil.NegotiateAccept(c, apiutil.HTMLOrActivityPubHeaders...)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if format == string(api.TextHTML) { +	if format == string(apiutil.TextHTML) {  		// redirect to the user's profile  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)  		return  	} -	resp, errWithCode := m.processor.GetFediFollowing(transferContext(c), requestedUsername, c.Request.URL) +	resp, errWithCode := m.processor.GetFediFollowing(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	b, err := json.Marshal(resp)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} diff --git a/internal/api/s2s/user/inboxpost.go b/internal/api/activitypub/users/inboxpost.go index fa6792cd2..1d8f9f923 100644 --- a/internal/api/s2s/user/inboxpost.go +++ b/internal/api/activitypub/users/inboxpost.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user +package users  import (  	"errors"  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror" //nolint:typecheck  ) @@ -34,18 +34,18 @@ func (m *Module) InboxPOSTHandler(c *gin.Context) {  	requestedUsername := strings.ToLower(c.Param(UsernameKey))  	if requestedUsername == "" {  		err := errors.New("no username specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if posted, err := m.processor.InboxPost(transferContext(c), c.Writer, c.Request); err != nil { +	if posted, err := m.processor.InboxPost(apiutil.TransferSignatureContext(c), c.Writer, c.Request); err != nil {  		if withCode, ok := err.(gtserror.WithCode); ok { -			api.ErrorHandler(c, withCode, m.processor.InstanceGet) +			apiutil.ErrorHandler(c, withCode, m.processor.InstanceGet)  		} else { -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		}  	} else if !posted {  		err := errors.New("unable to process request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  	}  } diff --git a/internal/api/s2s/user/inboxpost_test.go b/internal/api/activitypub/users/inboxpost_test.go index 3821406be..53b6b8aac 100644 --- a/internal/api/s2s/user/inboxpost_test.go +++ b/internal/api/activitypub/users/inboxpost_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user_test +package users_test  import (  	"bytes" @@ -32,7 +32,7 @@ import (  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/activity/pub"  	"github.com/superseriousbusiness/activity/streams" -	"github.com/superseriousbusiness/gotosocial/internal/api/s2s/user" +	"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -92,7 +92,7 @@ func (suite *InboxPostTestSuite) TestPostBlock() {  	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker)  	emailSender := testrig.NewEmailSender("../../../../web/template/", nil)  	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) -	userModule := user.New(processor).(*user.Module) +	userModule := users.New(processor)  	suite.NoError(processor.Start())  	// setup request @@ -105,13 +105,13 @@ func (suite *InboxPostTestSuite) TestPostBlock() {  	ctx.Request.Header.Set("Content-Type", "application/activity+json")  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: blockedAccount.Username,  		},  	} @@ -196,7 +196,7 @@ func (suite *InboxPostTestSuite) TestPostUnblock() {  	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker)  	emailSender := testrig.NewEmailSender("../../../../web/template/", nil)  	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) -	userModule := user.New(processor).(*user.Module) +	userModule := users.New(processor)  	suite.NoError(processor.Start())  	// setup request @@ -209,13 +209,13 @@ func (suite *InboxPostTestSuite) TestPostUnblock() {  	ctx.Request.Header.Set("Content-Type", "application/activity+json")  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: blockedAccount.Username,  		},  	} @@ -298,7 +298,7 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {  	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker)  	emailSender := testrig.NewEmailSender("../../../../web/template/", nil)  	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) -	userModule := user.New(processor).(*user.Module) +	userModule := users.New(processor)  	suite.NoError(processor.Start())  	// setup request @@ -311,13 +311,13 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {  	ctx.Request.Header.Set("Content-Type", "application/activity+json")  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: receivingAccount.Username,  		},  	} @@ -431,7 +431,7 @@ func (suite *InboxPostTestSuite) TestPostDelete() {  	emailSender := testrig.NewEmailSender("../../../../web/template/", nil)  	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker)  	suite.NoError(processor.Start()) -	userModule := user.New(processor).(*user.Module) +	userModule := users.New(processor)  	// setup request  	recorder := httptest.NewRecorder() @@ -443,13 +443,13 @@ func (suite *InboxPostTestSuite) TestPostDelete() {  	ctx.Request.Header.Set("Content-Type", "application/activity+json")  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: receivingAccount.Username,  		},  	} diff --git a/internal/api/s2s/user/outboxget.go b/internal/api/activitypub/users/outboxget.go index 726f86237..8e5d1a751 100644 --- a/internal/api/s2s/user/outboxget.go +++ b/internal/api/activitypub/users/outboxget.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user +package users  import (  	"encoding/json" @@ -27,7 +27,7 @@ import (  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  ) @@ -90,17 +90,17 @@ func (m *Module) OutboxGETHandler(c *gin.Context) {  	requestedUsername := strings.ToLower(c.Param(UsernameKey))  	if requestedUsername == "" {  		err := errors.New("no username specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...) +	format, err := apiutil.NegotiateAccept(c, apiutil.HTMLOrActivityPubHeaders...)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if format == string(api.TextHTML) { +	if format == string(apiutil.TextHTML) {  		// redirect to the user's profile  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)  		return @@ -111,7 +111,7 @@ func (m *Module) OutboxGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(pageString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", PageKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		page = i @@ -129,15 +129,15 @@ func (m *Module) OutboxGETHandler(c *gin.Context) {  		maxID = maxIDString  	} -	resp, errWithCode := m.processor.GetFediOutbox(transferContext(c), requestedUsername, page, maxID, minID, c.Request.URL) +	resp, errWithCode := m.processor.GetFediOutbox(apiutil.TransferSignatureContext(c), requestedUsername, page, maxID, minID, c.Request.URL)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	b, err := json.Marshal(resp)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} diff --git a/internal/api/s2s/user/outboxget_test.go b/internal/api/activitypub/users/outboxget_test.go index 35a048323..cd0518350 100644 --- a/internal/api/s2s/user/outboxget_test.go +++ b/internal/api/activitypub/users/outboxget_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user_test +package users_test  import (  	"context" @@ -30,7 +30,7 @@ import (  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/activity/streams"  	"github.com/superseriousbusiness/activity/streams/vocab" -	"github.com/superseriousbusiness/gotosocial/internal/api/s2s/user" +	"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/messages"  	"github.com/superseriousbusiness/gotosocial/testrig" @@ -55,13 +55,13 @@ func (suite *OutboxGetTestSuite) TestGetOutbox() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: targetAccount.Username,  		},  	} @@ -102,7 +102,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxFirstPage() {  	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker)  	emailSender := testrig.NewEmailSender("../../../../web/template/", nil)  	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) -	userModule := user.New(processor).(*user.Module) +	userModule := users.New(processor)  	suite.NoError(processor.Start())  	// setup request @@ -114,13 +114,13 @@ func (suite *OutboxGetTestSuite) TestGetOutboxFirstPage() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: targetAccount.Username,  		},  	} @@ -161,7 +161,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() {  	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker)  	emailSender := testrig.NewEmailSender("../../../../web/template/", nil)  	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) -	userModule := user.New(processor).(*user.Module) +	userModule := users.New(processor)  	suite.NoError(processor.Start())  	// setup request @@ -173,17 +173,17 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: targetAccount.Username,  		},  		gin.Param{ -			Key:   user.MaxIDKey, +			Key:   users.MaxIDKey,  			Value: "01F8MHAMCHF6Y650WCRSCP4WMY",  		},  	} diff --git a/internal/api/s2s/user/publickeyget.go b/internal/api/activitypub/users/publickeyget.go index 265c01ee5..d57aa2f9d 100644 --- a/internal/api/s2s/user/publickeyget.go +++ b/internal/api/activitypub/users/publickeyget.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user +package users  import (  	"encoding/json" @@ -25,7 +25,7 @@ import (  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  ) @@ -39,31 +39,31 @@ func (m *Module) PublicKeyGETHandler(c *gin.Context) {  	requestedUsername := strings.ToLower(c.Param(UsernameKey))  	if requestedUsername == "" {  		err := errors.New("no username specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...) +	format, err := apiutil.NegotiateAccept(c, apiutil.HTMLOrActivityPubHeaders...)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if format == string(api.TextHTML) { +	if format == string(apiutil.TextHTML) {  		// redirect to the user's profile  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)  		return  	} -	resp, errWithCode := m.processor.GetFediUser(transferContext(c), requestedUsername, c.Request.URL) +	resp, errWithCode := m.processor.GetFediUser(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	b, err := json.Marshal(resp)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} diff --git a/internal/api/s2s/user/repliesget.go b/internal/api/activitypub/users/repliesget.go index b3b20d0c2..253166446 100644 --- a/internal/api/s2s/user/repliesget.go +++ b/internal/api/activitypub/users/repliesget.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user +package users  import (  	"encoding/json" @@ -27,7 +27,7 @@ import (  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  ) @@ -97,7 +97,7 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {  	requestedUsername := strings.ToLower(c.Param(UsernameKey))  	if requestedUsername == "" {  		err := errors.New("no username specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -105,17 +105,17 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {  	requestedStatusID := strings.ToUpper(c.Param(StatusIDKey))  	if requestedStatusID == "" {  		err := errors.New("no status id specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...) +	format, err := apiutil.NegotiateAccept(c, apiutil.HTMLOrActivityPubHeaders...)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if format == string(api.TextHTML) { +	if format == string(apiutil.TextHTML) {  		// redirect to the status  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername+"/statuses/"+requestedStatusID)  		return @@ -126,7 +126,7 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(pageString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", PageKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		page = i @@ -138,7 +138,7 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(onlyOtherAccountsString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", OnlyOtherAccountsKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		onlyOtherAccounts = i @@ -150,15 +150,15 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {  		minID = minIDString  	} -	resp, errWithCode := m.processor.GetFediStatusReplies(transferContext(c), requestedUsername, requestedStatusID, page, onlyOtherAccounts, minID, c.Request.URL) +	resp, errWithCode := m.processor.GetFediStatusReplies(apiutil.TransferSignatureContext(c), requestedUsername, requestedStatusID, page, onlyOtherAccounts, minID, c.Request.URL)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	b, err := json.Marshal(resp)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} diff --git a/internal/api/s2s/user/repliesget_test.go b/internal/api/activitypub/users/repliesget_test.go index 5ab7f1ebd..3d8e0b12a 100644 --- a/internal/api/s2s/user/repliesget_test.go +++ b/internal/api/activitypub/users/repliesget_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user_test +package users_test  import (  	"context" @@ -32,7 +32,7 @@ import (  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/activity/streams"  	"github.com/superseriousbusiness/activity/streams/vocab" -	"github.com/superseriousbusiness/gotosocial/internal/api/s2s/user" +	"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/messages"  	"github.com/superseriousbusiness/gotosocial/testrig" @@ -58,17 +58,17 @@ func (suite *RepliesGetTestSuite) TestGetReplies() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: targetAccount.Username,  		},  		gin.Param{ -			Key:   user.StatusIDKey, +			Key:   users.StatusIDKey,  			Value: targetStatus.ID,  		},  	} @@ -111,7 +111,7 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() {  	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker)  	emailSender := testrig.NewEmailSender("../../../../web/template/", nil)  	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) -	userModule := user.New(processor).(*user.Module) +	userModule := users.New(processor)  	suite.NoError(processor.Start())  	// setup request @@ -123,17 +123,17 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: targetAccount.Username,  		},  		gin.Param{ -			Key:   user.StatusIDKey, +			Key:   users.StatusIDKey,  			Value: targetStatus.ID,  		},  	} @@ -179,7 +179,7 @@ func (suite *RepliesGetTestSuite) TestGetRepliesLast() {  	federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker)  	emailSender := testrig.NewEmailSender("../../../../web/template/", nil)  	processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) -	userModule := user.New(processor).(*user.Module) +	userModule := users.New(processor)  	suite.NoError(processor.Start())  	// setup request @@ -191,17 +191,17 @@ func (suite *RepliesGetTestSuite) TestGetRepliesLast() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: targetAccount.Username,  		},  		gin.Param{ -			Key:   user.StatusIDKey, +			Key:   users.StatusIDKey,  			Value: targetStatus.ID,  		},  	} diff --git a/internal/api/s2s/user/statusget.go b/internal/api/activitypub/users/statusget.go index 3e3d6ea56..a72f8fd16 100644 --- a/internal/api/s2s/user/statusget.go +++ b/internal/api/activitypub/users/statusget.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user +package users  import (  	"encoding/json" @@ -25,7 +25,7 @@ import (  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  ) @@ -35,7 +35,7 @@ func (m *Module) StatusGETHandler(c *gin.Context) {  	requestedUsername := strings.ToLower(c.Param(UsernameKey))  	if requestedUsername == "" {  		err := errors.New("no username specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -43,31 +43,31 @@ func (m *Module) StatusGETHandler(c *gin.Context) {  	requestedStatusID := strings.ToUpper(c.Param(StatusIDKey))  	if requestedStatusID == "" {  		err := errors.New("no status id specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...) +	format, err := apiutil.NegotiateAccept(c, apiutil.HTMLOrActivityPubHeaders...)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if format == string(api.TextHTML) { +	if format == string(apiutil.TextHTML) {  		// redirect to the status  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername+"/statuses/"+requestedStatusID)  		return  	} -	resp, errWithCode := m.processor.GetFediStatus(transferContext(c), requestedUsername, requestedStatusID, c.Request.URL) +	resp, errWithCode := m.processor.GetFediStatus(apiutil.TransferSignatureContext(c), requestedUsername, requestedStatusID, c.Request.URL)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	b, err := json.Marshal(resp)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} diff --git a/internal/api/s2s/user/statusget_test.go b/internal/api/activitypub/users/statusget_test.go index 42f7dbb1b..37d311e7b 100644 --- a/internal/api/s2s/user/statusget_test.go +++ b/internal/api/activitypub/users/statusget_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user_test +package users_test  import (  	"context" @@ -31,7 +31,7 @@ import (  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/activity/streams"  	"github.com/superseriousbusiness/activity/streams/vocab" -	"github.com/superseriousbusiness/gotosocial/internal/api/s2s/user" +	"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -55,17 +55,17 @@ func (suite *StatusGetTestSuite) TestGetStatus() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: targetAccount.Username,  		},  		gin.Param{ -			Key:   user.StatusIDKey, +			Key:   users.StatusIDKey,  			Value: targetStatus.ID,  		},  	} @@ -114,17 +114,17 @@ func (suite *StatusGetTestSuite) TestGetStatusLowercase() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: strings.ToLower(targetAccount.Username),  		},  		gin.Param{ -			Key:   user.StatusIDKey, +			Key:   users.StatusIDKey,  			Value: strings.ToLower(targetStatus.ID),  		},  	} diff --git a/internal/api/activitypub/users/user.go b/internal/api/activitypub/users/user.go new file mode 100644 index 000000000..d2e02fb08 --- /dev/null +++ b/internal/api/activitypub/users/user.go @@ -0,0 +1,80 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 users + +import ( +	"net/http" + +	"github.com/gin-gonic/gin" +	"github.com/superseriousbusiness/gotosocial/internal/processing" +	"github.com/superseriousbusiness/gotosocial/internal/uris" +) + +const ( +	// UsernameKey is for account usernames. +	UsernameKey = "username" +	// StatusIDKey is for status IDs +	StatusIDKey = "status" +	// OnlyOtherAccountsKey is for filtering status responses. +	OnlyOtherAccountsKey = "only_other_accounts" +	// MinIDKey is for filtering status responses. +	MinIDKey = "min_id" +	// MaxIDKey is for filtering status responses. +	MaxIDKey = "max_id" +	// PageKey is for filtering status responses. +	PageKey = "page" + +	// BasePath is the base path for serving AP 'users' requests, minus the 'users' prefix. +	BasePath = "/:" + UsernameKey +	// PublicKeyPath is a path to a user's public key, for serving bare minimum AP representations. +	PublicKeyPath = BasePath + "/" + uris.PublicKeyPath +	// InboxPath is for serving POST requests to a user's inbox with the given username key. +	InboxPath = BasePath + "/" + uris.InboxPath +	// OutboxPath is for serving GET requests to a user's outbox with the given username key. +	OutboxPath = BasePath + "/" + uris.OutboxPath +	// FollowersPath is for serving GET request's to a user's followers list, with the given username key. +	FollowersPath = BasePath + "/" + uris.FollowersPath +	// FollowingPath is for serving GET request's to a user's following list, with the given username key. +	FollowingPath = BasePath + "/" + uris.FollowingPath +	// StatusPath is for serving GET requests to a particular status by a user, with the given username key and status ID +	StatusPath = BasePath + "/" + uris.StatusesPath + "/:" + StatusIDKey +	// StatusRepliesPath is for serving the replies collection of a status. +	StatusRepliesPath = StatusPath + "/replies" +) + +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.UsersGETHandler) +	attachHandler(http.MethodPost, InboxPath, m.InboxPOSTHandler) +	attachHandler(http.MethodGet, FollowersPath, m.FollowersGETHandler) +	attachHandler(http.MethodGet, FollowingPath, m.FollowingGETHandler) +	attachHandler(http.MethodGet, StatusPath, m.StatusGETHandler) +	attachHandler(http.MethodGet, PublicKeyPath, m.PublicKeyGETHandler) +	attachHandler(http.MethodGet, StatusRepliesPath, m.StatusRepliesGETHandler) +	attachHandler(http.MethodGet, OutboxPath, m.OutboxGETHandler) +} diff --git a/internal/api/s2s/user/user_test.go b/internal/api/activitypub/users/user_test.go index 444e9cab5..0f08366f0 100644 --- a/internal/api/s2s/user/user_test.go +++ b/internal/api/activitypub/users/user_test.go @@ -16,12 +16,12 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user_test +package users_test  import ( +	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/s2s/user" -	"github.com/superseriousbusiness/gotosocial/internal/api/security" +	"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/email" @@ -29,7 +29,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/media"  	"github.com/superseriousbusiness/gotosocial/internal/messages" -	"github.com/superseriousbusiness/gotosocial/internal/oauth" +	"github.com/superseriousbusiness/gotosocial/internal/middleware"  	"github.com/superseriousbusiness/gotosocial/internal/processing"  	"github.com/superseriousbusiness/gotosocial/internal/storage"  	"github.com/superseriousbusiness/gotosocial/internal/typeutils" @@ -39,15 +39,13 @@ import (  type UserStandardTestSuite struct {  	// standard suite interfaces  	suite.Suite -	db             db.DB -	tc             typeutils.TypeConverter -	mediaManager   media.Manager -	federator      federation.Federator -	emailSender    email.Sender -	processor      processing.Processor -	storage        *storage.Driver -	oauthServer    oauth.Server -	securityModule *security.Module +	db           db.DB +	tc           typeutils.TypeConverter +	mediaManager media.Manager +	federator    federation.Federator +	emailSender  email.Sender +	processor    processing.Processor +	storage      *storage.Driver  	// standard suite models  	testTokens       map[string]*gtsmodel.Token @@ -60,7 +58,9 @@ type UserStandardTestSuite struct {  	testBlocks       map[string]*gtsmodel.Block  	// module being tested -	userModule *user.Module +	userModule *users.Module + +	signatureCheck gin.HandlerFunc  }  func (suite *UserStandardTestSuite) SetupSuite() { @@ -88,12 +88,12 @@ func (suite *UserStandardTestSuite) SetupTest() {  	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.userModule = user.New(suite.processor).(*user.Module) -	suite.oauthServer = testrig.NewTestOauthServer(suite.db) -	suite.securityModule = security.New(suite.db, suite.oauthServer).(*security.Module) +	suite.userModule = users.New(suite.processor)  	testrig.StandardDBSetup(suite.db, suite.testAccounts)  	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") +	suite.signatureCheck = middleware.SignatureCheck(suite.db.IsURIBlocked) +  	suite.NoError(suite.processor.Start())  } diff --git a/internal/api/s2s/user/userget.go b/internal/api/activitypub/users/userget.go index 508c8be7d..51b0ebd72 100644 --- a/internal/api/s2s/user/userget.go +++ b/internal/api/activitypub/users/userget.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user +package users  import (  	"encoding/json" @@ -25,7 +25,7 @@ import (  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  ) @@ -43,31 +43,31 @@ func (m *Module) UsersGETHandler(c *gin.Context) {  	requestedUsername := strings.ToLower(c.Param(UsernameKey))  	if requestedUsername == "" {  		err := errors.New("no username specified in request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	format, err := api.NegotiateAccept(c, api.HTMLOrActivityPubHeaders...) +	format, err := apiutil.NegotiateAccept(c, apiutil.HTMLOrActivityPubHeaders...)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if format == string(api.TextHTML) { +	if format == string(apiutil.TextHTML) {  		// redirect to the user's profile  		c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)  		return  	} -	resp, errWithCode := m.processor.GetFediUser(transferContext(c), requestedUsername, c.Request.URL) +	resp, errWithCode := m.processor.GetFediUser(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	b, err := json.Marshal(resp)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} diff --git a/internal/api/s2s/user/userget_test.go b/internal/api/activitypub/users/userget_test.go index c656911d7..511634fc7 100644 --- a/internal/api/s2s/user/userget_test.go +++ b/internal/api/activitypub/users/userget_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package user_test +package users_test  import (  	"context" @@ -30,8 +30,8 @@ import (  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/activity/streams"  	"github.com/superseriousbusiness/activity/streams/vocab" +	"github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users"  	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" -	"github.com/superseriousbusiness/gotosocial/internal/api/s2s/user"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -55,13 +55,13 @@ func (suite *UserGetTestSuite) TestGetUser() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: targetAccount.Username,  		},  	} @@ -98,7 +98,7 @@ func (suite *UserGetTestSuite) TestGetUser() {  // TestGetUserPublicKeyDeleted checks whether the public key of a deleted account can still be dereferenced.  // This is needed by remote instances for authenticating delete requests and stuff like that.  func (suite *UserGetTestSuite) TestGetUserPublicKeyDeleted() { -	userModule := user.New(suite.processor).(*user.Module) +	userModule := users.New(suite.processor)  	targetAccount := suite.testAccounts["local_account_1"]  	// first delete the account, as though zork had deleted himself @@ -133,13 +133,13 @@ func (suite *UserGetTestSuite) TestGetUserPublicKeyDeleted() {  	ctx.Request.Header.Set("Date", signedRequest.DateHeader)  	// we need to pass the context through signature check first to set appropriate values on it -	suite.securityModule.SignatureCheck(ctx) +	suite.signatureCheck(ctx)  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   user.UsernameKey, +			Key:   users.UsernameKey,  			Value: targetAccount.Username,  		},  	} diff --git a/internal/api/apimodule.go b/internal/api/apimodule.go deleted file mode 100644 index 67dc3a850..000000000 --- a/internal/api/apimodule.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 api - -import ( -	"github.com/superseriousbusiness/gotosocial/internal/router" -) - -// ClientModule represents a chunk of code (usually contained in a single package) that adds a set -// of functionalities and/or side effects to a router, by mapping routes and/or middlewares onto it--in other words, a REST API ;) -// A ClientAPIMpdule with routes corresponds roughly to one main path of the gotosocial REST api, for example /api/v1/accounts/ or /oauth/ -type ClientModule interface { -	Route(s router.Router) error -} - -// FederationModule represents a chunk of code (usually contained in a single package) that adds a set -// of functionalities and/or side effects to a router, by mapping routes and/or middlewares onto it--in other words, a REST API ;) -// Unlike ClientAPIModule, federation API module is not intended to be interacted with by clients directly -- it is primarily a server-to-server interface. -type FederationModule interface { -	Route(s router.Router) error -} diff --git a/internal/api/auth.go b/internal/api/auth.go new file mode 100644 index 000000000..472c6922a --- /dev/null +++ b/internal/api/auth.go @@ -0,0 +1,64 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 api + +import ( +	"github.com/superseriousbusiness/gotosocial/internal/api/auth" +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/middleware" +	"github.com/superseriousbusiness/gotosocial/internal/oidc" +	"github.com/superseriousbusiness/gotosocial/internal/processing" +	"github.com/superseriousbusiness/gotosocial/internal/router" +) + +type Auth struct { +	routerSession *gtsmodel.RouterSession +	sessionName   string + +	auth *auth.Module +} + +// Route attaches 'auth' and 'oauth' groups to the given router. +func (a *Auth) Route(r router.Router) { +	// create groupings for the 'auth' and 'oauth' prefixes +	authGroup := r.AttachGroup("auth") +	oauthGroup := r.AttachGroup("oauth") + +	// instantiate + attach shared, non-global middlewares to both of these groups +	var ( +		rateLimitMiddleware    = middleware.RateLimit() // nolint:contextcheck +		gzipMiddleware         = middleware.Gzip() +		cacheControlMiddleware = middleware.CacheControl("private", "max-age=120") +		sessionMiddleware      = middleware.Session(a.sessionName, a.routerSession.Auth, a.routerSession.Crypt) +	) +	authGroup.Use(rateLimitMiddleware, gzipMiddleware, cacheControlMiddleware, sessionMiddleware) +	oauthGroup.Use(rateLimitMiddleware, gzipMiddleware, cacheControlMiddleware, sessionMiddleware) + +	a.auth.RouteAuth(authGroup.Handle) +	a.auth.RouteOauth(oauthGroup.Handle) +} + +func NewAuth(db db.DB, p processing.Processor, idp oidc.IDP, routerSession *gtsmodel.RouterSession, sessionName string) *Auth { +	return &Auth{ +		routerSession: routerSession, +		sessionName:   sessionName, +		auth:          auth.New(db, p, idp), +	} +} diff --git a/internal/api/auth/auth.go b/internal/api/auth/auth.go new file mode 100644 index 000000000..7ce992466 --- /dev/null +++ b/internal/api/auth/auth.go @@ -0,0 +1,117 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 ( +	"net/http" + +	"github.com/gin-contrib/sessions" +	"github.com/gin-gonic/gin" +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/oidc" +	"github.com/superseriousbusiness/gotosocial/internal/processing" +) + +const ( +	/* +		paths prefixed with 'auth' +	*/ + +	// AuthSignInPath is the API path for users to sign in through +	AuthSignInPath = "/sign_in" +	// AuthCheckYourEmailPath users land here after registering a new account, instructs them to confirm their email +	AuthCheckYourEmailPath = "/check_your_email" +	// AuthWaitForApprovalPath users land here after confirming their email +	// but before an admin approves their account (if such is required) +	AuthWaitForApprovalPath = "/wait_for_approval" +	// AuthAccountDisabledPath users land here when their account is suspended by an admin +	AuthAccountDisabledPath = "/account_disabled" +	// AuthCallbackPath is the API path for receiving callback tokens from external OIDC providers +	AuthCallbackPath = "/callback" + +	/* +		paths prefixed with 'oauth' +	*/ + +	// OauthTokenPath is the API path to use for granting token requests to users with valid credentials +	OauthTokenPath = "/token" // #nosec G101 else we get a hardcoded credentials warning +	// OauthAuthorizePath is the API path for authorization requests (eg., authorize this app to act on my behalf as a user) +	OauthAuthorizePath = "/authorize" +	// OauthFinalizePath is the API path for completing user registration with additional user details +	OauthFinalizePath = "/finalize" +	// OauthOobTokenPath is the path for serving an html representation of an oob token page. +	OauthOobTokenPath = "/oob" // #nosec G101 else we get a hardcoded credentials warning + +	/* +		params / session keys +	*/ + +	callbackStateParam   = "state" +	callbackCodeParam    = "code" +	sessionUserID        = "userid" +	sessionClientID      = "client_id" +	sessionRedirectURI   = "redirect_uri" +	sessionForceLogin    = "force_login" +	sessionResponseType  = "response_type" +	sessionScope         = "scope" +	sessionInternalState = "internal_state" +	sessionClientState   = "client_state" +	sessionClaims        = "claims" +	sessionAppID         = "app_id" +) + +type Module struct { +	db        db.DB +	processor processing.Processor +	idp       oidc.IDP +} + +// New returns an Auth module which provides both 'oauth' and 'auth' endpoints. +// +// It is safe to pass a nil idp if oidc is disabled. +func New(db db.DB, processor processing.Processor, idp oidc.IDP) *Module { +	return &Module{ +		db:        db, +		processor: processor, +		idp:       idp, +	} +} + +// RouteAuth routes all paths that should have an 'auth' prefix +func (m *Module) RouteAuth(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, AuthSignInPath, m.SignInGETHandler) +	attachHandler(http.MethodPost, AuthSignInPath, m.SignInPOSTHandler) +	attachHandler(http.MethodGet, AuthCallbackPath, m.CallbackGETHandler) +} + +// RouteOauth routes all paths that should have an 'oauth' prefix +func (m *Module) RouteOauth(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodPost, OauthTokenPath, m.TokenPOSTHandler) +	attachHandler(http.MethodGet, OauthAuthorizePath, m.AuthorizeGETHandler) +	attachHandler(http.MethodPost, OauthAuthorizePath, m.AuthorizePOSTHandler) +	attachHandler(http.MethodPost, OauthFinalizePath, m.FinalizePOSTHandler) +	attachHandler(http.MethodGet, OauthOobTokenPath, m.OobHandler) +} + +func (m *Module) clearSession(s sessions.Session) { +	s.Clear() +	if err := s.Save(); err != nil { +		panic(err) +	} +} diff --git a/internal/api/client/auth/auth_test.go b/internal/api/auth/auth_test.go index 75e958418..cb92850d0 100644 --- a/internal/api/client/auth/auth_test.go +++ b/internal/api/auth/auth_test.go @@ -20,7 +20,6 @@ package auth_test  import (  	"bytes" -	"context"  	"fmt"  	"net/http/httptest" @@ -28,7 +27,7 @@ import (  	"github.com/gin-contrib/sessions/memstore"  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/auth" +	"github.com/superseriousbusiness/gotosocial/internal/api/auth"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db" @@ -37,10 +36,9 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/media"  	"github.com/superseriousbusiness/gotosocial/internal/messages" -	"github.com/superseriousbusiness/gotosocial/internal/oauth" +	"github.com/superseriousbusiness/gotosocial/internal/middleware"  	"github.com/superseriousbusiness/gotosocial/internal/oidc"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  	"github.com/superseriousbusiness/gotosocial/internal/storage"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -54,7 +52,6 @@ type AuthStandardTestSuite struct {  	processor    processing.Processor  	emailSender  email.Sender  	idp          oidc.IDP -	oauthServer  oauth.Server  	// standard suite models  	testTokens       map[string]*gtsmodel.Token @@ -90,17 +87,10 @@ func (suite *AuthStandardTestSuite) SetupTest() {  	suite.db = testrig.NewTestDB()  	suite.storage = testrig.NewInMemoryStorage()  	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage) -	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker) -	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) +	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker) +	suite.emailSender = testrig.NewEmailSender("../../../web/template/", nil)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) - -	suite.oauthServer = testrig.NewTestOauthServer(suite.db) -	var err error -	suite.idp, err = oidc.NewIDP(context.Background()) -	if err != nil { -		panic(err) -	} -	suite.authModule = auth.New(suite.db, suite.idp, suite.processor).(*auth.Module) +	suite.authModule = auth.New(suite.db, suite.processor, suite.idp)  	testrig.StandardDBSetup(suite.db, suite.testAccounts)  } @@ -114,7 +104,7 @@ func (suite *AuthStandardTestSuite) newContext(requestMethod string, requestPath  	ctx, engine := testrig.CreateGinTestContext(recorder, nil)  	// load templates into the engine -	testrig.ConfigureTemplatesWithGin(engine, "../../../../web/template") +	testrig.ConfigureTemplatesWithGin(engine, "../../../web/template")  	// create the request  	protocol := config.GetProtocol() @@ -131,7 +121,7 @@ func (suite *AuthStandardTestSuite) newContext(requestMethod string, requestPath  	// trigger the session middleware on the context  	store := memstore.NewStore(make([]byte, 32), make([]byte, 32)) -	store.Options(router.SessionOptions()) +	store.Options(middleware.SessionOptions())  	sessionMiddleware := sessions.Sessions("gotosocial-localhost", store)  	sessionMiddleware(ctx) diff --git a/internal/api/client/auth/authorize.go b/internal/api/auth/authorize.go index f28d1dfc9..e504f6be2 100644 --- a/internal/api/client/auth/authorize.go +++ b/internal/api/auth/authorize.go @@ -27,8 +27,8 @@ import (  	"github.com/gin-contrib/sessions"  	"github.com/gin-gonic/gin"  	"github.com/google/uuid" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -42,8 +42,8 @@ import (  func (m *Module) AuthorizeGETHandler(c *gin.Context) {  	s := sessions.Default(c) -	if _, err := api.NegotiateAccept(c, api.HTMLAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.HTMLAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -51,20 +51,20 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {  	// If it's not set, then we don't know yet who the user is, so we need to redirect them to the sign in page.  	userID, ok := s.Get(sessionUserID).(string)  	if !ok || userID == "" { -		form := &model.OAuthAuthorize{} +		form := &apimodel.OAuthAuthorize{}  		if err := c.ShouldBind(form); err != nil {  			m.clearSession(s) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  			return  		}  		if errWithCode := saveAuthFormToSession(s, form); errWithCode != nil {  			m.clearSession(s) -			api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +			apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  			return  		} -		c.Redirect(http.StatusSeeOther, AuthSignInPath) +		c.Redirect(http.StatusSeeOther, "/auth"+AuthSignInPath)  		return  	} @@ -73,7 +73,7 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {  	if !ok || clientID == "" {  		m.clearSession(s)  		err := fmt.Errorf("key %s was not found in session", sessionClientID) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  		return  	} @@ -87,7 +87,7 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {  		} else {  			errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)  		} -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -101,7 +101,7 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {  		} else {  			errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)  		} -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -115,7 +115,7 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {  		} else {  			errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)  		} -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -128,7 +128,7 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {  	if !ok || redirect == "" {  		m.clearSession(s)  		err := fmt.Errorf("key %s was not found in session", sessionRedirectURI) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  		return  	} @@ -136,13 +136,13 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {  	if !ok || scope == "" {  		m.clearSession(s)  		err := fmt.Errorf("key %s was not found in session", sessionScope) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  		return  	}  	instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost())  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -206,7 +206,7 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {  	if len(errs) != 0 {  		errs = append(errs, oauth.HelpfulAdvice) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(errors.New("one or more missing keys on session during AuthorizePOSTHandler"), errs...), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(errors.New("one or more missing keys on session during AuthorizePOSTHandler"), errs...), m.processor.InstanceGet)  		return  	} @@ -220,7 +220,7 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {  		} else {  			errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)  		} -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -234,7 +234,7 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {  		} else {  			errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)  		} -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -263,13 +263,13 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) {  	}  	if errWithCode := m.processor.OAuthHandleAuthorizeRequest(c.Writer, c.Request); errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  	}  }  // saveAuthFormToSession checks the given OAuthAuthorize form,  // and stores the values in the form into the session. -func saveAuthFormToSession(s sessions.Session, form *model.OAuthAuthorize) gtserror.WithCode { +func saveAuthFormToSession(s sessions.Session, form *apimodel.OAuthAuthorize) gtserror.WithCode {  	if form == nil {  		err := errors.New("OAuthAuthorize form was nil")  		return gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice) @@ -314,19 +314,19 @@ func saveAuthFormToSession(s sessions.Session, form *model.OAuthAuthorize) gtser  func ensureUserIsAuthorizedOrRedirect(ctx *gin.Context, user *gtsmodel.User, account *gtsmodel.Account) (redirected bool) {  	if user.ConfirmedAt.IsZero() { -		ctx.Redirect(http.StatusSeeOther, CheckYourEmailPath) +		ctx.Redirect(http.StatusSeeOther, "/auth"+AuthCheckYourEmailPath)  		redirected = true  		return  	}  	if !*user.Approved { -		ctx.Redirect(http.StatusSeeOther, WaitForApprovalPath) +		ctx.Redirect(http.StatusSeeOther, "/auth"+AuthWaitForApprovalPath)  		redirected = true  		return  	}  	if *user.Disabled || !account.SuspendedAt.IsZero() { -		ctx.Redirect(http.StatusSeeOther, AccountDisabledPath) +		ctx.Redirect(http.StatusSeeOther, "/auth"+AuthAccountDisabledPath)  		redirected = true  		return  	} diff --git a/internal/api/client/auth/authorize_test.go b/internal/api/auth/authorize_test.go index 738b3b910..ff65d041b 100644 --- a/internal/api/client/auth/authorize_test.go +++ b/internal/api/auth/authorize_test.go @@ -9,7 +9,7 @@ import (  	"github.com/gin-contrib/sessions"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/auth" +	"github.com/superseriousbusiness/gotosocial/internal/api/auth"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -34,7 +34,7 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {  				return []string{"confirmed_at"}  			},  			expectedStatusCode:     http.StatusSeeOther, -			expectedLocationHeader: auth.CheckYourEmailPath, +			expectedLocationHeader: "/auth" + auth.AuthCheckYourEmailPath,  		},  		{  			description: "user has their email confirmed but is not approved", @@ -44,7 +44,7 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {  				return []string{"confirmed_at", "email"}  			},  			expectedStatusCode:     http.StatusSeeOther, -			expectedLocationHeader: auth.WaitForApprovalPath, +			expectedLocationHeader: "/auth" + auth.AuthWaitForApprovalPath,  		},  		{  			description: "user has their email confirmed and is approved, but User entity has been disabled", @@ -56,7 +56,7 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {  				return []string{"confirmed_at", "email", "approved", "disabled"}  			},  			expectedStatusCode:     http.StatusSeeOther, -			expectedLocationHeader: auth.AccountDisabledPath, +			expectedLocationHeader: "/auth" + auth.AuthAccountDisabledPath,  		},  		{  			description: "user has their email confirmed and is approved, but Account entity has been suspended", @@ -69,7 +69,7 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {  				return []string{"confirmed_at", "email", "approved", "disabled"}  			},  			expectedStatusCode:     http.StatusSeeOther, -			expectedLocationHeader: auth.AccountDisabledPath, +			expectedLocationHeader: "/auth" + auth.AuthAccountDisabledPath,  		},  	} diff --git a/internal/api/client/auth/callback.go b/internal/api/auth/callback.go index c97abf7aa..d344b5d5f 100644 --- a/internal/api/client/auth/callback.go +++ b/internal/api/auth/callback.go @@ -29,7 +29,7 @@ import (  	"github.com/gin-contrib/sessions"  	"github.com/gin-gonic/gin"  	"github.com/google/uuid" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -47,6 +47,12 @@ type extraInfo struct {  // CallbackGETHandler parses a token from an external auth provider.  func (m *Module) CallbackGETHandler(c *gin.Context) { +	if !config.GetOIDCEnabled() { +		err := errors.New("oidc is not enabled for this server") +		apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet) +		return +	} +  	s := sessions.Default(c)  	// check the query vs session state parameter to mitigate csrf @@ -56,7 +62,7 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {  	if returnedInternalState == "" {  		m.clearSession(s)  		err := fmt.Errorf("%s parameter not found on callback query", callbackStateParam) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -65,14 +71,14 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {  	if !ok {  		m.clearSession(s)  		err := fmt.Errorf("key %s was not found in session", sessionInternalState) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if returnedInternalState != savedInternalState {  		m.clearSession(s)  		err := errors.New("mismatch between callback state and saved state") -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -81,14 +87,14 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {  	if code == "" {  		m.clearSession(s)  		err := fmt.Errorf("%s parameter not found on callback query", callbackCodeParam) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	claims, errWithCode := m.idp.HandleCallback(c.Request.Context(), code)  	if errWithCode != nil {  		m.clearSession(s) -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -98,7 +104,7 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {  	if !ok || clientID == "" {  		m.clearSession(s)  		err := fmt.Errorf("key %s was not found in session", sessionClientID) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  		return  	} @@ -112,21 +118,21 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {  		} else {  			errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)  		} -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	user, errWithCode := m.fetchUserForClaims(c.Request.Context(), claims, net.IP(c.ClientIP()), app.ID)  	if errWithCode != nil {  		m.clearSession(s) -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	if user == nil {  		// no user exists yet - let's ask them for their preferred username  		instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost())  		if errWithCode != nil { -			api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +			apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  			return  		} @@ -135,7 +141,7 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {  		s.Set(sessionAppID, app.ID)  		if err := s.Save(); err != nil {  			m.clearSession(s) -			api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  			return  		}  		c.HTML(http.StatusOK, "finalize.tmpl", gin.H{ @@ -148,10 +154,10 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {  	s.Set(sessionUserID, user.ID)  	if err := s.Save(); err != nil {  		m.clearSession(s) -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} -	c.Redirect(http.StatusFound, OauthAuthorizePath) +	c.Redirect(http.StatusFound, "/oauth"+OauthAuthorizePath)  }  // FinalizePOSTHandler registers the user after additional data has been provided @@ -161,7 +167,7 @@ func (m *Module) FinalizePOSTHandler(c *gin.Context) {  	form := &extraInfo{}  	if err := c.ShouldBind(form); err != nil {  		m.clearSession(s) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  		return  	} @@ -169,7 +175,7 @@ func (m *Module) FinalizePOSTHandler(c *gin.Context) {  	validationError := func(err error) {  		instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost())  		if errWithCode != nil { -			api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +			apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  			return  		}  		c.HTML(http.StatusOK, "finalize.tmpl", gin.H{ @@ -189,7 +195,7 @@ func (m *Module) FinalizePOSTHandler(c *gin.Context) {  	// see if the username is still available  	usernameAvailable, err := m.db.IsUsernameAvailable(c.Request.Context(), form.Username)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  		return  	}  	if !usernameAvailable { @@ -201,7 +207,7 @@ func (m *Module) FinalizePOSTHandler(c *gin.Context) {  	appID, ok := s.Get(sessionAppID).(string)  	if !ok {  		err := fmt.Errorf("key %s was not found in session", sessionAppID) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  		return  	} @@ -209,7 +215,7 @@ func (m *Module) FinalizePOSTHandler(c *gin.Context) {  	claims, ok := s.Get(sessionClaims).(*oidc.Claims)  	if !ok {  		err := fmt.Errorf("key %s was not found in session", sessionClaims) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  		return  	} @@ -217,7 +223,7 @@ func (m *Module) FinalizePOSTHandler(c *gin.Context) {  	user, errWithCode := m.createUserFromOIDC(c.Request.Context(), claims, form, net.IP(c.ClientIP()), appID)  	if errWithCode != nil {  		m.clearSession(s) -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	s.Delete(sessionClaims) @@ -225,10 +231,10 @@ func (m *Module) FinalizePOSTHandler(c *gin.Context) {  	s.Set(sessionUserID, user.ID)  	if err := s.Save(); err != nil {  		m.clearSession(s) -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} -	c.Redirect(http.StatusFound, OauthAuthorizePath) +	c.Redirect(http.StatusFound, "/oauth"+OauthAuthorizePath)  }  func (m *Module) fetchUserForClaims(ctx context.Context, claims *oidc.Claims, ip net.IP, appID string) (*gtsmodel.User, gtserror.WithCode) { diff --git a/internal/api/client/auth/oob.go b/internal/api/auth/oob.go index 92e49d328..97f9c0f8c 100644 --- a/internal/api/client/auth/oob.go +++ b/internal/api/auth/oob.go @@ -26,8 +26,8 @@ import (  	"github.com/gin-contrib/sessions"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -38,16 +38,18 @@ func (m *Module) OobHandler(c *gin.Context) {  	host := config.GetHost()  	instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), host)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} -	instanceGet := func(ctx context.Context, domain string) (*model.Instance, gtserror.WithCode) { return instance, nil } +	instanceGet := func(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode) { +		return instance, nil +	}  	oobToken := c.Query("code")  	if oobToken == "" {  		err := errors.New("no 'code' query value provided in callback redirect") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice), instanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error(), oauth.HelpfulAdvice), instanceGet)  		return  	} @@ -67,7 +69,7 @@ func (m *Module) OobHandler(c *gin.Context) {  	if len(errs) != 0 {  		errs = append(errs, oauth.HelpfulAdvice) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(errors.New("one or more missing keys on session during OobHandler"), errs...), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(errors.New("one or more missing keys on session during OobHandler"), errs...), m.processor.InstanceGet)  		return  	} @@ -81,7 +83,7 @@ func (m *Module) OobHandler(c *gin.Context) {  		} else {  			errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)  		} -		api.ErrorHandler(c, errWithCode, instanceGet) +		apiutil.ErrorHandler(c, errWithCode, instanceGet)  		return  	} @@ -95,7 +97,7 @@ func (m *Module) OobHandler(c *gin.Context) {  		} else {  			errWithCode = gtserror.NewErrorInternalError(err, safe, oauth.HelpfulAdvice)  		} -		api.ErrorHandler(c, errWithCode, instanceGet) +		apiutil.ErrorHandler(c, errWithCode, instanceGet)  		return  	} diff --git a/internal/api/client/auth/signin.go b/internal/api/auth/signin.go index 73a5de398..bae33a43b 100644 --- a/internal/api/client/auth/signin.go +++ b/internal/api/auth/signin.go @@ -26,7 +26,7 @@ import (  	"github.com/gin-contrib/sessions"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -44,15 +44,15 @@ type login struct {  // The form will then POST to the sign in page, which will be handled by SignInPOSTHandler.  // If an idp provider is set, then the user will be redirected to that to do their sign in.  func (m *Module) SignInGETHandler(c *gin.Context) { -	if _, err := api.NegotiateAccept(c, api.HTMLAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.HTMLAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if m.idp == nil { +	if !config.GetOIDCEnabled() {  		instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost())  		if errWithCode != nil { -			api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +			apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  			return  		} @@ -71,7 +71,7 @@ func (m *Module) SignInGETHandler(c *gin.Context) {  	if !ok {  		m.clearSession(s)  		err := fmt.Errorf("key %s was not found in session", sessionInternalState) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -87,7 +87,7 @@ func (m *Module) SignInPOSTHandler(c *gin.Context) {  	form := &login{}  	if err := c.ShouldBind(form); err != nil {  		m.clearSession(s) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  		return  	} @@ -95,17 +95,17 @@ func (m *Module) SignInPOSTHandler(c *gin.Context) {  	if errWithCode != nil {  		// don't clear session here, so the user can just press back and try again  		// if they accidentally gave the wrong password or something -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	s.Set(sessionUserID, userid)  	if err := s.Save(); err != nil {  		err := fmt.Errorf("error saving user id onto session: %s", err) -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err, oauth.HelpfulAdvice), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err, oauth.HelpfulAdvice), m.processor.InstanceGet)  	} -	c.Redirect(http.StatusFound, OauthAuthorizePath) +	c.Redirect(http.StatusFound, "/oauth"+OauthAuthorizePath)  }  // ValidatePassword takes an email address and a password. diff --git a/internal/api/client/auth/token.go b/internal/api/auth/token.go index fbbd08404..17c4d8d8b 100644 --- a/internal/api/client/auth/token.go +++ b/internal/api/auth/token.go @@ -22,7 +22,7 @@ import (  	"net/http"  	"net/url" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -41,8 +41,8 @@ type tokenRequestForm 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.  func (m *Module) TokenPOSTHandler(c *gin.Context) { -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -50,7 +50,7 @@ func (m *Module) TokenPOSTHandler(c *gin.Context) {  	form := &tokenRequestForm{}  	if err := c.ShouldBind(form); err != nil { -		api.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.InvalidRequest(), err.Error())) +		apiutil.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.InvalidRequest(), err.Error()))  		return  	} @@ -99,13 +99,13 @@ func (m *Module) TokenPOSTHandler(c *gin.Context) {  	}  	if len(help) != 0 { -		api.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.InvalidRequest(), help...)) +		apiutil.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.InvalidRequest(), help...))  		return  	}  	token, errWithCode := m.processor.OAuthHandleTokenRequest(c.Request)  	if errWithCode != nil { -		api.OAuthErrorHandler(c, errWithCode) +		apiutil.OAuthErrorHandler(c, errWithCode)  		return  	} diff --git a/internal/api/client/auth/token_test.go b/internal/api/auth/token_test.go index 50bbd6918..50bbd6918 100644 --- a/internal/api/client/auth/token_test.go +++ b/internal/api/auth/token_test.go diff --git a/internal/api/client.go b/internal/api/client.go new file mode 100644 index 000000000..7736a99f4 --- /dev/null +++ b/internal/api/client.go @@ -0,0 +1,129 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 api + +import ( +	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/admin" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/apps" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/blocks" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/bookmarks" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/customemojis" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/favourites" +	filter "github.com/superseriousbusiness/gotosocial/internal/api/client/filters" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequests" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/instance" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/lists" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/media" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/notifications" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/search" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/streaming" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/timelines" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/user" +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/middleware" +	"github.com/superseriousbusiness/gotosocial/internal/processing" +	"github.com/superseriousbusiness/gotosocial/internal/router" +) + +type Client struct { +	processor processing.Processor +	db        db.DB + +	accounts       *accounts.Module       // api/v1/accounts +	admin          *admin.Module          // api/v1/admin +	apps           *apps.Module           // api/v1/apps +	blocks         *blocks.Module         // api/v1/blocks +	bookmarks      *bookmarks.Module      // api/v1/bookmarks +	customEmojis   *customemojis.Module   // api/v1/custom_emojis +	favourites     *favourites.Module     // api/v1/favourites +	filters        *filter.Module         // api/v1/filters +	followRequests *followrequests.Module // api/v1/follow_requests +	instance       *instance.Module       // api/v1/instance +	lists          *lists.Module          // api/v1/lists +	media          *media.Module          // api/v1/media, api/v2/media +	notifications  *notifications.Module  // api/v1/notifications +	search         *search.Module         // api/v1/search, api/v2/search +	statuses       *statuses.Module       // api/v1/statuses +	streaming      *streaming.Module      // api/v1/streaming +	timelines      *timelines.Module      // api/v1/timelines +	user           *user.Module           // api/v1/user +} + +func (c *Client) Route(r router.Router) { +	// create a new group on the top level client 'api' prefix +	apiGroup := r.AttachGroup("api") + +	// attach non-global middlewares appropriate to the client api +	apiGroup.Use( +		middleware.TokenCheck(c.db, c.processor.OAuthValidateBearerToken), +		middleware.RateLimit(), +		middleware.Gzip(), +		middleware.CacheControl("no-store"), // never cache api responses +	) + +	// for each client api module, pass it the Handle function +	// so that the module can attach its routes to this group +	h := apiGroup.Handle +	c.accounts.Route(h) +	c.admin.Route(h) +	c.apps.Route(h) +	c.blocks.Route(h) +	c.bookmarks.Route(h) +	c.customEmojis.Route(h) +	c.favourites.Route(h) +	c.filters.Route(h) +	c.followRequests.Route(h) +	c.instance.Route(h) +	c.lists.Route(h) +	c.media.Route(h) +	c.notifications.Route(h) +	c.search.Route(h) +	c.statuses.Route(h) +	c.streaming.Route(h) +	c.timelines.Route(h) +	c.user.Route(h) +} + +func NewClient(db db.DB, p processing.Processor) *Client { +	return &Client{ +		processor: p, +		db:        db, + +		accounts:       accounts.New(p), +		admin:          admin.New(p), +		apps:           apps.New(p), +		blocks:         blocks.New(p), +		bookmarks:      bookmarks.New(p), +		customEmojis:   customemojis.New(p), +		favourites:     favourites.New(p), +		filters:        filter.New(p), +		followRequests: followrequests.New(p), +		instance:       instance.New(p), +		lists:          lists.New(p), +		media:          media.New(p), +		notifications:  notifications.New(p), +		search:         search.New(p), +		statuses:       statuses.New(p), +		streaming:      streaming.New(p), +		timelines:      timelines.New(p), +		user:           user.New(p), +	} +} diff --git a/internal/api/client/account/account_test.go b/internal/api/client/accounts/account_test.go index 90dbd6249..57d1e6c04 100644 --- a/internal/api/client/account/account_test.go +++ b/internal/api/client/accounts/account_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account_test +package accounts_test  import (  	"bytes" @@ -26,7 +26,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/account" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db" @@ -62,7 +62,7 @@ type AccountStandardTestSuite struct {  	testStatuses     map[string]*gtsmodel.Status  	// module being tested -	accountModule *account.Module +	accountsModule *accounts.Module  }  func (suite *AccountStandardTestSuite) SetupSuite() { @@ -89,7 +89,7 @@ func (suite *AccountStandardTestSuite) SetupTest() {  	suite.sentEmails = make(map[string]string)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.accountModule = account.New(suite.processor).(*account.Module) +	suite.accountsModule = accounts.New(suite.processor)  	testrig.StandardDBSetup(suite.db, nil)  	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/client/account/accountcreate.go b/internal/api/client/accounts/accountcreate.go index e7b6c642d..041ca7fc4 100644 --- a/internal/api/client/account/accountcreate.go +++ b/internal/api/client/accounts/accountcreate.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors" @@ -24,8 +24,8 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -73,23 +73,23 @@ import (  func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, false, false)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.AccountCreateRequest{} +	form := &apimodel.AccountCreateRequest{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if err := validateCreateAccount(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -97,14 +97,14 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {  	signUpIP := net.ParseIP(clientIP)  	if signUpIP == nil {  		err := errors.New("ip address could not be parsed from request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	form.IP = signUpIP  	ti, errWithCode := m.processor.AccountCreate(c.Request.Context(), authed, form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -113,7 +113,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {  // validateCreateAccount checks through all the necessary prerequisites for creating a new account,  // according to the provided account create request. If the account isn't eligible, an error will be returned. -func validateCreateAccount(form *model.AccountCreateRequest) error { +func validateCreateAccount(form *apimodel.AccountCreateRequest) error {  	if form == nil {  		return errors.New("form was nil")  	} diff --git a/internal/api/client/account/accountcreate_test.go b/internal/api/client/accounts/accountcreate_test.go index a4fc165bf..b2b8c715f 100644 --- a/internal/api/client/account/accountcreate_test.go +++ b/internal/api/client/accounts/accountcreate_test.go @@ -16,4 +16,4 @@  //    along with this program.  If not, see <http://www.gnu.org/licenses/>.  // */ -package account_test +package accounts_test diff --git a/internal/api/client/account/accountdelete.go b/internal/api/client/accounts/accountdelete.go index 53bdedd0f..f1b95e95a 100644 --- a/internal/api/client/account/accountdelete.go +++ b/internal/api/client/accounts/accountdelete.go @@ -16,15 +16,15 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	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"  ) @@ -68,26 +68,26 @@ import (  func (m *Module) AccountDeletePOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.AccountDeleteRequest{} +	form := &apimodel.AccountDeleteRequest{}  	if err := c.ShouldBind(&form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if form.Password == "" {  		err = errors.New("no password provided in account delete request") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	form.DeleteOriginID = authed.Account.ID  	if errWithCode := m.processor.AccountDeleteLocal(c.Request.Context(), authed, form); errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/account/accountdelete_test.go b/internal/api/client/accounts/accountdelete_test.go index 78348eabc..31559d59a 100644 --- a/internal/api/client/account/accountdelete_test.go +++ b/internal/api/client/accounts/accountdelete_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account_test +package accounts_test  import (  	"net/http" @@ -24,7 +24,7 @@ import (  	"testing"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/account" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -45,10 +45,10 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandler() {  	}  	bodyBytes := requestBody.Bytes()  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, account.DeleteAccountPath, w.FormDataContentType()) +	ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeleteAccountPath, w.FormDataContentType())  	// call the handler -	suite.accountModule.AccountDeletePOSTHandler(ctx) +	suite.accountsModule.AccountDeletePOSTHandler(ctx)  	// 1. we should have Accepted because our request was valid  	suite.Equal(http.StatusAccepted, recorder.Code) @@ -67,10 +67,10 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerWrongPassword()  	}  	bodyBytes := requestBody.Bytes()  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, account.DeleteAccountPath, w.FormDataContentType()) +	ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeleteAccountPath, w.FormDataContentType())  	// call the handler -	suite.accountModule.AccountDeletePOSTHandler(ctx) +	suite.accountsModule.AccountDeletePOSTHandler(ctx)  	// 1. we should have Forbidden because we supplied the wrong password  	suite.Equal(http.StatusForbidden, recorder.Code) @@ -87,10 +87,10 @@ func (suite *AccountDeleteTestSuite) TestAccountDeletePOSTHandlerNoPassword() {  	}  	bodyBytes := requestBody.Bytes()  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, account.DeleteAccountPath, w.FormDataContentType()) +	ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, accounts.DeleteAccountPath, w.FormDataContentType())  	// call the handler -	suite.accountModule.AccountDeletePOSTHandler(ctx) +	suite.accountsModule.AccountDeletePOSTHandler(ctx)  	// 1. we should have StatusBadRequest because our request was invalid  	suite.Equal(http.StatusBadRequest, recorder.Code) diff --git a/internal/api/client/account/accountget.go b/internal/api/client/accounts/accountget.go index c9aae5b2b..1a6354490 100644 --- a/internal/api/client/account/accountget.go +++ b/internal/api/client/accounts/accountget.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -69,25 +69,25 @@ import (  func (m *Module) AccountGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetAcctID := c.Param(IDKey)  	if targetAcctID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	acctInfo, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/account/account.go b/internal/api/client/accounts/accounts.go index 4205baa2c..54c6c5f22 100644 --- a/internal/api/client/account/account.go +++ b/internal/api/client/accounts/accounts.go @@ -16,17 +16,13 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"net/http" -	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api"  	"github.com/superseriousbusiness/gotosocial/internal/processing" - -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( @@ -49,8 +45,8 @@ const (  	// IDKey is the key to use for retrieving account ID in requests  	IDKey = "id" -	// BasePath is the base API path for this module -	BasePath = "/api/v1/accounts" +	// BasePath is the base API path for this module, excluding the 'api' prefix +	BasePath = "/v1/accounts"  	// BasePathWithID is the base path for this module with the ID key  	BasePathWithID = BasePath + "/:" + IDKey  	// VerifyPath is for verifying account credentials @@ -77,65 +73,47 @@ const (  	DeleteAccountPath = BasePath + "/delete"  ) -// Module implements the ClientAPIModule interface for account-related actions  type Module struct {  	processor processing.Processor  } -// New returns a new account module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) {  	// create account -	r.AttachHandler(http.MethodPost, BasePath, m.AccountCreatePOSTHandler) +	attachHandler(http.MethodPost, BasePath, m.AccountCreatePOSTHandler) + +	// get account +	attachHandler(http.MethodGet, BasePathWithID, m.AccountGETHandler)  	// delete account -	r.AttachHandler(http.MethodPost, DeleteAccountPath, m.AccountDeletePOSTHandler) +	attachHandler(http.MethodPost, DeleteAccountPath, m.AccountDeletePOSTHandler) -	// get account -	r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler) +	// verify account +	attachHandler(http.MethodGet, VerifyPath, m.AccountVerifyGETHandler)  	// modify account -	r.AttachHandler(http.MethodPatch, BasePathWithID, m.muxHandler) +	attachHandler(http.MethodPatch, UpdateCredentialsPath, m.AccountUpdateCredentialsPATCHHandler)  	// get account's statuses -	r.AttachHandler(http.MethodGet, GetStatusesPath, m.AccountStatusesGETHandler) +	attachHandler(http.MethodGet, GetStatusesPath, m.AccountStatusesGETHandler)  	// get following or followers -	r.AttachHandler(http.MethodGet, GetFollowersPath, m.AccountFollowersGETHandler) -	r.AttachHandler(http.MethodGet, GetFollowingPath, m.AccountFollowingGETHandler) +	attachHandler(http.MethodGet, GetFollowersPath, m.AccountFollowersGETHandler) +	attachHandler(http.MethodGet, GetFollowingPath, m.AccountFollowingGETHandler)  	// get relationship with account -	r.AttachHandler(http.MethodGet, GetRelationshipsPath, m.AccountRelationshipsGETHandler) +	attachHandler(http.MethodGet, GetRelationshipsPath, m.AccountRelationshipsGETHandler)  	// follow or unfollow account -	r.AttachHandler(http.MethodPost, FollowPath, m.AccountFollowPOSTHandler) -	r.AttachHandler(http.MethodPost, UnfollowPath, m.AccountUnfollowPOSTHandler) +	attachHandler(http.MethodPost, FollowPath, m.AccountFollowPOSTHandler) +	attachHandler(http.MethodPost, UnfollowPath, m.AccountUnfollowPOSTHandler)  	// block or unblock account -	r.AttachHandler(http.MethodPost, BlockPath, m.AccountBlockPOSTHandler) -	r.AttachHandler(http.MethodPost, UnblockPath, m.AccountUnblockPOSTHandler) - -	return nil -} - -func (m *Module) muxHandler(c *gin.Context) { -	ru := c.Request.RequestURI -	switch c.Request.Method { -	case http.MethodGet: -		if strings.HasPrefix(ru, VerifyPath) { -			m.AccountVerifyGETHandler(c) -		} else { -			m.AccountGETHandler(c) -		} -	case http.MethodPatch: -		if strings.HasPrefix(ru, UpdateCredentialsPath) { -			m.AccountUpdateCredentialsPATCHHandler(c) -		} -	} +	attachHandler(http.MethodPost, BlockPath, m.AccountBlockPOSTHandler) +	attachHandler(http.MethodPost, UnblockPath, m.AccountUnblockPOSTHandler)  } diff --git a/internal/api/client/account/accountupdate.go b/internal/api/client/accounts/accountupdate.go index f89259a96..5dbf0ce46 100644 --- a/internal/api/client/account/accountupdate.go +++ b/internal/api/client/accounts/accountupdate.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors" @@ -25,8 +25,8 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	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"  ) @@ -138,33 +138,33 @@ import (  func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	form, err := parseUpdateAccountForm(c)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	acctSensitive, errWithCode := m.processor.AccountUpdate(c.Request.Context(), authed, form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	c.JSON(http.StatusOK, acctSensitive)  } -func parseUpdateAccountForm(c *gin.Context) (*model.UpdateCredentialsRequest, error) { -	form := &model.UpdateCredentialsRequest{ -		Source: &model.UpdateSource{}, +func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest, error) { +	form := &apimodel.UpdateCredentialsRequest{ +		Source: &apimodel.UpdateSource{},  	}  	if err := c.ShouldBind(&form); err != nil { diff --git a/internal/api/client/account/accountupdate_test.go b/internal/api/client/accounts/accountupdate_test.go index 259bb69e9..45a287ec8 100644 --- a/internal/api/client/account/accountupdate_test.go +++ b/internal/api/client/accounts/accountupdate_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account_test +package accounts_test  import (  	"context" @@ -27,7 +27,7 @@ import (  	"testing"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/account" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"  	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -50,10 +50,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandler()  	}  	bodyBytes := requestBody.Bytes()  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) +	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType())  	// call the handler -	suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) +	suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder.Code) @@ -89,10 +89,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUnl  	}  	bodyBytes1 := requestBody1.Bytes()  	recorder1 := httptest.NewRecorder() -	ctx1 := suite.newContext(recorder1, http.MethodPatch, bodyBytes1, account.UpdateCredentialsPath, w1.FormDataContentType()) +	ctx1 := suite.newContext(recorder1, http.MethodPatch, bodyBytes1, accounts.UpdateCredentialsPath, w1.FormDataContentType())  	// call the handler -	suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx1) +	suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx1)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder1.Code) @@ -125,10 +125,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUnl  	}  	bodyBytes2 := requestBody2.Bytes()  	recorder2 := httptest.NewRecorder() -	ctx2 := suite.newContext(recorder2, http.MethodPatch, bodyBytes2, account.UpdateCredentialsPath, w2.FormDataContentType()) +	ctx2 := suite.newContext(recorder2, http.MethodPatch, bodyBytes2, accounts.UpdateCredentialsPath, w2.FormDataContentType())  	// call the handler -	suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx2) +	suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx2)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder1.Code) @@ -170,10 +170,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerGet  	}  	bodyBytes := requestBody.Bytes()  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) +	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType())  	// call the handler -	suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) +	suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder.Code) @@ -212,10 +212,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerTwo  	}  	bodyBytes := requestBody.Bytes()  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) +	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType())  	// call the handler -	suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) +	suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder.Code) @@ -266,10 +266,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerWit  	}  	bodyBytes := requestBody.Bytes()  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) +	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType())  	// call the handler -	suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) +	suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder.Code) @@ -308,10 +308,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerEmp  	// set up the request  	bodyBytes := []byte{}  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, "") +	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, "")  	// call the handler -	suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) +	suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusBadRequest, recorder.Code) @@ -343,10 +343,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd  	}  	bodyBytes := requestBody.Bytes()  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) +	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType())  	// call the handler -	suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) +	suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder.Code) @@ -385,10 +385,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd  	}  	bodyBytes := requestBody.Bytes()  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) +	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType())  	// call the handler -	suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) +	suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder.Code) @@ -430,10 +430,10 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandlerUpd  	}  	bodyBytes := requestBody.Bytes()  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, account.UpdateCredentialsPath, w.FormDataContentType()) +	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, accounts.UpdateCredentialsPath, w.FormDataContentType())  	// call the handler -	suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx) +	suite.accountsModule.AccountUpdateCredentialsPATCHHandler(ctx)  	suite.Equal(http.StatusBadRequest, recorder.Code) diff --git a/internal/api/client/account/accountverify.go b/internal/api/client/accounts/accountverify.go index 916d0a322..2b39d5ab2 100644 --- a/internal/api/client/account/accountverify.go +++ b/internal/api/client/accounts/accountverify.go @@ -16,13 +16,13 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -59,18 +59,18 @@ import (  func (m *Module) AccountVerifyGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	acctSensitive, errWithCode := m.processor.AccountGet(c.Request.Context(), authed, authed.Account.ID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/account/accountverify_test.go b/internal/api/client/accounts/accountverify_test.go index 886272865..e74c30aba 100644 --- a/internal/api/client/account/accountverify_test.go +++ b/internal/api/client/accounts/accountverify_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account_test +package accounts_test  import (  	"encoding/json" @@ -28,7 +28,7 @@ import (  	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/account" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"  	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  ) @@ -42,10 +42,10 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() {  	// set up the request  	recorder := httptest.NewRecorder() -	ctx := suite.newContext(recorder, http.MethodGet, nil, account.VerifyPath, "") +	ctx := suite.newContext(recorder, http.MethodGet, nil, accounts.VerifyPath, "")  	// call the handler -	suite.accountModule.AccountVerifyGETHandler(ctx) +	suite.accountsModule.AccountVerifyGETHandler(ctx)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder.Code) diff --git a/internal/api/client/account/block.go b/internal/api/client/accounts/block.go index 9840c96ab..9e14ecb6e 100644 --- a/internal/api/client/account/block.go +++ b/internal/api/client/accounts/block.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -69,25 +69,25 @@ import (  func (m *Module) AccountBlockPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetAcctID := c.Param(IDKey)  	if targetAcctID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	relationship, errWithCode := m.processor.AccountBlockCreate(c.Request.Context(), authed, targetAcctID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/account/block_test.go b/internal/api/client/accounts/block_test.go index 9c75330aa..474a53eb8 100644 --- a/internal/api/client/account/block_test.go +++ b/internal/api/client/accounts/block_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account_test +package accounts_test  import (  	"fmt" @@ -29,7 +29,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/account" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -46,16 +46,16 @@ func (suite *BlockTestSuite) TestBlockSelf() {  	ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))  	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])  	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(account.BlockPath, ":id", testAcct.ID, 1)), nil) +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(accounts.BlockPath, ":id", testAcct.ID, 1)), nil)  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   account.IDKey, +			Key:   accounts.IDKey,  			Value: testAcct.ID,  		},  	} -	suite.accountModule.AccountBlockPOSTHandler(ctx) +	suite.accountsModule.AccountBlockPOSTHandler(ctx)  	// 1. status should be Not Acceptable due to attempted self-block  	suite.Equal(http.StatusNotAcceptable, recorder.Code) diff --git a/internal/api/client/account/follow.go b/internal/api/client/accounts/follow.go index cc523a7f8..d2a8af886 100644 --- a/internal/api/client/account/follow.go +++ b/internal/api/client/accounts/follow.go @@ -16,15 +16,15 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	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"  ) @@ -91,32 +91,32 @@ import (  func (m *Module) AccountFollowPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetAcctID := c.Param(IDKey)  	if targetAcctID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.AccountFollowRequest{} +	form := &apimodel.AccountFollowRequest{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	form.ID = targetAcctID  	relationship, errWithCode := m.processor.AccountFollowCreate(c.Request.Context(), authed, form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/account/follow_test.go b/internal/api/client/accounts/follow_test.go index fad67b185..fd15c3734 100644 --- a/internal/api/client/account/follow_test.go +++ b/internal/api/client/accounts/follow_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account_test +package accounts_test  import (  	"fmt" @@ -29,7 +29,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/account" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -46,17 +46,17 @@ func (suite *FollowTestSuite) TestFollowSelf() {  	ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))  	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])  	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(account.FollowPath, ":id", testAcct.ID, 1)), nil) +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(accounts.FollowPath, ":id", testAcct.ID, 1)), nil)  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   account.IDKey, +			Key:   accounts.IDKey,  			Value: testAcct.ID,  		},  	}  	// call the handler -	suite.accountModule.AccountFollowPOSTHandler(ctx) +	suite.accountsModule.AccountFollowPOSTHandler(ctx)  	// 1. status should be Not Acceptable due to self-follow attempt  	suite.Equal(http.StatusNotAcceptable, recorder.Code) diff --git a/internal/api/client/account/followers.go b/internal/api/client/accounts/followers.go index cb2f4bfa6..b464a5ad6 100644 --- a/internal/api/client/account/followers.go +++ b/internal/api/client/accounts/followers.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -72,25 +72,25 @@ import (  func (m *Module) AccountFollowersGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetAcctID := c.Param(IDKey)  	if targetAcctID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	followers, errWithCode := m.processor.AccountFollowersGet(c.Request.Context(), authed, targetAcctID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/account/following.go b/internal/api/client/accounts/following.go index 3d69739c3..4589ad07a 100644 --- a/internal/api/client/account/following.go +++ b/internal/api/client/accounts/following.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -72,25 +72,25 @@ import (  func (m *Module) AccountFollowingGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetAcctID := c.Param(IDKey)  	if targetAcctID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	following, errWithCode := m.processor.AccountFollowingGet(c.Request.Context(), authed, targetAcctID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/account/relationships.go b/internal/api/client/accounts/relationships.go index 56159d48e..60e7b517c 100644 --- a/internal/api/client/account/relationships.go +++ b/internal/api/client/accounts/relationships.go @@ -1,12 +1,12 @@ -package account +package accounts  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	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"  ) @@ -57,12 +57,12 @@ import (  func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -72,18 +72,18 @@ func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) {  		id := c.Query("id")  		if id == "" {  			err = errors.New("no account id(s) specified in query") -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		targetAccountIDs = append(targetAccountIDs, id)  	} -	relationships := []model.Relationship{} +	relationships := []apimodel.Relationship{}  	for _, targetAccountID := range targetAccountIDs {  		r, errWithCode := m.processor.AccountRelationshipGet(c.Request.Context(), authed, targetAccountID)  		if errWithCode != nil { -			api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +			apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  			return  		}  		relationships = append(relationships, *r) diff --git a/internal/api/client/account/statuses.go b/internal/api/client/accounts/statuses.go index 7ecf3ba9f..a04517feb 100644 --- a/internal/api/client/account/statuses.go +++ b/internal/api/client/accounts/statuses.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors" @@ -25,7 +25,7 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -133,19 +133,19 @@ import (  func (m *Module) AccountStatusesGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, false, false, false, false)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetAcctID := c.Param(IDKey)  	if targetAcctID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -155,7 +155,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {  		i, err := strconv.ParseInt(limitString, 10, 32)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		limit = int(i) @@ -167,7 +167,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(excludeRepliesString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", ExcludeRepliesKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		excludeReplies = i @@ -179,7 +179,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(excludeReblogsString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", ExcludeReblogsKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		excludeReblogs = i @@ -203,7 +203,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(pinnedString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", PinnedKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		pinnedOnly = i @@ -215,7 +215,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(mediaOnlyString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", OnlyMediaKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		mediaOnly = i @@ -227,7 +227,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(publicOnlyString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", OnlyPublicKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		publicOnly = i @@ -235,7 +235,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {  	resp, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, excludeReblogs, maxID, minID, pinnedOnly, mediaOnly, publicOnly)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/account/statuses_test.go b/internal/api/client/accounts/statuses_test.go index 1f935896c..92ca9d925 100644 --- a/internal/api/client/account/statuses_test.go +++ b/internal/api/client/accounts/statuses_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account_test +package accounts_test  import (  	"encoding/json" @@ -29,7 +29,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/account" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts"  	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  ) @@ -45,13 +45,13 @@ func (suite *AccountStatusesTestSuite) TestGetStatusesPublicOnly() {  	ctx := suite.newContext(recorder, http.MethodGet, nil, fmt.Sprintf("/api/v1/accounts/%s/statuses?limit=20&only_media=false&only_public=true", targetAccount.ID), "")  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   account.IDKey, +			Key:   accounts.IDKey,  			Value: targetAccount.ID,  		},  	}  	// call the handler -	suite.accountModule.AccountStatusesGETHandler(ctx) +	suite.accountsModule.AccountStatusesGETHandler(ctx)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder.Code) @@ -85,13 +85,13 @@ func (suite *AccountStatusesTestSuite) TestGetStatusesPublicOnlyMediaOnly() {  	ctx := suite.newContext(recorder, http.MethodGet, nil, fmt.Sprintf("/api/v1/accounts/%s/statuses?limit=20&only_media=true&only_public=true", targetAccount.ID), "")  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   account.IDKey, +			Key:   accounts.IDKey,  			Value: targetAccount.ID,  		},  	}  	// call the handler -	suite.accountModule.AccountStatusesGETHandler(ctx) +	suite.accountsModule.AccountStatusesGETHandler(ctx)  	// 1. we should have OK because our request was valid  	suite.Equal(http.StatusOK, recorder.Code) diff --git a/internal/api/client/account/unblock.go b/internal/api/client/accounts/unblock.go index 451b7fd27..e0a0a978e 100644 --- a/internal/api/client/account/unblock.go +++ b/internal/api/client/accounts/unblock.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -70,25 +70,25 @@ import (  func (m *Module) AccountUnblockPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetAcctID := c.Param(IDKey)  	if targetAcctID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	relationship, errWithCode := m.processor.AccountBlockRemove(c.Request.Context(), authed, targetAcctID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/account/unfollow.go b/internal/api/client/accounts/unfollow.go index fafba99fd..95c819903 100644 --- a/internal/api/client/account/unfollow.go +++ b/internal/api/client/accounts/unfollow.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package account +package accounts  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -70,25 +70,25 @@ import (  func (m *Module) AccountUnfollowPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetAcctID := c.Param(IDKey)  	if targetAcctID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	relationship, errWithCode := m.processor.AccountFollowRemove(c.Request.Context(), authed, targetAcctID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/admin/accountaction.go b/internal/api/client/admin/accountaction.go index 2dc84a2d0..d40404b15 100644 --- a/internal/api/client/admin/accountaction.go +++ b/internal/api/client/admin/accountaction.go @@ -24,8 +24,8 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	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"  ) @@ -85,38 +85,38 @@ import (  func (m *Module) AccountActionPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.AdminAccountActionRequest{} +	form := &apimodel.AdminAccountActionRequest{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if form.Type == "" {  		err := errors.New("no type specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetAcctID := c.Param(IDKey)  	if targetAcctID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	form.TargetAccountID = targetAcctID  	if errWithCode := m.processor.AdminAccountAction(c.Request.Context(), authed, form); errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/admin/admin.go b/internal/api/client/admin/admin.go index 569354c96..b4fb4d6d1 100644 --- a/internal/api/client/admin/admin.go +++ b/internal/api/client/admin/admin.go @@ -21,14 +21,13 @@ package admin  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// BasePath is the base API path for this module. -	BasePath = "/api/v1/admin" +	// BasePath is the base API path for this module, excluding the api prefix +	BasePath = "/v1/admin"  	// EmojiPath is used for posting/deleting custom emojis.  	EmojiPath = BasePath + "/custom_emojis"  	// EmojiPathWithID is used for interacting with a single emoji. @@ -68,32 +67,28 @@ const (  	DomainQueryKey = "domain"  ) -// Module implements the ClientAPIModule interface for admin-related actions (reports, emojis, etc)  type Module struct {  	processor processing.Processor  } -// New returns a new admin module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodPost, EmojiPath, m.EmojiCreatePOSTHandler) -	r.AttachHandler(http.MethodGet, EmojiPath, m.EmojisGETHandler) -	r.AttachHandler(http.MethodDelete, EmojiPathWithID, m.EmojiDELETEHandler) -	r.AttachHandler(http.MethodGet, EmojiPathWithID, m.EmojiGETHandler) -	r.AttachHandler(http.MethodPatch, EmojiPathWithID, m.EmojiPATCHHandler) -	r.AttachHandler(http.MethodPost, DomainBlocksPath, m.DomainBlocksPOSTHandler) -	r.AttachHandler(http.MethodGet, DomainBlocksPath, m.DomainBlocksGETHandler) -	r.AttachHandler(http.MethodGet, DomainBlocksPathWithID, m.DomainBlockGETHandler) -	r.AttachHandler(http.MethodDelete, DomainBlocksPathWithID, m.DomainBlockDELETEHandler) -	r.AttachHandler(http.MethodPost, AccountsActionPath, m.AccountActionPOSTHandler) -	r.AttachHandler(http.MethodPost, MediaCleanupPath, m.MediaCleanupPOSTHandler) -	r.AttachHandler(http.MethodPost, MediaRefetchPath, m.MediaRefetchPOSTHandler) -	r.AttachHandler(http.MethodGet, EmojiCategoriesPath, m.EmojiCategoriesGETHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodPost, EmojiPath, m.EmojiCreatePOSTHandler) +	attachHandler(http.MethodGet, EmojiPath, m.EmojisGETHandler) +	attachHandler(http.MethodDelete, EmojiPathWithID, m.EmojiDELETEHandler) +	attachHandler(http.MethodGet, EmojiPathWithID, m.EmojiGETHandler) +	attachHandler(http.MethodPatch, EmojiPathWithID, m.EmojiPATCHHandler) +	attachHandler(http.MethodPost, DomainBlocksPath, m.DomainBlocksPOSTHandler) +	attachHandler(http.MethodGet, DomainBlocksPath, m.DomainBlocksGETHandler) +	attachHandler(http.MethodGet, DomainBlocksPathWithID, m.DomainBlockGETHandler) +	attachHandler(http.MethodDelete, DomainBlocksPathWithID, m.DomainBlockDELETEHandler) +	attachHandler(http.MethodPost, AccountsActionPath, m.AccountActionPOSTHandler) +	attachHandler(http.MethodPost, MediaCleanupPath, m.MediaCleanupPOSTHandler) +	attachHandler(http.MethodPost, MediaRefetchPath, m.MediaRefetchPOSTHandler) +	attachHandler(http.MethodGet, EmojiCategoriesPath, m.EmojiCategoriesGETHandler)  } diff --git a/internal/api/client/admin/admin_test.go b/internal/api/client/admin/admin_test.go index 52c2630d9..ac3bbcb98 100644 --- a/internal/api/client/admin/admin_test.go +++ b/internal/api/client/admin/admin_test.go @@ -93,7 +93,7 @@ func (suite *AdminStandardTestSuite) SetupTest() {  	suite.sentEmails = make(map[string]string)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.adminModule = admin.New(suite.processor).(*admin.Module) +	suite.adminModule = admin.New(suite.processor)  	testrig.StandardDBSetup(suite.db, nil)  	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")  } diff --git a/internal/api/client/admin/domainblockcreate.go b/internal/api/client/admin/domainblockcreate.go index 034ea8682..44410abe3 100644 --- a/internal/api/client/admin/domainblockcreate.go +++ b/internal/api/client/admin/domainblockcreate.go @@ -25,8 +25,8 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	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"  ) @@ -126,18 +126,18 @@ import (  func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -147,21 +147,21 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {  		i, err := strconv.ParseBool(importString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", ImportQueryKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		imp = i  	} -	form := &model.DomainBlockCreateRequest{} +	form := &apimodel.DomainBlockCreateRequest{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if err := validateCreateDomainBlock(form, imp); err != nil {  		err := fmt.Errorf("error validating form: %s", err) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -169,7 +169,7 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {  		// we're importing multiple blocks  		domainBlocks, errWithCode := m.processor.AdminDomainBlocksImport(c.Request.Context(), authed, form)  		if errWithCode != nil { -			api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +			apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  			return  		}  		c.JSON(http.StatusOK, domainBlocks) @@ -179,13 +179,13 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {  	// we're just creating one block  	domainBlock, errWithCode := m.processor.AdminDomainBlockCreate(c.Request.Context(), authed, form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	c.JSON(http.StatusOK, domainBlock)  } -func validateCreateDomainBlock(form *model.DomainBlockCreateRequest, imp bool) error { +func validateCreateDomainBlock(form *apimodel.DomainBlockCreateRequest, imp bool) error {  	if imp {  		if form.Domains.Size == 0 {  			return errors.New("import was specified but list of domains is empty") diff --git a/internal/api/client/admin/domainblockdelete.go b/internal/api/client/admin/domainblockdelete.go index 6f3684418..ddb07e6f6 100644 --- a/internal/api/client/admin/domainblockdelete.go +++ b/internal/api/client/admin/domainblockdelete.go @@ -24,7 +24,7 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -72,31 +72,31 @@ import (  func (m *Module) DomainBlockDELETEHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	domainBlockID := c.Param(IDKey)  	if domainBlockID == "" {  		err := errors.New("no domain block id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	domainBlock, errWithCode := m.processor.AdminDomainBlockDelete(c.Request.Context(), authed, domainBlockID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/admin/domainblockget.go b/internal/api/client/admin/domainblockget.go index 3d27b585e..b9d365caa 100644 --- a/internal/api/client/admin/domainblockget.go +++ b/internal/api/client/admin/domainblockget.go @@ -25,7 +25,7 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -73,25 +73,25 @@ import (  func (m *Module) DomainBlockGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	domainBlockID := c.Param(IDKey)  	if domainBlockID == "" {  		err := errors.New("no domain block id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -101,7 +101,7 @@ func (m *Module) DomainBlockGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(exportString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", ExportQueryKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		export = i @@ -109,7 +109,7 @@ func (m *Module) DomainBlockGETHandler(c *gin.Context) {  	domainBlock, errWithCode := m.processor.AdminDomainBlockGet(c.Request.Context(), authed, domainBlockID, export)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/admin/domainblocksget.go b/internal/api/client/admin/domainblocksget.go index a4ab4ac1c..fea0ca35e 100644 --- a/internal/api/client/admin/domainblocksget.go +++ b/internal/api/client/admin/domainblocksget.go @@ -24,7 +24,7 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -78,18 +78,18 @@ import (  func (m *Module) DomainBlocksGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -99,7 +99,7 @@ func (m *Module) DomainBlocksGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(exportString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", ExportQueryKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		export = i @@ -107,7 +107,7 @@ func (m *Module) DomainBlocksGETHandler(c *gin.Context) {  	domainBlocks, errWithCode := m.processor.AdminDomainBlocksGet(c.Request.Context(), authed, export)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/admin/emojicategoriesget.go b/internal/api/client/admin/emojicategoriesget.go index d8b379674..e69506413 100644 --- a/internal/api/client/admin/emojicategoriesget.go +++ b/internal/api/client/admin/emojicategoriesget.go @@ -23,7 +23,7 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -69,24 +69,24 @@ import (  func (m *Module) EmojiCategoriesGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	categories, errWithCode := m.processor.AdminEmojiCategoriesGet(c.Request.Context())  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/admin/emojicreate.go b/internal/api/client/admin/emojicreate.go index 2a075708f..8368a12b0 100644 --- a/internal/api/client/admin/emojicreate.go +++ b/internal/api/client/admin/emojicreate.go @@ -24,8 +24,8 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -100,42 +100,42 @@ import (  func (m *Module) EmojiCreatePOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.EmojiCreateRequest{} +	form := &apimodel.EmojiCreateRequest{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if err := validateCreateEmoji(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiEmoji, errWithCode := m.processor.AdminEmojiCreate(c.Request.Context(), authed, form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	c.JSON(http.StatusOK, apiEmoji)  } -func validateCreateEmoji(form *model.EmojiCreateRequest) error { +func validateCreateEmoji(form *apimodel.EmojiCreateRequest) error {  	if form.Image == nil || form.Image.Size == 0 {  		return errors.New("no emoji given")  	} diff --git a/internal/api/client/admin/emojidelete.go b/internal/api/client/admin/emojidelete.go index 14f3c70ff..b66116b6d 100644 --- a/internal/api/client/admin/emojidelete.go +++ b/internal/api/client/admin/emojidelete.go @@ -24,7 +24,7 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -78,31 +78,31 @@ import (  func (m *Module) EmojiDELETEHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	emojiID := c.Param(IDKey)  	if emojiID == "" {  		err := errors.New("no emoji id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	emoji, errWithCode := m.processor.AdminEmojiDelete(c.Request.Context(), authed, emojiID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/admin/emojiget.go b/internal/api/client/admin/emojiget.go index 60f7d5948..49d586756 100644 --- a/internal/api/client/admin/emojiget.go +++ b/internal/api/client/admin/emojiget.go @@ -24,7 +24,7 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -68,31 +68,31 @@ import (  func (m *Module) EmojiGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	emojiID := c.Param(IDKey)  	if emojiID == "" {  		err := errors.New("no emoji id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	emoji, errWithCode := m.processor.AdminEmojiGet(c.Request.Context(), authed, emojiID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/admin/emojisget.go b/internal/api/client/admin/emojisget.go index 0b7cfe059..e8b3c0e49 100644 --- a/internal/api/client/admin/emojisget.go +++ b/internal/api/client/admin/emojisget.go @@ -25,7 +25,7 @@ import (  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -125,18 +125,18 @@ import (  func (m *Module) EmojisGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -149,7 +149,7 @@ func (m *Module) EmojisGETHandler(c *gin.Context) {  		i, err := strconv.ParseInt(limitString, 10, 32)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		limit = int(i) @@ -177,7 +177,7 @@ func (m *Module) EmojisGETHandler(c *gin.Context) {  				shortcode = strings.Trim(filter[10:], ":") // remove any errant ":"  			default:  				err := fmt.Errorf("filter %s not recognized; accepted values are 'domain:[domain]', 'disabled', 'enabled', 'shortcode:[shortcode]'", filter) -				api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +				apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  				return  			}  		} @@ -200,7 +200,7 @@ func (m *Module) EmojisGETHandler(c *gin.Context) {  	resp, errWithCode := m.processor.AdminEmojisGet(c.Request.Context(), authed, domain, includeDisabled, includeEnabled, shortcode, maxShortcodeDomain, minShortcodeDomain, limit)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/admin/emojiupdate.go b/internal/api/client/admin/emojiupdate.go index 695c6bcde..8402b30e9 100644 --- a/internal/api/client/admin/emojiupdate.go +++ b/internal/api/client/admin/emojiupdate.go @@ -25,8 +25,8 @@ import (  	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -123,42 +123,42 @@ import (  func (m *Module) EmojiPATCHHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	emojiID := c.Param(IDKey)  	if emojiID == "" {  		err := errors.New("no emoji id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.EmojiUpdateRequest{} +	form := &apimodel.EmojiUpdateRequest{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if err := validateUpdateEmoji(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	emoji, errWithCode := m.processor.AdminEmojiUpdate(c.Request.Context(), emojiID, form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -166,14 +166,14 @@ func (m *Module) EmojiPATCHHandler(c *gin.Context) {  }  // do a first pass on the form here -func validateUpdateEmoji(form *model.EmojiUpdateRequest) error { +func validateUpdateEmoji(form *apimodel.EmojiUpdateRequest) error {  	// check + normalize update type so we don't need  	// to do this trimming + lowercasing again later  	switch strings.TrimSpace(strings.ToLower(string(form.Type))) { -	case string(model.EmojiUpdateDisable): +	case string(apimodel.EmojiUpdateDisable):  		// no params required for this one, so don't bother checking -		form.Type = model.EmojiUpdateDisable -	case string(model.EmojiUpdateCopy): +		form.Type = apimodel.EmojiUpdateDisable +	case string(apimodel.EmojiUpdateCopy):  		// need at least a valid shortcode when doing a copy  		if form.Shortcode == nil {  			return errors.New("emoji action type was 'copy' but no shortcode was provided") @@ -190,8 +190,8 @@ func validateUpdateEmoji(form *model.EmojiUpdateRequest) error {  			}  		} -		form.Type = model.EmojiUpdateCopy -	case string(model.EmojiUpdateModify): +		form.Type = apimodel.EmojiUpdateCopy +	case string(apimodel.EmojiUpdateModify):  		// need either image or category name for modify  		hasImage := form.Image != nil && form.Image.Size != 0  		hasCategoryName := form.CategoryName != nil @@ -212,7 +212,7 @@ func validateUpdateEmoji(form *model.EmojiUpdateRequest) error {  			}  		} -		form.Type = model.EmojiUpdateModify +		form.Type = apimodel.EmojiUpdateModify  	default:  		return errors.New("emoji action type must be one of 'disable', 'copy', 'modify'")  	} diff --git a/internal/api/client/admin/mediacleanup.go b/internal/api/client/admin/mediacleanup.go index 157f35ab0..7f3fc11d5 100644 --- a/internal/api/client/admin/mediacleanup.go +++ b/internal/api/client/admin/mediacleanup.go @@ -23,8 +23,8 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -71,19 +71,19 @@ import (  func (m *Module) MediaCleanupPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.MediaCleanupRequest{} +	form := &apimodel.MediaCleanupRequest{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -98,7 +98,7 @@ func (m *Module) MediaCleanupPOSTHandler(c *gin.Context) {  	}  	if errWithCode := m.processor.AdminMediaPrune(c.Request.Context(), remoteCacheDays); errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/admin/mediarefetch.go b/internal/api/client/admin/mediarefetch.go index 9c8a30c1b..5618843e5 100644 --- a/internal/api/client/admin/mediarefetch.go +++ b/internal/api/client/admin/mediarefetch.go @@ -23,7 +23,7 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -74,18 +74,18 @@ import (  func (m *Module) MediaRefetchPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := fmt.Errorf("user %s not an admin", authed.User.ID) -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if errWithCode := m.processor.AdminMediaRefetch(c.Request.Context(), authed, c.Query(DomainQueryKey)); errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/app/app_test.go b/internal/api/client/app/app_test.go deleted file mode 100644 index 5c1981ba1..000000000 --- a/internal/api/client/app/app_test.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 app_test - -// TODO: write tests diff --git a/internal/api/client/app/appcreate.go b/internal/api/client/apps/appcreate.go index 6060c9480..f381e9954 100644 --- a/internal/api/client/app/appcreate.go +++ b/internal/api/client/apps/appcreate.go @@ -16,15 +16,15 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package app +package apps  import (  	"fmt"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	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"  ) @@ -77,48 +77,48 @@ const (  func (m *Module) AppsPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, false, false, false, false)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.ApplicationCreateRequest{} +	form := &apimodel.ApplicationCreateRequest{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if len([]rune(form.ClientName)) > formFieldLen {  		err := fmt.Errorf("client_name must be less than %d characters", formFieldLen) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if len([]rune(form.RedirectURIs)) > formRedirectLen {  		err := fmt.Errorf("redirect_uris must be less than %d characters", formRedirectLen) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if len([]rune(form.Scopes)) > formFieldLen {  		err := fmt.Errorf("scopes must be less than %d characters", formFieldLen) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if len([]rune(form.Website)) > formFieldLen {  		err := fmt.Errorf("website must be less than %d characters", formFieldLen) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiApp, errWithCode := m.processor.AppCreate(c.Request.Context(), authed, form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/security/useragentblock.go b/internal/api/client/apps/apps.go index b117e8608..264a76f6f 100644 --- a/internal/api/security/useragentblock.go +++ b/internal/api/client/apps/apps.go @@ -16,20 +16,28 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package security +package apps  import ( -	"errors"  	"net/http"  	"github.com/gin-gonic/gin" +	"github.com/superseriousbusiness/gotosocial/internal/processing"  ) -// UserAgentBlock aborts requests with empty user agent strings. -func (m *Module) UserAgentBlock(c *gin.Context) { -	if ua := c.Request.UserAgent(); ua == "" { -		code := http.StatusTeapot -		err := errors.New(http.StatusText(code) + ": no user-agent sent with request") -		c.AbortWithStatusJSON(code, gin.H{"error": err.Error()}) +// BasePath is the base path for this api module, excluding the api prefix +const BasePath = "/v1/apps" + +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.MethodPost, BasePath, m.AppsPOSTHandler) +} diff --git a/internal/api/client/auth/auth.go b/internal/api/client/auth/auth.go deleted file mode 100644 index 8a1d9d483..000000000 --- a/internal/api/client/auth/auth.go +++ /dev/null @@ -1,105 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 ( -	"net/http" - -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/db" -	"github.com/superseriousbusiness/gotosocial/internal/oauth" -	"github.com/superseriousbusiness/gotosocial/internal/oidc" -	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router" -) - -/* #nosec G101 */ -const ( -	// AuthSignInPath is the API path for users to sign in through -	AuthSignInPath = "/auth/sign_in" - -	// CheckYourEmailPath users land here after registering a new account, instructs them to confirm thier email -	CheckYourEmailPath = "/check_your_email" - -	// WaitForApprovalPath users land here after confirming thier email but before an admin approves thier account -	// (if such is required) -	WaitForApprovalPath = "/wait_for_approval" - -	// AccountDisabledPath users land here when thier account is suspended by an admin -	AccountDisabledPath = "/account_disabled" - -	// OauthTokenPath is the API path to use for granting token requests to users with valid credentials -	OauthTokenPath = "/oauth/token" - -	// OauthAuthorizePath is the API path for authorization requests (eg., authorize this app to act on my behalf as a user) -	OauthAuthorizePath = "/oauth/authorize" - -	// OauthFinalizePath is the API path for completing user registration with additional user details -	OauthFinalizePath = "/oauth/finalize" - -	// CallbackPath is the API path for receiving callback tokens from external OIDC providers -	CallbackPath = oidc.CallbackPath - -	callbackStateParam = "state" -	callbackCodeParam  = "code" - -	sessionUserID        = "userid" -	sessionClientID      = "client_id" -	sessionRedirectURI   = "redirect_uri" -	sessionForceLogin    = "force_login" -	sessionResponseType  = "response_type" -	sessionScope         = "scope" -	sessionInternalState = "internal_state" -	sessionClientState   = "client_state" -	sessionClaims        = "claims" -	sessionAppID         = "app_id" -) - -// Module implements the ClientAPIModule interface for -type Module struct { -	db        db.DB -	idp       oidc.IDP -	processor processing.Processor -} - -// New returns a new auth module -func New(db db.DB, idp oidc.IDP, processor processing.Processor) api.ClientModule { -	return &Module{ -		db:        db, -		idp:       idp, -		processor: processor, -	} -} - -// Route satisfies the RESTAPIModule interface -func (m *Module) Route(s router.Router) error { -	s.AttachHandler(http.MethodGet, AuthSignInPath, m.SignInGETHandler) -	s.AttachHandler(http.MethodPost, AuthSignInPath, m.SignInPOSTHandler) - -	s.AttachHandler(http.MethodPost, OauthTokenPath, m.TokenPOSTHandler) - -	s.AttachHandler(http.MethodGet, OauthAuthorizePath, m.AuthorizeGETHandler) -	s.AttachHandler(http.MethodPost, OauthAuthorizePath, m.AuthorizePOSTHandler) - -	s.AttachHandler(http.MethodGet, CallbackPath, m.CallbackGETHandler) -	s.AttachHandler(http.MethodPost, OauthFinalizePath, m.FinalizePOSTHandler) - -	s.AttachHandler(http.MethodGet, oauth.OOBTokenPath, m.OobHandler) -	return nil -} diff --git a/internal/api/client/blocks/blocks.go b/internal/api/client/blocks/blocks.go index 2211a8076..df2ee65bb 100644 --- a/internal/api/client/blocks/blocks.go +++ b/internal/api/client/blocks/blocks.go @@ -21,14 +21,13 @@ package blocks  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// BasePath is the base URI path for serving favourites -	BasePath = "/api/v1/blocks" +	// BasePath is the base URI path for serving blocks, minus the api prefix. +	BasePath = "/v1/blocks"  	// MaxIDKey is the url query for setting a max ID to return  	MaxIDKey = "max_id" @@ -38,20 +37,16 @@ const (  	LimitKey = "limit"  ) -// Module implements the ClientAPIModule interface for everything relating to viewing blocks  type Module struct {  	processor processing.Processor  } -// New returns a new blocks module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, BasePath, m.BlocksGETHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, BasePath, m.BlocksGETHandler)  } diff --git a/internal/api/client/blocks/blocksget.go b/internal/api/client/blocks/blocksget.go index 98f5ce6ea..290ea6617 100644 --- a/internal/api/client/blocks/blocksget.go +++ b/internal/api/client/blocks/blocksget.go @@ -24,7 +24,7 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -96,12 +96,12 @@ import (  func (m *Module) BlocksGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -123,7 +123,7 @@ func (m *Module) BlocksGETHandler(c *gin.Context) {  		i, err := strconv.ParseInt(limitString, 10, 32)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		limit = int(i) @@ -131,7 +131,7 @@ func (m *Module) BlocksGETHandler(c *gin.Context) {  	resp, errWithCode := m.processor.BlocksGet(c.Request.Context(), authed, maxID, sinceID, limit)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/bookmarks/bookmarks.go b/internal/api/client/bookmarks/bookmarks.go index 492b7364c..d0273321c 100644 --- a/internal/api/client/bookmarks/bookmarks.go +++ b/internal/api/client/bookmarks/bookmarks.go @@ -21,9 +21,8 @@ package bookmarks  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( @@ -31,20 +30,16 @@ const (  	BasePath = "/api/v1/bookmarks"  ) -// Module implements the ClientAPIModule interface for everything related to bookmarks  type Module struct {  	processor processing.Processor  } -// New returns a new emoji module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, BasePath, m.BookmarksGETHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, BasePath, m.BookmarksGETHandler)  } diff --git a/internal/api/client/bookmarks/bookmarks_test.go b/internal/api/client/bookmarks/bookmarks_test.go index b4a4bdfb1..3bd12aee1 100644 --- a/internal/api/client/bookmarks/bookmarks_test.go +++ b/internal/api/client/bookmarks/bookmarks_test.go @@ -29,7 +29,7 @@ import (  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/gotosocial/internal/api/client/bookmarks" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"  	"github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/db" @@ -67,7 +67,7 @@ type BookmarkTestSuite struct {  	testFollows      map[string]*gtsmodel.Follow  	// module being tested -	statusModule   *status.Module +	statusModule   *statuses.Module  	bookmarkModule *bookmarks.Module  } @@ -99,8 +99,8 @@ func (suite *BookmarkTestSuite) SetupTest() {  	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.statusModule = status.New(suite.processor).(*status.Module) -	suite.bookmarkModule = bookmarks.New(suite.processor).(*bookmarks.Module) +	suite.statusModule = statuses.New(suite.processor) +	suite.bookmarkModule = bookmarks.New(suite.processor)  	suite.NoError(suite.processor.Start())  } @@ -123,7 +123,7 @@ func (suite *BookmarkTestSuite) TestGetBookmark() {  	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", strings.Replace(status.BookmarkPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.BookmarkPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	suite.bookmarkModule.BookmarksGETHandler(ctx) diff --git a/internal/api/client/bookmarks/bookmarksget.go b/internal/api/client/bookmarks/bookmarksget.go index dafc896ef..8f587f13d 100644 --- a/internal/api/client/bookmarks/bookmarksget.go +++ b/internal/api/client/bookmarks/bookmarksget.go @@ -6,7 +6,7 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -56,12 +56,12 @@ const (  func (m *Module) BookmarksGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -71,7 +71,7 @@ func (m *Module) BookmarksGETHandler(c *gin.Context) {  		i, err := strconv.ParseInt(limitString, 10, 64)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		limit = int(i) @@ -91,12 +91,12 @@ func (m *Module) BookmarksGETHandler(c *gin.Context) {  	resp, errWithCode := m.processor.BookmarksGet(c.Request.Context(), authed, maxID, minID, limit)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/app/app.go b/internal/api/client/customemojis/customemojis.go index 0bbeb6cc9..ab89415d0 100644 --- a/internal/api/client/app/app.go +++ b/internal/api/client/customemojis/customemojis.go @@ -16,33 +16,30 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package app +package customemojis  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  ) -// BasePath is the base path for this api module -const BasePath = "/api/v1/apps" +const ( +	// BasePath is the base path for serving custom emojis, minus the 'api' prefix +	BasePath = "/v1/custom_emojis" +) -// Module implements the ClientAPIModule interface for requests relating to registering/removing applications  type Module struct {  	processor processing.Processor  } -// New returns a new auth module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route satisfies the RESTAPIModule interface -func (m *Module) Route(s router.Router) error { -	s.AttachHandler(http.MethodPost, BasePath, m.AppsPOSTHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, BasePath, m.CustomEmojisGETHandler)  } diff --git a/internal/api/client/customemojis/customemojisget.go b/internal/api/client/customemojis/customemojisget.go new file mode 100644 index 000000000..3428071d0 --- /dev/null +++ b/internal/api/client/customemojis/customemojisget.go @@ -0,0 +1,76 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 customemojis + +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" +) + +// CustomEmojisGETHandler swagger:operation GET /api/v1/custom_emojis customEmojisGet +// +// Get an array of custom emojis available on the instance. +// +//	--- +//	tags: +//	- custom_emojis +// +//	produces: +//	- application/json +// +//	security: +//	- OAuth2 Bearer: +//		- read:custom_emojis +// +//	responses: +//		'200': +//			description: Array of custom emojis. +//			schema: +//				type: array +//				items: +//					"$ref": "#/definitions/emoji" +//		'401': +//			description: unauthorized +//		'406': +//			description: not acceptable +//		'500': +//			description: internal server error +func (m *Module) CustomEmojisGETHandler(c *gin.Context) { +	if _, err := oauth.Authed(c, true, true, true, true); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	emojis, errWithCode := m.processor.CustomEmojisGet(c) +	if errWithCode != nil { +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		return +	} + +	c.JSON(http.StatusOK, emojis) +} diff --git a/internal/api/client/emoji/emojisget.go b/internal/api/client/emoji/emojisget.go deleted file mode 100644 index d41e5e7df..000000000 --- a/internal/api/client/emoji/emojisget.go +++ /dev/null @@ -1,58 +0,0 @@ -package emoji - -import ( -	"net/http" - -	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/gtserror" -	"github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// EmojisGETHandler swagger:operation GET /api/v1/custom_emojis customEmojisGet -// -// Get an array of custom emojis available on the instance. -// -//	--- -//	tags: -//	- custom_emojis -// -//	produces: -//	- application/json -// -//	security: -//	- OAuth2 Bearer: -//		- read:custom_emojis -// -//	responses: -//		'200': -//			description: Array of custom emojis. -//			schema: -//				type: array -//				items: -//					"$ref": "#/definitions/emoji" -//		'401': -//			description: unauthorized -//		'406': -//			description: not acceptable -//		'500': -//			description: internal server error -func (m *Module) EmojisGETHandler(c *gin.Context) { -	if _, err := oauth.Authed(c, true, true, true, true); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) -		return -	} - -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) -		return -	} - -	emojis, errWithCode := m.processor.CustomEmojisGet(c) -	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) -		return -	} - -	c.JSON(http.StatusOK, emojis) -} diff --git a/internal/api/client/favourites/favourites.go b/internal/api/client/favourites/favourites.go index f310d6873..5abc85a27 100644 --- a/internal/api/client/favourites/favourites.go +++ b/internal/api/client/favourites/favourites.go @@ -21,14 +21,13 @@ package favourites  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// BasePath is the base URI path for serving favourites -	BasePath = "/api/v1/favourites" +	// BasePath is the base URI path for serving favourites, minus the 'api' prefix +	BasePath = "/v1/favourites"  	// MaxIDKey is the url query for setting a max status ID to return  	MaxIDKey = "max_id" @@ -42,20 +41,16 @@ const (  	LocalKey = "local"  ) -// Module implements the ClientAPIModule interface for everything relating to viewing favourites  type Module struct {  	processor processing.Processor  } -// New returns a new favourites module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, BasePath, m.FavouritesGETHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, BasePath, m.FavouritesGETHandler)  } diff --git a/internal/api/client/favourites/favourites_test.go b/internal/api/client/favourites/favourites_test.go index c84da6b32..050b72536 100644 --- a/internal/api/client/favourites/favourites_test.go +++ b/internal/api/client/favourites/favourites_test.go @@ -87,7 +87,7 @@ func (suite *FavouritesStandardTestSuite) SetupTest() {  	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.favModule = favourites.New(suite.processor).(*favourites.Module) +	suite.favModule = favourites.New(suite.processor)  	suite.NoError(suite.processor.Start())  } diff --git a/internal/api/client/favourites/favouritesget.go b/internal/api/client/favourites/favouritesget.go index 5ff032b9a..9b6bb715e 100644 --- a/internal/api/client/favourites/favouritesget.go +++ b/internal/api/client/favourites/favouritesget.go @@ -6,7 +6,7 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -78,12 +78,12 @@ import (  func (m *Module) FavouritesGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -105,7 +105,7 @@ func (m *Module) FavouritesGETHandler(c *gin.Context) {  		i, err := strconv.ParseInt(limitString, 10, 32)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		limit = int(i) @@ -113,7 +113,7 @@ func (m *Module) FavouritesGETHandler(c *gin.Context) {  	resp, errWithCode := m.processor.FavedTimelineGet(c.Request.Context(), authed, maxID, minID, limit)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/filter/filter.go b/internal/api/client/filters/filter.go index cf801e0a5..bdfd89ffe 100644 --- a/internal/api/client/filter/filter.go +++ b/internal/api/client/filters/filter.go @@ -21,30 +21,25 @@ package filter  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// BasePath is the base path for serving the filter API -	BasePath = "/api/v1/filters" +	// BasePath is the base path for serving the filters API, minus the 'api' prefix +	BasePath = "/v1/filters"  ) -// Module implements the ClientAPIModule interface for every related to filters  type Module struct {  	processor processing.Processor  } -// New returns a new filter module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, BasePath, m.FiltersGETHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, BasePath, m.FiltersGETHandler)  } diff --git a/internal/api/client/filter/filtersget.go b/internal/api/client/filters/filtersget.go index 8e0a0bb34..71d6cac3e 100644 --- a/internal/api/client/filter/filtersget.go +++ b/internal/api/client/filters/filtersget.go @@ -4,7 +4,7 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -12,12 +12,12 @@ import (  // FiltersGETHandler returns a list of filters set by/for the authed account  func (m *Module) FiltersGETHandler(c *gin.Context) {  	if _, err := oauth.Authed(c, true, true, true, true); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/followrequest/authorize.go b/internal/api/client/followrequests/authorize.go index a5a392f76..d30bb979f 100644 --- a/internal/api/client/followrequest/authorize.go +++ b/internal/api/client/followrequests/authorize.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package followrequest +package followrequests  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -72,25 +72,25 @@ import (  func (m *Module) FollowRequestAuthorizePOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	originAccountID := c.Param(IDKey)  	if originAccountID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	relationship, errWithCode := m.processor.FollowRequestAccept(c.Request.Context(), authed, originAccountID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/followrequest/authorize_test.go b/internal/api/client/followrequests/authorize_test.go index 693380d91..048c462c7 100644 --- a/internal/api/client/followrequest/authorize_test.go +++ b/internal/api/client/followrequests/authorize_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package followrequest_test +package followrequests_test  import (  	"context" @@ -30,7 +30,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequests"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  ) @@ -60,7 +60,7 @@ func (suite *AuthorizeTestSuite) TestAuthorize() {  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   followrequest.IDKey, +			Key:   followrequests.IDKey,  			Value: requestingAccount.ID,  		},  	} @@ -90,7 +90,7 @@ func (suite *AuthorizeTestSuite) TestAuthorizeNoFR() {  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   followrequest.IDKey, +			Key:   followrequests.IDKey,  			Value: requestingAccount.ID,  		},  	} diff --git a/internal/api/client/followrequest/followrequest.go b/internal/api/client/followrequests/followrequest.go index a511d7226..d9d241e63 100644 --- a/internal/api/client/followrequest/followrequest.go +++ b/internal/api/client/followrequests/followrequest.go @@ -16,21 +16,20 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package followrequest +package followrequests  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const (  	// IDKey is for account IDs  	IDKey = "id" -	// BasePath is the base path for serving the follow request API -	BasePath = "/api/v1/follow_requests" +	// BasePath is the base path for serving the follow request API, minus the 'api' prefix +	BasePath = "/v1/follow_requests"  	// BasePathWithID is just the base path with the ID key in it.  	// Use this anywhere you need to know the ID of the account that owns the follow request being queried.  	BasePathWithID = BasePath + "/:" + IDKey @@ -40,22 +39,18 @@ const (  	RejectPath = BasePathWithID + "/reject"  ) -// Module implements the ClientAPIModule interface  type Module struct {  	processor processing.Processor  } -// New returns a new follow request module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, BasePath, m.FollowRequestGETHandler) -	r.AttachHandler(http.MethodPost, AuthorizePath, m.FollowRequestAuthorizePOSTHandler) -	r.AttachHandler(http.MethodPost, RejectPath, m.FollowRequestRejectPOSTHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, BasePath, m.FollowRequestGETHandler) +	attachHandler(http.MethodPost, AuthorizePath, m.FollowRequestAuthorizePOSTHandler) +	attachHandler(http.MethodPost, RejectPath, m.FollowRequestRejectPOSTHandler)  } diff --git a/internal/api/client/followrequest/followrequest_test.go b/internal/api/client/followrequests/followrequest_test.go index ca00ea054..c8036cd24 100644 --- a/internal/api/client/followrequest/followrequest_test.go +++ b/internal/api/client/followrequests/followrequest_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package followrequest_test +package followrequests_test  import (  	"bytes" @@ -25,7 +25,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequests"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db" @@ -59,7 +59,7 @@ type FollowRequestStandardTestSuite struct {  	testStatuses     map[string]*gtsmodel.Status  	// module being tested -	followRequestModule *followrequest.Module +	followRequestModule *followrequests.Module  }  func (suite *FollowRequestStandardTestSuite) SetupSuite() { @@ -85,7 +85,7 @@ func (suite *FollowRequestStandardTestSuite) SetupTest() {  	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.followRequestModule = followrequest.New(suite.processor).(*followrequest.Module) +	suite.followRequestModule = followrequests.New(suite.processor)  	testrig.StandardDBSetup(suite.db, nil)  	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/client/followrequest/get.go b/internal/api/client/followrequests/get.go index 8a2be3686..1153f0f4b 100644 --- a/internal/api/client/followrequest/get.go +++ b/internal/api/client/followrequests/get.go @@ -16,13 +16,13 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package followrequest +package followrequests  import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -74,18 +74,18 @@ import (  func (m *Module) FollowRequestGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	accts, errWithCode := m.processor.FollowRequestsGet(c.Request.Context(), authed)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/followrequest/get_test.go b/internal/api/client/followrequests/get_test.go index c9b72a35b..d4c9da0a1 100644 --- a/internal/api/client/followrequest/get_test.go +++ b/internal/api/client/followrequests/get_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package followrequest_test +package followrequests_test  import (  	"context" diff --git a/internal/api/client/followrequest/reject.go b/internal/api/client/followrequests/reject.go index 717dbf4dd..782f932cd 100644 --- a/internal/api/client/followrequest/reject.go +++ b/internal/api/client/followrequests/reject.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package followrequest +package followrequests  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -70,25 +70,25 @@ import (  func (m *Module) FollowRequestRejectPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	originAccountID := c.Param(IDKey)  	if originAccountID == "" {  		err := errors.New("no account id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	relationship, errWithCode := m.processor.FollowRequestReject(c.Request.Context(), authed, originAccountID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/followrequest/reject_test.go b/internal/api/client/followrequests/reject_test.go index 94c646ddc..cea42829d 100644 --- a/internal/api/client/followrequest/reject_test.go +++ b/internal/api/client/followrequests/reject_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package followrequest_test +package followrequests_test  import (  	"context" @@ -30,7 +30,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequests"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  ) @@ -60,7 +60,7 @@ func (suite *RejectTestSuite) TestReject() {  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   followrequest.IDKey, +			Key:   followrequests.IDKey,  			Value: requestingAccount.ID,  		},  	} diff --git a/internal/api/client/instance/instance.go b/internal/api/client/instance/instance.go index 16ff7c9f9..101e8cea4 100644 --- a/internal/api/client/instance/instance.go +++ b/internal/api/client/instance/instance.go @@ -21,36 +21,31 @@ package instance  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// InstanceInformationPath is for serving instance info requests -	InstanceInformationPath = "api/v1/instance" +	// InstanceInformationPath is for serving instance info requests, minus the 'api' prefix. +	InstanceInformationPath = "/v1/instance"  	// InstancePeersPath is for serving instance peers requests.  	InstancePeersPath = InstanceInformationPath + "/peers"  	// PeersFilterKey is used to provide filters to /api/v1/instance/peers  	PeersFilterKey = "filter"  ) -// Module implements the ClientModule interface  type Module struct {  	processor processing.Processor  } -// New returns a new instance information module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route satisfies the ClientModule interface -func (m *Module) Route(s router.Router) error { -	s.AttachHandler(http.MethodGet, InstanceInformationPath, m.InstanceInformationGETHandler) -	s.AttachHandler(http.MethodPatch, InstanceInformationPath, m.InstanceUpdatePATCHHandler) -	s.AttachHandler(http.MethodGet, InstancePeersPath, m.InstancePeersGETHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, InstanceInformationPath, m.InstanceInformationGETHandler) +	attachHandler(http.MethodPatch, InstanceInformationPath, m.InstanceUpdatePATCHHandler) +	attachHandler(http.MethodGet, InstancePeersPath, m.InstancePeersGETHandler)  } diff --git a/internal/api/client/instance/instance_test.go b/internal/api/client/instance/instance_test.go index 26f29027d..33efbc847 100644 --- a/internal/api/client/instance/instance_test.go +++ b/internal/api/client/instance/instance_test.go @@ -88,7 +88,7 @@ func (suite *InstanceStandardTestSuite) SetupTest() {  	suite.sentEmails = make(map[string]string)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.instanceModule = instance.New(suite.processor).(*instance.Module) +	suite.instanceModule = instance.New(suite.processor)  	testrig.StandardDBSetup(suite.db, nil)  	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")  } diff --git a/internal/api/client/instance/instanceget.go b/internal/api/client/instance/instanceget.go index bcedf398b..dfb8330ff 100644 --- a/internal/api/client/instance/instanceget.go +++ b/internal/api/client/instance/instanceget.go @@ -21,7 +21,7 @@ package instance  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror" @@ -49,14 +49,14 @@ import (  //		'500':  //			description: internal error  func (m *Module) InstanceInformationGETHandler(c *gin.Context) { -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost())  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/instance/instancepatch.go b/internal/api/client/instance/instancepatch.go index d4fa8ca5d..891ce8e38 100644 --- a/internal/api/client/instance/instancepatch.go +++ b/internal/api/client/instance/instancepatch.go @@ -24,8 +24,8 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -130,42 +130,42 @@ import (  func (m *Module) InstanceUpdatePATCHHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if !*authed.User.Admin {  		err := errors.New("user is not an admin so cannot update instance settings") -		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.InstanceSettingsUpdateRequest{} +	form := &apimodel.InstanceSettingsUpdateRequest{}  	if err := c.ShouldBind(&form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if err := validateInstanceUpdate(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	i, errWithCode := m.processor.InstancePatch(c.Request.Context(), form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	c.JSON(http.StatusOK, i)  } -func validateInstanceUpdate(form *model.InstanceSettingsUpdateRequest) error { +func validateInstanceUpdate(form *apimodel.InstanceSettingsUpdateRequest) error {  	if form.Title == nil &&  		form.ContactUsername == nil &&  		form.ContactEmail == nil && diff --git a/internal/api/client/instance/instancepeersget.go b/internal/api/client/instance/instancepeersget.go index f7d05acdc..de6e40e7c 100644 --- a/internal/api/client/instance/instancepeersget.go +++ b/internal/api/client/instance/instancepeersget.go @@ -23,7 +23,7 @@ import (  	"net/http"  	"strings" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -101,12 +101,12 @@ import (  func (m *Module) InstancePeersGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, false, false, false, false)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -124,7 +124,7 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) {  				includeOpen = true  			default:  				err := fmt.Errorf("filter %s not recognized; accepted values are 'open', 'suspended'", trimmed) -				api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +				apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  				return  			}  		} @@ -138,7 +138,7 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) {  	data, errWithCode := m.processor.InstancePeersGet(c.Request.Context(), authed, includeSuspended, includeOpen, flat)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/list/listsgets.go b/internal/api/client/list/listsgets.go deleted file mode 100644 index 246a1216a..000000000 --- a/internal/api/client/list/listsgets.go +++ /dev/null @@ -1,25 +0,0 @@ -package list - -import ( -	"net/http" - -	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/gtserror" -	"github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// ListsGETHandler returns a list of lists created by/for the authed account -func (m *Module) ListsGETHandler(c *gin.Context) { -	if _, err := oauth.Authed(c, true, true, true, true); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) -		return -	} - -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) -		return -	} - -	c.JSON(http.StatusOK, []string{}) -} diff --git a/internal/api/client/list/list.go b/internal/api/client/lists/list.go index c64ada43e..c14917b98 100644 --- a/internal/api/client/list/list.go +++ b/internal/api/client/lists/list.go @@ -16,35 +16,30 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package list +package lists  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// BasePath is the base path for serving the lists API -	BasePath = "/api/v1/lists" +	// BasePath is the base path for serving the lists API, minus the 'api' prefix +	BasePath = "/v1/lists"  ) -// Module implements the ClientAPIModule interface for everything related to lists  type Module struct {  	processor processing.Processor  } -// New returns a new list module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, BasePath, m.ListsGETHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, BasePath, m.ListsGETHandler)  } diff --git a/internal/api/security/robots.go b/internal/api/client/lists/listsgets.go index 5b8ba3c05..a4e5cbefa 100644 --- a/internal/api/security/robots.go +++ b/internal/api/client/lists/listsgets.go @@ -16,42 +16,29 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package security +package lists  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"  ) -const robotsString = `User-agent: * -Crawl-delay: 500 -# api stuff -Disallow: /api/ -# auth/login stuff -Disallow: /auth/ -Disallow: /oauth/ -Disallow: /check_your_email -Disallow: /wait_for_approval -Disallow: /account_disabled -# well known stuff -Disallow: /.well-known/ -# files -Disallow: /fileserver/ -# s2s AP stuff -Disallow: /users/ -Disallow: /emoji/ -# panels -Disallow: /admin -Disallow: /user -Disallow: /settings/ -` - -// RobotsGETHandler returns a decent robots.txt that prevents crawling -// the api, auth pages, settings pages, etc. -// -// More granular robots meta tags are then applied for web pages -// depending on user preferences (see internal/web). -func (m *Module) RobotsGETHandler(c *gin.Context) { -	c.String(http.StatusOK, robotsString) +// ListsGETHandler returns a list of lists created by/for the authed account +func (m *Module) ListsGETHandler(c *gin.Context) { +	if _, err := oauth.Authed(c, true, true, true, true); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		return +	} + +	// todo: implement this; currently it's a no-op +	c.JSON(http.StatusOK, []string{})  } diff --git a/internal/api/client/media/media.go b/internal/api/client/media/media.go index 87cc2f091..889a4f3df 100644 --- a/internal/api/client/media/media.go +++ b/internal/api/client/media/media.go @@ -21,34 +21,31 @@ package media  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	IDKey                  = "id"                                // IDKey is the key for media attachment IDs -	APIVersionKey          = "api_version"                       // APIVersionKey is the key for which version of the API to use (v1 or v2) -	BasePathWithAPIVersion = "/api/:" + APIVersionKey + "/media" // BasePathWithAPIVersion is the base API path for making media requests through v1 or v2 of the api (for mastodon API compatibility) -	BasePathWithIDV1       = "/api/v1/media/:" + IDKey           // BasePathWithID corresponds to a media attachment with the given ID +	IDKey            = "id"                            // IDKey is the key for media attachment IDs +	APIVersionKey    = "api_version"                   // APIVersionKey is the key for which version of the API to use (v1 or v2) +	APIv1            = "v1"                            // APIV1 corresponds to version 1 of the api +	APIv2            = "v2"                            // APIV2 corresponds to version 2 of the api +	BasePath         = "/:" + APIVersionKey + "/media" // BasePath is the base API path for making media requests through v1 or v2 of the api (for mastodon API compatibility) +	AttachmentWithID = BasePath + "/:" + IDKey         // BasePathWithID corresponds to a media attachment with the given ID  ) -// Module implements the ClientAPIModule interface for media  type Module struct {  	processor processing.Processor  } -// New returns a new auth module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route satisfies the RESTAPIModule interface -func (m *Module) Route(s router.Router) error { -	s.AttachHandler(http.MethodPost, BasePathWithAPIVersion, m.MediaCreatePOSTHandler) -	s.AttachHandler(http.MethodGet, BasePathWithIDV1, m.MediaGETHandler) -	s.AttachHandler(http.MethodPut, BasePathWithIDV1, m.MediaPUTHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodPost, BasePath, m.MediaCreatePOSTHandler) +	attachHandler(http.MethodGet, AttachmentWithID, m.MediaGETHandler) +	attachHandler(http.MethodPut, AttachmentWithID, m.MediaPUTHandler)  } diff --git a/internal/api/client/media/mediacreate.go b/internal/api/client/media/mediacreate.go index db8b2ea56..7e29b2bb3 100644 --- a/internal/api/client/media/mediacreate.go +++ b/internal/api/client/media/mediacreate.go @@ -24,8 +24,8 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -94,42 +94,42 @@ import (  //		'500':  //			description: internal server error  func (m *Module) MediaCreatePOSTHandler(c *gin.Context) { -	authed, err := oauth.Authed(c, true, true, true, true) -	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +	apiVersion := c.Param(APIVersionKey) +	if apiVersion != APIv1 && apiVersion != APIv2 { +		err := errors.New("api version must be one of v1 or v2 for this path") +		apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	authed, err := oauth.Authed(c, true, true, true, true) +	if err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	apiVersion := c.Param(APIVersionKey) -	if apiVersion != "v1" && apiVersion != "v2" { -		err := errors.New("api version must be one of v1 or v2") -		api.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.AttachmentRequest{} +	form := &apimodel.AttachmentRequest{}  	if err := c.ShouldBind(&form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if err := validateCreateMedia(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiAttachment, errWithCode := m.processor.MediaCreate(c.Request.Context(), authed, form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} -	if apiVersion == "v2" { +	if apiVersion == APIv2 {  		// the mastodon v2 media API specifies that the URL should be null  		// and that the client should call /api/v1/media/:id to get the URL  		// @@ -141,7 +141,7 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {  	c.JSON(http.StatusOK, apiAttachment)  } -func validateCreateMedia(form *model.AttachmentRequest) error { +func validateCreateMedia(form *apimodel.AttachmentRequest) error {  	// check there actually is a file attached and it's not size 0  	if form.File == nil {  		return errors.New("no attachment given") diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go index 2f6fb12a4..9e787b4b9 100644 --- a/internal/api/client/media/mediacreate_test.go +++ b/internal/api/client/media/mediacreate_test.go @@ -30,10 +30,9 @@ import (  	"net/http/httptest"  	"testing" -	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite"  	mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db" @@ -96,7 +95,7 @@ func (suite *MediaCreateTestSuite) SetupSuite() {  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker)  	// setup module being tested -	suite.mediaModule = mediamodule.New(suite.processor).(*mediamodule.Module) +	suite.mediaModule = mediamodule.New(suite.processor)  }  func (suite *MediaCreateTestSuite) TearDownSuite() { @@ -158,12 +157,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() {  	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json") -	ctx.Params = gin.Params{ -		gin.Param{ -			Key:   mediamodule.APIVersionKey, -			Value: "v1", -		}, -	} +	ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1)  	// do the actual request  	suite.mediaModule.MediaCreatePOSTHandler(ctx) @@ -188,26 +182,26 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() {  	suite.NoError(err)  	fmt.Println(string(b)) -	attachmentReply := &model.Attachment{} +	attachmentReply := &apimodel.Attachment{}  	err = json.Unmarshal(b, attachmentReply)  	suite.NoError(err)  	suite.Equal("this is a test image -- a cool background from somewhere", *attachmentReply.Description)  	suite.Equal("image", attachmentReply.Type) -	suite.EqualValues(model.MediaMeta{ -		Original: model.MediaDimensions{ +	suite.EqualValues(apimodel.MediaMeta{ +		Original: apimodel.MediaDimensions{  			Width:  1920,  			Height: 1080,  			Size:   "1920x1080",  			Aspect: 1.7777778,  		}, -		Small: model.MediaDimensions{ +		Small: apimodel.MediaDimensions{  			Width:  512,  			Height: 288,  			Size:   "512x288",  			Aspect: 1.7777778,  		}, -		Focus: model.MediaFocus{ +		Focus: apimodel.MediaFocus{  			X: -0.5,  			Y: 0.5,  		}, @@ -252,12 +246,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() {  	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v2/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json") -	ctx.Params = gin.Params{ -		gin.Param{ -			Key:   mediamodule.APIVersionKey, -			Value: "v2", -		}, -	} +	ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv2)  	// do the actual request  	suite.mediaModule.MediaCreatePOSTHandler(ctx) @@ -282,26 +271,26 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() {  	suite.NoError(err)  	fmt.Println(string(b)) -	attachmentReply := &model.Attachment{} +	attachmentReply := &apimodel.Attachment{}  	err = json.Unmarshal(b, attachmentReply)  	suite.NoError(err)  	suite.Equal("this is a test image -- a cool background from somewhere", *attachmentReply.Description)  	suite.Equal("image", attachmentReply.Type) -	suite.EqualValues(model.MediaMeta{ -		Original: model.MediaDimensions{ +	suite.EqualValues(apimodel.MediaMeta{ +		Original: apimodel.MediaDimensions{  			Width:  1920,  			Height: 1080,  			Size:   "1920x1080",  			Aspect: 1.7777778,  		}, -		Small: model.MediaDimensions{ +		Small: apimodel.MediaDimensions{  			Width:  512,  			Height: 288,  			Size:   "512x288",  			Aspect: 1.7777778,  		}, -		Focus: model.MediaFocus{ +		Focus: apimodel.MediaFocus{  			X: -0.5,  			Y: 0.5,  		}, @@ -342,12 +331,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() {  	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json") -	ctx.Params = gin.Params{ -		gin.Param{ -			Key:   mediamodule.APIVersionKey, -			Value: "v1", -		}, -	} +	ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1)  	// do the actual request  	suite.mediaModule.MediaCreatePOSTHandler(ctx) @@ -388,12 +372,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() {  	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json") -	ctx.Params = gin.Params{ -		gin.Param{ -			Key:   mediamodule.APIVersionKey, -			Value: "v1", -		}, -	} +	ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1)  	// do the actual request  	suite.mediaModule.MediaCreatePOSTHandler(ctx) diff --git a/internal/api/client/media/mediaget.go b/internal/api/client/media/mediaget.go index fd232c4c7..b22c8e79c 100644 --- a/internal/api/client/media/mediaget.go +++ b/internal/api/client/media/mediaget.go @@ -23,7 +23,7 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -67,27 +67,33 @@ import (  //		'500':  //		   description: internal server error  func (m *Module) MediaGETHandler(c *gin.Context) { +	if apiVersion := c.Param(APIVersionKey); apiVersion != APIv1 { +		err := errors.New("api version must be one v1 for this path") +		apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet) +		return +	} +  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	attachmentID := c.Param(IDKey)  	if attachmentID == "" {  		err := errors.New("no attachment id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	attachment, errWithCode := m.processor.MediaGet(c.Request.Context(), authed, attachmentID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/media/mediaupdate.go b/internal/api/client/media/mediaupdate.go index 438eaca23..9cfd8a5f1 100644 --- a/internal/api/client/media/mediaupdate.go +++ b/internal/api/client/media/mediaupdate.go @@ -24,8 +24,8 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -99,45 +99,51 @@ import (  //		'500':  //			description: internal server error  func (m *Module) MediaPUTHandler(c *gin.Context) { +	if apiVersion := c.Param(APIVersionKey); apiVersion != APIv1 { +		err := errors.New("api version must be one v1 for this path") +		apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet) +		return +	} +  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	attachmentID := c.Param(IDKey)  	if attachmentID == "" {  		err := errors.New("no attachment id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.AttachmentUpdateRequest{} +	form := &apimodel.AttachmentUpdateRequest{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if err := validateUpdateMedia(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	attachment, errWithCode := m.processor.MediaUpdate(c.Request.Context(), authed, attachmentID, form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	c.JSON(http.StatusOK, attachment)  } -func validateUpdateMedia(form *model.AttachmentUpdateRequest) error { +func validateUpdateMedia(form *apimodel.AttachmentUpdateRequest) error {  	minDescriptionChars := config.GetMediaDescriptionMinChars()  	maxDescriptionChars := config.GetMediaDescriptionMaxChars() diff --git a/internal/api/client/media/mediaupdate_test.go b/internal/api/client/media/mediaupdate_test.go index e5abb0a91..bcf9a4dfe 100644 --- a/internal/api/client/media/mediaupdate_test.go +++ b/internal/api/client/media/mediaupdate_test.go @@ -28,10 +28,9 @@ import (  	"net/http/httptest"  	"testing" -	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite"  	mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/db" @@ -94,7 +93,7 @@ func (suite *MediaUpdateTestSuite) SetupSuite() {  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker)  	// setup module being tested -	suite.mediaModule = mediamodule.New(suite.processor).(*mediamodule.Module) +	suite.mediaModule = mediamodule.New(suite.processor)  }  func (suite *MediaUpdateTestSuite) TearDownSuite() { @@ -148,12 +147,8 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() {  	ctx.Request = httptest.NewRequest(http.MethodPut, fmt.Sprintf("http://localhost:8080/api/v1/media/%s", toUpdate.ID), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json") -	ctx.Params = gin.Params{ -		gin.Param{ -			Key:   mediamodule.IDKey, -			Value: toUpdate.ID, -		}, -	} +	ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1) +	ctx.AddParam(mediamodule.IDKey, toUpdate.ID)  	// do the actual request  	suite.mediaModule.MediaPUTHandler(ctx) @@ -167,17 +162,17 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() {  	suite.NoError(err)  	// reply should be an attachment -	attachmentReply := &model.Attachment{} +	attachmentReply := &apimodel.Attachment{}  	err = json.Unmarshal(b, attachmentReply)  	suite.NoError(err)  	// the reply should contain the updated fields  	suite.Equal("new description!", *attachmentReply.Description)  	suite.EqualValues("image", attachmentReply.Type) -	suite.EqualValues(model.MediaMeta{ -		Original: model.MediaDimensions{Width: 800, Height: 450, FrameRate: "", Duration: 0, Bitrate: 0, Size: "800x450", Aspect: 1.7777778}, -		Small:    model.MediaDimensions{Width: 256, Height: 144, FrameRate: "", Duration: 0, Bitrate: 0, Size: "256x144", Aspect: 1.7777778}, -		Focus:    model.MediaFocus{X: -0.1, Y: 0.3}, +	suite.EqualValues(apimodel.MediaMeta{ +		Original: apimodel.MediaDimensions{Width: 800, Height: 450, FrameRate: "", Duration: 0, Bitrate: 0, Size: "800x450", Aspect: 1.7777778}, +		Small:    apimodel.MediaDimensions{Width: 256, Height: 144, FrameRate: "", Duration: 0, Bitrate: 0, Size: "256x144", Aspect: 1.7777778}, +		Focus:    apimodel.MediaFocus{X: -0.1, Y: 0.3},  	}, attachmentReply.Meta)  	suite.Equal(toUpdate.Blurhash, attachmentReply.Blurhash)  	suite.Equal(toUpdate.ID, attachmentReply.ID) @@ -213,12 +208,8 @@ func (suite *MediaUpdateTestSuite) TestUpdateImageShortDescription() {  	ctx.Request = httptest.NewRequest(http.MethodPut, fmt.Sprintf("http://localhost:8080/api/v1/media/%s", toUpdate.ID), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json") -	ctx.Params = gin.Params{ -		gin.Param{ -			Key:   mediamodule.IDKey, -			Value: toUpdate.ID, -		}, -	} +	ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1) +	ctx.AddParam(mediamodule.IDKey, toUpdate.ID)  	// do the actual request  	suite.mediaModule.MediaPUTHandler(ctx) diff --git a/internal/api/client/notification/notification.go b/internal/api/client/notifications/notifications.go index 6ade0b02f..235f0a678 100644 --- a/internal/api/client/notification/notification.go +++ b/internal/api/client/notifications/notifications.go @@ -16,21 +16,20 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package notification +package notifications  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const (  	// IDKey is for notification UUIDs  	IDKey = "id" -	// BasePath is the base path for serving the notification API -	BasePath = "/api/v1/notifications" +	// BasePath is the base path for serving the notification API, minus the 'api' prefix. +	BasePath = "/v1/notifications"  	// BasePathWithID is just the base path with the ID key in it.  	// Use this anywhere you need to know the ID of the notification being queried.  	BasePathWithID    = BasePath + "/:" + IDKey @@ -46,21 +45,17 @@ const (  	SinceIDKey = "since_id"  ) -// Module implements the ClientAPIModule interface for every related to posting/deleting/interacting with notifications  type Module struct {  	processor processing.Processor  } -// New returns a new notification module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, BasePath, m.NotificationsGETHandler) -	r.AttachHandler(http.MethodPost, BasePathWithClear, m.NotificationsClearPOSTHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, BasePath, m.NotificationsGETHandler) +	attachHandler(http.MethodPost, BasePathWithClear, m.NotificationsClearPOSTHandler)  } diff --git a/internal/api/client/notification/notificationsclear.go b/internal/api/client/notifications/notificationsclear.go index b97371638..48c074504 100644 --- a/internal/api/client/notification/notificationsclear.go +++ b/internal/api/client/notifications/notificationsclear.go @@ -16,13 +16,13 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package notification +package notifications  import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -61,18 +61,18 @@ import (  func (m *Module) NotificationsClearPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	errWithCode := m.processor.NotificationsClear(c.Request.Context(), authed)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/notification/notificationsget.go b/internal/api/client/notifications/notificationsget.go index d6b3f5162..09000d02a 100644 --- a/internal/api/client/notification/notificationsget.go +++ b/internal/api/client/notifications/notificationsget.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package notification +package notifications  import (  	"fmt" @@ -24,7 +24,7 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -111,12 +111,12 @@ import (  func (m *Module) NotificationsGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -126,7 +126,7 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) {  		i, err := strconv.ParseInt(limitString, 10, 32)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		limit = int(i) @@ -148,7 +148,7 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) {  	resp, errWithCode := m.processor.NotificationsGet(c.Request.Context(), authed, excludeTypes, limit, maxID, sinceID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/search/search.go b/internal/api/client/search/search.go index 71370a6d5..bebe0bd61 100644 --- a/internal/api/client/search/search.go +++ b/internal/api/client/search/search.go @@ -21,17 +21,16 @@ package search  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// BasePathV1 is the base path for serving v1 of the search API -	BasePathV1 = "/api/v1/search" +	// BasePathV1 is the base path for serving v1 of the search API, minus the 'api' prefix +	BasePathV1 = "/v1/search" -	// BasePathV2 is the base path for serving v2 of the search API -	BasePathV2 = "/api/v2/search" +	// BasePathV2 is the base path for serving v2 of the search API, minus the 'api' prefix +	BasePathV2 = "/v2/search"  	// AccountIDKey -- If provided, statuses returned will be authored only by this account  	AccountIDKey = "account_id" @@ -62,21 +61,17 @@ const (  	TypeStatuses = "statuses"  ) -// Module implements the ClientAPIModule interface for everything related to searching  type Module struct {  	processor processing.Processor  } -// New returns a new search module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, BasePathV1, m.SearchGETHandler) -	r.AttachHandler(http.MethodGet, BasePathV2, m.SearchGETHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, BasePathV1, m.SearchGETHandler) +	attachHandler(http.MethodGet, BasePathV2, m.SearchGETHandler)  } diff --git a/internal/api/client/search/search_test.go b/internal/api/client/search/search_test.go index 11b5b80b2..3cb5e8377 100644 --- a/internal/api/client/search/search_test.go +++ b/internal/api/client/search/search_test.go @@ -84,7 +84,7 @@ func (suite *SearchStandardTestSuite) SetupTest() {  	suite.sentEmails = make(map[string]string)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.searchModule = search.New(suite.processor).(*search.Module) +	suite.searchModule = search.New(suite.processor)  	testrig.StandardDBSetup(suite.db, nil)  	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/client/search/searchget.go b/internal/api/client/search/searchget.go index 7026213ac..15786e6e3 100644 --- a/internal/api/client/search/searchget.go +++ b/internal/api/client/search/searchget.go @@ -25,8 +25,8 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	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"  ) @@ -66,12 +66,12 @@ import (  func (m *Module) SearchGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -82,7 +82,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) {  		excludeUnreviewed, err = strconv.ParseBool(excludeUnreviewedString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", ExcludeUnreviewedKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  	} @@ -90,7 +90,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) {  	query := c.Query(QueryKey)  	if query == "" {  		err := errors.New("query parameter q was empty") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -101,7 +101,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) {  		resolve, err = strconv.ParseBool(resolveString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", ResolveKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  	} @@ -112,7 +112,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) {  		i, err := strconv.ParseInt(limitString, 10, 32)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		limit = int(i) @@ -130,7 +130,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) {  		i, err := strconv.ParseInt(offsetString, 10, 32)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", OffsetKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		offset = int(i) @@ -143,12 +143,12 @@ func (m *Module) SearchGETHandler(c *gin.Context) {  		following, err = strconv.ParseBool(followingString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", FollowingKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  	} -	searchQuery := &model.SearchQuery{ +	searchQuery := &apimodel.SearchQuery{  		AccountID:         c.Query(AccountIDKey),  		MaxID:             c.Query(MaxIDKey),  		MinID:             c.Query(MinIDKey), @@ -163,7 +163,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) {  	results, errWithCode := m.processor.SearchGet(c.Request.Context(), authed, searchQuery)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/status.go b/internal/api/client/statuses/status.go index dc32ae9b5..7f58e8c9d 100644 --- a/internal/api/client/status/status.go +++ b/internal/api/client/statuses/status.go @@ -16,31 +16,24 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"net/http" -	"strings"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/log"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const (  	// IDKey is for status UUIDs  	IDKey = "id" -	// BasePath is the base path for serving the status API -	BasePath = "/api/v1/statuses" +	// BasePath is the base path for serving the statuses API, minus the 'api' prefix +	BasePath = "/v1/statuses"  	// BasePathWithID is just the base path with the ID key in it.  	// Use this anywhere you need to know the ID of the status being queried.  	BasePathWithID = BasePath + "/:" + IDKey -	// ContextPath is used for fetching context of posts -	ContextPath = BasePathWithID + "/context" -  	// FavouritedPath is for seeing who's faved a given status  	FavouritedPath = BasePathWithID + "/favourited_by"  	// FavouritePath is for posting a fave on a status @@ -69,55 +62,39 @@ const (  	PinPath = BasePathWithID + "/pin"  	// UnpinPath is for undoing a pin and returning a status to the ever-swirling drain of time and entropy  	UnpinPath = BasePathWithID + "/unpin" + +	// ContextPath is used for fetching context of posts +	ContextPath = BasePathWithID + "/context"  ) -// Module implements the ClientAPIModule interface for every related to posting/deleting/interacting with statuses  type Module struct {  	processor processing.Processor  } -// New returns a new account module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodPost, BasePath, m.StatusCreatePOSTHandler) -	r.AttachHandler(http.MethodDelete, BasePathWithID, m.StatusDELETEHandler) - -	r.AttachHandler(http.MethodPost, FavouritePath, m.StatusFavePOSTHandler) -	r.AttachHandler(http.MethodPost, UnfavouritePath, m.StatusUnfavePOSTHandler) -	r.AttachHandler(http.MethodGet, FavouritedPath, m.StatusFavedByGETHandler) - -	r.AttachHandler(http.MethodPost, ReblogPath, m.StatusBoostPOSTHandler) -	r.AttachHandler(http.MethodPost, UnreblogPath, m.StatusUnboostPOSTHandler) -	r.AttachHandler(http.MethodGet, RebloggedPath, m.StatusBoostedByGETHandler) - -	r.AttachHandler(http.MethodPost, BookmarkPath, m.StatusBookmarkPOSTHandler) -	r.AttachHandler(http.MethodPost, UnbookmarkPath, m.StatusUnbookmarkPOSTHandler) - -	r.AttachHandler(http.MethodGet, ContextPath, m.StatusContextGETHandler) - -	r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler) -	return nil -} - -// muxHandler is a little workaround to overcome the limitations of Gin -func (m *Module) muxHandler(c *gin.Context) { -	log.Debug("entering mux handler") -	ru := c.Request.RequestURI - -	if c.Request.Method == http.MethodGet { -		switch { -		case strings.HasPrefix(ru, ContextPath): -			// TODO -		case strings.HasPrefix(ru, FavouritedPath): -			m.StatusFavedByGETHandler(c) -		default: -			m.StatusGETHandler(c) -		} -	} +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	// create / get / delete status +	attachHandler(http.MethodPost, BasePath, m.StatusCreatePOSTHandler) +	attachHandler(http.MethodGet, BasePathWithID, m.StatusGETHandler) +	attachHandler(http.MethodDelete, BasePathWithID, m.StatusDELETEHandler) + +	// fave stuff +	attachHandler(http.MethodPost, FavouritePath, m.StatusFavePOSTHandler) +	attachHandler(http.MethodPost, UnfavouritePath, m.StatusUnfavePOSTHandler) +	attachHandler(http.MethodGet, FavouritedPath, m.StatusFavedByGETHandler) + +	// reblog stuff +	attachHandler(http.MethodPost, ReblogPath, m.StatusBoostPOSTHandler) +	attachHandler(http.MethodPost, UnreblogPath, m.StatusUnboostPOSTHandler) +	attachHandler(http.MethodGet, RebloggedPath, m.StatusBoostedByGETHandler) +	attachHandler(http.MethodPost, BookmarkPath, m.StatusBookmarkPOSTHandler) +	attachHandler(http.MethodPost, UnbookmarkPath, m.StatusUnbookmarkPOSTHandler) + +	// context / status thread +	attachHandler(http.MethodGet, ContextPath, m.StatusContextGETHandler)  } diff --git a/internal/api/client/status/status_test.go b/internal/api/client/statuses/status_test.go index 7c3f094f2..0bf824fdb 100644 --- a/internal/api/client/status/status_test.go +++ b/internal/api/client/statuses/status_test.go @@ -16,11 +16,11 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/email" @@ -56,7 +56,7 @@ type StatusStandardTestSuite struct {  	testFollows      map[string]*gtsmodel.Follow  	// module being tested -	statusModule *status.Module +	statusModule *statuses.Module  }  func (suite *StatusStandardTestSuite) SetupSuite() { @@ -87,7 +87,7 @@ func (suite *StatusStandardTestSuite) SetupTest() {  	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.statusModule = status.New(suite.processor).(*status.Module) +	suite.statusModule = statuses.New(suite.processor)  	suite.NoError(suite.processor.Start())  } diff --git a/internal/api/client/status/statusbookmark.go b/internal/api/client/statuses/statusbookmark.go index 983becd72..4efa53528 100644 --- a/internal/api/client/status/statusbookmark.go +++ b/internal/api/client/statuses/statusbookmark.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -72,25 +72,25 @@ import (  func (m *Module) StatusBookmarkPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiStatus, errWithCode := m.processor.StatusBookmark(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statusbookmark_test.go b/internal/api/client/statuses/statusbookmark_test.go index d3da4f297..ba2de78e1 100644 --- a/internal/api/client/status/statusbookmark_test.go +++ b/internal/api/client/statuses/statusbookmark_test.go @@ -13,7 +13,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"encoding/json" @@ -26,7 +26,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"  	"github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig" @@ -49,14 +49,14 @@ func (suite *StatusBookmarkTestSuite) TestPostBookmark() {  	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", strings.Replace(status.BookmarkPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.BookmarkPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} diff --git a/internal/api/client/status/statusboost.go b/internal/api/client/statuses/statusboost.go index d43bedd6c..c8921b1b6 100644 --- a/internal/api/client/status/statusboost.go +++ b/internal/api/client/statuses/statusboost.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -75,25 +75,25 @@ import (  func (m *Module) StatusBoostPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiStatus, errWithCode := m.processor.StatusBoost(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statusboost_test.go b/internal/api/client/statuses/statusboost_test.go index 5b4b1b3cd..13ca2acf2 100644 --- a/internal/api/client/status/statusboost_test.go +++ b/internal/api/client/statuses/statusboost_test.go @@ -13,7 +13,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"context" @@ -27,8 +27,8 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig" @@ -51,14 +51,14 @@ func (suite *StatusBoostTestSuite) TestPostBoost() {  	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", strings.Replace(status.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} @@ -73,12 +73,12 @@ func (suite *StatusBoostTestSuite) TestPostBoost() {  	b, err := ioutil.ReadAll(result.Body)  	suite.NoError(err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	suite.NoError(err)  	suite.False(statusReply.Sensitive) -	suite.Equal(model.VisibilityPublic, statusReply.Visibility) +	suite.Equal(apimodel.VisibilityPublic, statusReply.Visibility)  	suite.Equal(targetStatus.ContentWarning, statusReply.SpoilerText)  	suite.Equal(targetStatus.Content, statusReply.Content) @@ -117,12 +117,12 @@ func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() {  	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)  	ctx.Set(oauth.SessionAuthorizedUser, testUser)  	ctx.Set(oauth.SessionAuthorizedAccount, testAccount) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.ReblogPath, ":id", testStatus.ID, 1)), nil) +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", testStatus.ID, 1)), nil)  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: testStatus.ID,  		},  	} @@ -137,7 +137,7 @@ func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() {  	b, err := ioutil.ReadAll(result.Body)  	suite.NoError(err) -	responseStatus := &model.Status{} +	responseStatus := &apimodel.Status{}  	err = json.Unmarshal(b, responseStatus)  	suite.NoError(err) @@ -182,14 +182,14 @@ func (suite *StatusBoostTestSuite) TestPostUnboostable() {  	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", strings.Replace(status.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} @@ -224,14 +224,14 @@ func (suite *StatusBoostTestSuite) TestPostNotVisible() {  	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)  	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_2"])  	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_2"]) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} diff --git a/internal/api/client/status/statusboostedby.go b/internal/api/client/statuses/statusboostedby.go index 4a175f6e9..dc1567dba 100644 --- a/internal/api/client/status/statusboostedby.go +++ b/internal/api/client/statuses/statusboostedby.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -68,20 +68,20 @@ import (  func (m *Module) StatusBoostedByGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiAccounts, errWithCode := m.processor.StatusBoostedBy(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statusboostedby_test.go b/internal/api/client/statuses/statusboostedby_test.go index 0d7c9f7ab..576dee369 100644 --- a/internal/api/client/status/statusboostedby_test.go +++ b/internal/api/client/statuses/statusboostedby_test.go @@ -13,7 +13,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"encoding/json" @@ -25,7 +25,7 @@ import (  	"testing"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig" @@ -46,7 +46,7 @@ func (suite *StatusBoostedByTestSuite) TestRebloggedByOK() {  	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.MethodGet, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.RebloggedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.RebloggedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	ctx.AddParam("id", targetStatus.ID) @@ -82,7 +82,7 @@ func (suite *StatusBoostedByTestSuite) TestRebloggedByUseBoostWrapperID() {  	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.MethodGet, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.RebloggedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.RebloggedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	ctx.AddParam("id", targetStatus.ID) diff --git a/internal/api/client/status/statuscontext.go b/internal/api/client/statuses/statuscontext.go index 632a151d5..9a6ac9f7f 100644 --- a/internal/api/client/status/statuscontext.go +++ b/internal/api/client/statuses/statuscontext.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -74,25 +74,25 @@ import (  func (m *Module) StatusContextGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	statusContext, errWithCode := m.processor.StatusGetContext(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statuscreate.go b/internal/api/client/statuses/statuscreate.go index c1427411d..d36c93e77 100644 --- a/internal/api/client/status/statuscreate.go +++ b/internal/api/client/statuses/statuscreate.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors" @@ -24,8 +24,8 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -75,18 +75,18 @@ import (  func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.AdvancedStatusCreateForm{} +	form := &apimodel.AdvancedStatusCreateForm{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -100,20 +100,20 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {  	// form.Status += "\n\nsent from " + user + "'s iphone\n"  	if err := validateCreateStatus(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiStatus, errWithCode := m.processor.StatusCreate(c.Request.Context(), authed, form)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	c.JSON(http.StatusOK, apiStatus)  } -func validateCreateStatus(form *model.AdvancedStatusCreateForm) error { +func validateCreateStatus(form *apimodel.AdvancedStatusCreateForm) error {  	hasStatus := form.Status != ""  	hasMedia := len(form.MediaIDs) != 0  	hasPoll := form.Poll != nil diff --git a/internal/api/client/status/statuscreate_test.go b/internal/api/client/statuses/statuscreate_test.go index c143489f3..3648d7520 100644 --- a/internal/api/client/status/statuscreate_test.go +++ b/internal/api/client/statuses/statuscreate_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"context" @@ -29,8 +29,8 @@ import (  	"testing"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -59,13 +59,13 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {  	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", status.BasePath), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Request.Form = url.Values{  		"status":       {"this is a brand new status! #helloworld"},  		"spoiler_text": {"hello hello"},  		"sensitive":    {"true"}, -		"visibility":   {string(model.VisibilityMutualsOnly)}, +		"visibility":   {string(apimodel.VisibilityMutualsOnly)},  		"likeable":     {"false"},  		"replyable":    {"false"},  		"federated":    {"false"}, @@ -82,16 +82,16 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {  	b, err := ioutil.ReadAll(result.Body)  	suite.NoError(err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	suite.NoError(err)  	suite.Equal("hello hello", statusReply.SpoilerText)  	suite.Equal("<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)  	suite.True(statusReply.Sensitive) -	suite.Equal(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 +	suite.Equal(apimodel.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  	suite.Len(statusReply.Tags, 1) -	suite.Equal(model.Tag{ +	suite.Equal(apimodel.Tag{  		Name: "helloworld",  		URL:  "http://localhost:8080/tags/helloworld",  	}, statusReply.Tags[0]) @@ -124,11 +124,11 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() {  	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])  	ctx.Set(oauth.SessionAuthorizedAccount, a) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil)  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Request.Form = url.Values{  		"status":     {statusMarkdown}, -		"visibility": {string(model.VisibilityPublic)}, +		"visibility": {string(apimodel.VisibilityPublic)},  	}  	suite.statusModule.StatusCreatePOSTHandler(ctx) @@ -139,7 +139,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() {  	b, err := ioutil.ReadAll(result.Body)  	suite.NoError(err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	suite.NoError(err) @@ -163,11 +163,11 @@ func (suite *StatusCreateTestSuite) TestMentionUnknownAccount() {  	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", status.BasePath), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Request.Form = url.Values{  		"status":     {"hello @brand_new_person@unknown-instance.com"}, -		"visibility": {string(model.VisibilityPublic)}, +		"visibility": {string(apimodel.VisibilityPublic)},  	}  	suite.statusModule.StatusCreatePOSTHandler(ctx) @@ -178,13 +178,13 @@ func (suite *StatusCreateTestSuite) TestMentionUnknownAccount() {  	b, err := ioutil.ReadAll(result.Body)  	suite.NoError(err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	suite.NoError(err)  	// if the status is properly formatted, that means the account has been put in the db  	suite.Equal(`<p>hello <span class="h-card"><a href="https://unknown-instance.com/@brand_new_person" class="u-url mention" rel="nofollow noreferrer noopener" target="_blank">@<span>brand_new_person</span></a></span></p>`, statusReply.Content) -	suite.Equal(model.VisibilityPublic, statusReply.Visibility) +	suite.Equal(apimodel.VisibilityPublic, statusReply.Visibility)  }  func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() { @@ -198,7 +198,7 @@ func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() {  	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", status.BasePath), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Request.Form = url.Values{  		"status": {statusWithLinksAndTags}, @@ -215,7 +215,7 @@ func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() {  	b, err := ioutil.ReadAll(result.Body)  	suite.NoError(err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	suite.NoError(err) @@ -233,7 +233,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() {  	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", status.BasePath), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Request.Form = url.Values{  		"status": {"here is a rainbow emoji a few times! :rainbow: :rainbow: :rainbow: \n here's an emoji that isn't in the db: :test_emoji: "}, @@ -247,7 +247,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() {  	b, err := ioutil.ReadAll(result.Body)  	suite.NoError(err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	suite.NoError(err) @@ -275,7 +275,7 @@ func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() {  	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", status.BasePath), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Request.Form = url.Values{  		"status":         {"this is a reply to a status that doesn't exist"}, @@ -307,7 +307,7 @@ func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() {  	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", status.BasePath), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Request.Form = url.Values{  		"status":         {fmt.Sprintf("hello @%s this reply should work!", testrig.NewTestAccounts()["local_account_2"].Username)}, @@ -323,14 +323,14 @@ func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() {  	b, err := ioutil.ReadAll(result.Body)  	suite.NoError(err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	suite.NoError(err)  	suite.Equal("", statusReply.SpoilerText)  	suite.Equal(fmt.Sprintf("<p>hello <span class=\"h-card\"><a href=\"http://localhost:8080/@%s\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">@<span>%s</span></a></span> this reply should work!</p>", testrig.NewTestAccounts()["local_account_2"].Username, testrig.NewTestAccounts()["local_account_2"].Username), statusReply.Content)  	suite.False(statusReply.Sensitive) -	suite.Equal(model.VisibilityPublic, statusReply.Visibility) +	suite.Equal(apimodel.VisibilityPublic, statusReply.Visibility)  	suite.Equal(testrig.NewTestStatuses()["local_account_2_status_1"].ID, *statusReply.InReplyToID)  	suite.Equal(testrig.NewTestAccounts()["local_account_2"].ID, *statusReply.InReplyToAccountID)  	suite.Len(statusReply.Mentions, 1) @@ -350,7 +350,7 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() {  	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", status.BasePath), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Request.Form = url.Values{  		"status":      {"here's an image attachment"}, @@ -366,14 +366,14 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() {  	b, err := ioutil.ReadAll(result.Body)  	suite.NoError(err) -	statusResponse := &model.Status{} +	statusResponse := &apimodel.Status{}  	err = json.Unmarshal(b, statusResponse)  	suite.NoError(err)  	suite.Equal("", statusResponse.SpoilerText)  	suite.Equal("<p>here's an image attachment</p>", statusResponse.Content)  	suite.False(statusResponse.Sensitive) -	suite.Equal(model.VisibilityPublic, statusResponse.Visibility) +	suite.Equal(apimodel.VisibilityPublic, statusResponse.Visibility)  	// there should be one media attachment  	suite.Len(statusResponse.MediaAttachments, 1) diff --git a/internal/api/client/status/statusdelete.go b/internal/api/client/statuses/statusdelete.go index b37dd5f14..3db7397db 100644 --- a/internal/api/client/status/statusdelete.go +++ b/internal/api/client/statuses/statusdelete.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -74,25 +74,25 @@ import (  func (m *Module) StatusDELETEHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiStatus, errWithCode := m.processor.StatusDelete(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statusdelete_test.go b/internal/api/client/statuses/statusdelete_test.go index f97a13eec..9a9ceef8f 100644 --- a/internal/api/client/status/statusdelete_test.go +++ b/internal/api/client/statuses/statusdelete_test.go @@ -13,7 +13,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"encoding/json" @@ -27,8 +27,8 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig" @@ -50,14 +50,14 @@ func (suite *StatusDeleteTestSuite) TestPostDelete() {  	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.MethodDelete, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.BasePathWithID, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.BasePathWithID, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} @@ -72,7 +72,7 @@ func (suite *StatusDeleteTestSuite) TestPostDelete() {  	b, err := ioutil.ReadAll(result.Body)  	suite.NoError(err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	suite.NoError(err)  	suite.NotNil(statusReply) diff --git a/internal/api/client/status/statusfave.go b/internal/api/client/statuses/statusfave.go index 3117e7ef2..bd9ded147 100644 --- a/internal/api/client/status/statusfave.go +++ b/internal/api/client/statuses/statusfave.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -71,25 +71,25 @@ import (  func (m *Module) StatusFavePOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiStatus, errWithCode := m.processor.StatusFave(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statusfave_test.go b/internal/api/client/statuses/statusfave_test.go index da5d2a48a..20805d87c 100644 --- a/internal/api/client/status/statusfave_test.go +++ b/internal/api/client/statuses/statusfave_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"encoding/json" @@ -30,8 +30,9 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" + +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -54,14 +55,14 @@ func (suite *StatusFaveTestSuite) TestPostFave() {  	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", strings.Replace(status.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} @@ -76,14 +77,14 @@ func (suite *StatusFaveTestSuite) TestPostFave() {  	b, err := ioutil.ReadAll(result.Body)  	assert.NoError(suite.T(), err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	assert.NoError(suite.T(), err)  	assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText)  	assert.Equal(suite.T(), targetStatus.Content, statusReply.Content)  	assert.True(suite.T(), statusReply.Sensitive) -	assert.Equal(suite.T(), model.VisibilityPublic, statusReply.Visibility) +	assert.Equal(suite.T(), apimodel.VisibilityPublic, statusReply.Visibility)  	assert.True(suite.T(), statusReply.Favourited)  	assert.Equal(suite.T(), 1, statusReply.FavouritesCount)  } @@ -102,14 +103,14 @@ func (suite *StatusFaveTestSuite) TestPostUnfaveable() {  	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", strings.Replace(status.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} diff --git a/internal/api/client/status/statusfavedby.go b/internal/api/client/statuses/statusfavedby.go index 20ef86ded..aa0f1f8d6 100644 --- a/internal/api/client/status/statusfavedby.go +++ b/internal/api/client/statuses/statusfavedby.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -72,25 +72,25 @@ import (  func (m *Module) StatusFavedByGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiAccounts, errWithCode := m.processor.StatusFavedBy(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statusfavedby_test.go b/internal/api/client/statuses/statusfavedby_test.go index e704fa724..fc04c490e 100644 --- a/internal/api/client/status/statusfavedby_test.go +++ b/internal/api/client/statuses/statusfavedby_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"encoding/json" @@ -30,8 +30,8 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -53,14 +53,14 @@ func (suite *StatusFavedByTestSuite) TestGetFavedBy() {  	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)  	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_2"])  	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_2"]) -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.FavouritedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.FavouritedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} @@ -75,7 +75,7 @@ func (suite *StatusFavedByTestSuite) TestGetFavedBy() {  	b, err := ioutil.ReadAll(result.Body)  	assert.NoError(suite.T(), err) -	accts := []model.Account{} +	accts := []apimodel.Account{}  	err = json.Unmarshal(b, &accts)  	assert.NoError(suite.T(), err) diff --git a/internal/api/client/status/statusget.go b/internal/api/client/statuses/statusget.go index a0d0e913c..5e7a59027 100644 --- a/internal/api/client/status/statusget.go +++ b/internal/api/client/statuses/statusget.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -71,25 +71,25 @@ import (  func (m *Module) StatusGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiStatus, errWithCode := m.processor.StatusGet(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statusget_test.go b/internal/api/client/statuses/statusget_test.go index d11c9b587..e8e1fd8f4 100644 --- a/internal/api/client/status/statusget_test.go +++ b/internal/api/client/statuses/statusget_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"testing" diff --git a/internal/api/client/status/statusunbookmark.go b/internal/api/client/statuses/statusunbookmark.go index aa090f8c9..117ef833b 100644 --- a/internal/api/client/status/statusunbookmark.go +++ b/internal/api/client/statuses/statusunbookmark.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -72,25 +72,25 @@ import (  func (m *Module) StatusUnbookmarkPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiStatus, errWithCode := m.processor.StatusUnbookmark(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statusunbookmark_test.go b/internal/api/client/statuses/statusunbookmark_test.go index 09a18ab9b..9c4667ad8 100644 --- a/internal/api/client/status/statusunbookmark_test.go +++ b/internal/api/client/statuses/statusunbookmark_test.go @@ -13,7 +13,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"encoding/json" @@ -26,7 +26,7 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"  	"github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig" @@ -49,12 +49,12 @@ func (suite *StatusUnbookmarkTestSuite) TestPostUnbookmark() {  	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", strings.Replace(status.UnbookmarkPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.UnbookmarkPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} diff --git a/internal/api/client/status/statusunboost.go b/internal/api/client/statuses/statusunboost.go index 45a8e0ece..e91081195 100644 --- a/internal/api/client/status/statusunboost.go +++ b/internal/api/client/statuses/statusunboost.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -72,25 +72,25 @@ import (  func (m *Module) StatusUnboostPOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiStatus, errWithCode := m.processor.StatusUnboost(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statusunfave.go b/internal/api/client/statuses/statusunfave.go index 19d3da3bd..57ae88e1e 100644 --- a/internal/api/client/status/statusunfave.go +++ b/internal/api/client/statuses/statusunfave.go @@ -16,14 +16,14 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status +package statuses  import (  	"errors"  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -71,25 +71,25 @@ import (  func (m *Module) StatusUnfavePOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	}  	targetStatusID := c.Param(IDKey)  	if targetStatusID == "" {  		err := errors.New("no status id specified") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	apiStatus, errWithCode := m.processor.StatusUnfave(c.Request.Context(), authed, targetStatusID)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/status/statusunfave_test.go b/internal/api/client/statuses/statusunfave_test.go index b8448d657..2ca3450a4 100644 --- a/internal/api/client/status/statusunfave_test.go +++ b/internal/api/client/statuses/statusunfave_test.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package status_test +package statuses_test  import (  	"encoding/json" @@ -30,8 +30,8 @@ import (  	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/status" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  	"github.com/superseriousbusiness/gotosocial/testrig"  ) @@ -55,14 +55,14 @@ func (suite *StatusUnfaveTestSuite) TestPostUnfave() {  	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", strings.Replace(status.UnfavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.UnfavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} @@ -77,14 +77,14 @@ func (suite *StatusUnfaveTestSuite) TestPostUnfave() {  	b, err := ioutil.ReadAll(result.Body)  	assert.NoError(suite.T(), err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	assert.NoError(suite.T(), err)  	assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText)  	assert.Equal(suite.T(), targetStatus.Content, statusReply.Content)  	assert.False(suite.T(), statusReply.Sensitive) -	assert.Equal(suite.T(), model.VisibilityPublic, statusReply.Visibility) +	assert.Equal(suite.T(), apimodel.VisibilityPublic, statusReply.Visibility)  	assert.False(suite.T(), statusReply.Favourited)  	assert.Equal(suite.T(), 0, statusReply.FavouritesCount)  } @@ -104,14 +104,14 @@ func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() {  	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", strings.Replace(status.UnfavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.UnfavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting  	ctx.Request.Header.Set("accept", "application/json")  	// normally the router would populate these params from the path values,  	// but because we're calling the function directly, we need to set them manually.  	ctx.Params = gin.Params{  		gin.Param{ -			Key:   status.IDKey, +			Key:   statuses.IDKey,  			Value: targetStatus.ID,  		},  	} @@ -126,14 +126,14 @@ func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() {  	b, err := ioutil.ReadAll(result.Body)  	assert.NoError(suite.T(), err) -	statusReply := &model.Status{} +	statusReply := &apimodel.Status{}  	err = json.Unmarshal(b, statusReply)  	assert.NoError(suite.T(), err)  	assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText)  	assert.Equal(suite.T(), targetStatus.Content, statusReply.Content)  	assert.True(suite.T(), statusReply.Sensitive) -	assert.Equal(suite.T(), model.VisibilityPublic, statusReply.Visibility) +	assert.Equal(suite.T(), apimodel.VisibilityPublic, statusReply.Visibility)  	assert.False(suite.T(), statusReply.Favourited)  	assert.Equal(suite.T(), 0, statusReply.FavouritesCount)  } diff --git a/internal/api/client/streaming/stream.go b/internal/api/client/streaming/stream.go index a9cb62732..de98719c2 100644 --- a/internal/api/client/streaming/stream.go +++ b/internal/api/client/streaming/stream.go @@ -1,3 +1,21 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 streaming  import ( @@ -6,7 +24,7 @@ import (  	"time"  	"codeberg.org/gruf/go-kv" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/log" @@ -14,12 +32,15 @@ import (  	"github.com/gorilla/websocket"  ) -var wsUpgrader = websocket.Upgrader{ -	ReadBufferSize:  1024, -	WriteBufferSize: 1024, -	// we expect cors requests (via eg., pinafore.social) so be lenient -	CheckOrigin: func(r *http.Request) bool { return true }, -} +var ( +	wsUpgrader = websocket.Upgrader{ +		ReadBufferSize:  1024, +		WriteBufferSize: 1024, +		// we expect cors requests (via eg., pinafore.social) so be lenient +		CheckOrigin: func(r *http.Request) bool { return true }, +	} +	errNoToken = fmt.Errorf("no access token provided under query key %s or under header %s", AccessTokenQueryKey, AccessTokenHeader) +)  // StreamGETHandler swagger:operation GET /api/v1/streaming streamGet  // @@ -125,29 +146,33 @@ func (m *Module) StreamGETHandler(c *gin.Context) {  	streamType := c.Query(StreamQueryKey)  	if streamType == "" {  		err := fmt.Errorf("no stream type provided under query key %s", StreamQueryKey) -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	accessToken := c.Query(AccessTokenQueryKey) -	if accessToken == "" { -		accessToken = c.GetHeader(AccessTokenHeader) -	} -	if accessToken == "" { -		err := fmt.Errorf("no access token provided under query key %s or under header %s", AccessTokenQueryKey, AccessTokenHeader) -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +	var accessToken string +	if t := c.Query(AccessTokenQueryKey); t != "" { +		// try query param first +		accessToken = t +	} else if t := c.GetHeader(AccessTokenHeader); t != "" { +		// fall back to Sec-Websocket-Protocol +		accessToken = t +	} else { +		// no token +		err := errNoToken +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	}  	account, errWithCode := m.processor.AuthorizeStreamingRequest(c.Request.Context(), accessToken)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	}  	stream, errWithCode := m.processor.OpenStreamForAccount(c.Request.Context(), account, streamType)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -175,6 +200,7 @@ func (m *Module) StreamGETHandler(c *gin.Context) {  	}()  	streamTicker := time.NewTicker(m.tickDuration) +	defer streamTicker.Stop()  	// We want to stay in the loop as long as possible while the client is connected.  	// The only thing that should break the loop is if the client leaves or the connection becomes unhealthy. diff --git a/internal/api/client/streaming/streaming.go b/internal/api/client/streaming/streaming.go index b15dfbdbd..f9d9fdf36 100644 --- a/internal/api/client/streaming/streaming.go +++ b/internal/api/client/streaming/streaming.go @@ -22,14 +22,13 @@ import (  	"net/http"  	"time" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// BasePath is the path for the streaming api -	BasePath = "/api/v1/streaming" +	// BasePath is the path for the streaming api, minus the 'api' prefix +	BasePath = "/v1/streaming"  	// StreamQueryKey is the query key for the type of stream being requested  	StreamQueryKey = "stream" @@ -41,29 +40,25 @@ const (  	AccessTokenHeader = "Sec-Websocket-Protocol"  ) -// Module implements the api.ClientModule interface for everything related to streaming  type Module struct {  	processor    processing.Processor  	tickDuration time.Duration  } -// New returns a new streaming module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor:    processor,  		tickDuration: 30 * time.Second,  	}  } -func NewWithTickDuration(processor processing.Processor, tickDuration time.Duration) api.ClientModule { +func NewWithTickDuration(processor processing.Processor, tickDuration time.Duration) *Module {  	return &Module{  		processor:    processor,  		tickDuration: tickDuration,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, BasePath, m.StreamGETHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, BasePath, m.StreamGETHandler)  } diff --git a/internal/api/client/streaming/streaming_test.go b/internal/api/client/streaming/streaming_test.go index 49c983fff..2f2d850c1 100644 --- a/internal/api/client/streaming/streaming_test.go +++ b/internal/api/client/streaming/streaming_test.go @@ -99,7 +99,7 @@ func (suite *StreamingTestSuite) SetupTest() {  	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.streamingModule = streaming.NewWithTickDuration(suite.processor, 1).(*streaming.Module) +	suite.streamingModule = streaming.NewWithTickDuration(suite.processor, 1)  	suite.NoError(suite.processor.Start())  } diff --git a/internal/api/client/timeline/home.go b/internal/api/client/timelines/home.go index e6135dd63..33af8fe5e 100644 --- a/internal/api/client/timeline/home.go +++ b/internal/api/client/timelines/home.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package timeline +package timelines  import (  	"fmt" @@ -24,7 +24,7 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) @@ -112,12 +112,12 @@ import (  func (m *Module) HomeTimelineGETHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -145,7 +145,7 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) {  		i, err := strconv.ParseInt(limitString, 10, 32)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		limit = int(i) @@ -157,7 +157,7 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(localString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LocalKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		local = i @@ -165,7 +165,7 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) {  	resp, errWithCode := m.processor.HomeTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/timeline/public.go b/internal/api/client/timelines/public.go index fda23438b..efe351a37 100644 --- a/internal/api/client/timeline/public.go +++ b/internal/api/client/timelines/public.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package timeline +package timelines  import (  	"fmt" @@ -24,7 +24,7 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -123,12 +123,12 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {  	}  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} @@ -156,7 +156,7 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {  		i, err := strconv.ParseInt(limitString, 10, 32)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LimitKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		limit = int(i) @@ -168,7 +168,7 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {  		i, err := strconv.ParseBool(localString)  		if err != nil {  			err := fmt.Errorf("error parsing %s: %s", LocalKey, err) -			api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +			apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  			return  		}  		local = i @@ -176,7 +176,7 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {  	resp, errWithCode := m.processor.PublicTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/timeline/timeline.go b/internal/api/client/timelines/timeline.go index 3604a1fc2..609e1855e 100644 --- a/internal/api/client/timeline/timeline.go +++ b/internal/api/client/timelines/timeline.go @@ -16,19 +16,18 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package timeline +package timelines  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// BasePath is the base URI path for serving timelines -	BasePath = "/api/v1/timelines" +	// BasePath is the base URI path for serving timelines, minus the 'api' prefix. +	BasePath = "/v1/timelines"  	// HomeTimeline is the path for the home timeline  	HomeTimeline = BasePath + "/home"  	// PublicTimeline is the path for the public (and public local) timeline @@ -45,21 +44,17 @@ const (  	LocalKey = "local"  ) -// Module implements the ClientAPIModule interface for everything relating to viewing timelines  type Module struct {  	processor processing.Processor  } -// New returns a new timeline module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodGet, HomeTimeline, m.HomeTimelineGETHandler) -	r.AttachHandler(http.MethodGet, PublicTimeline, m.PublicTimelineGETHandler) -	return nil +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)  } diff --git a/internal/api/client/user/passwordchange.go b/internal/api/client/user/passwordchange.go index a900af897..c766d915c 100644 --- a/internal/api/client/user/passwordchange.go +++ b/internal/api/client/user/passwordchange.go @@ -23,8 +23,8 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	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"  ) @@ -68,35 +68,35 @@ import (  func (m *Module) PasswordChangePOSTHandler(c *gin.Context) {  	authed, err := oauth.Authed(c, true, true, true, true)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)  		return  	} -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	form := &model.PasswordChangeRequest{} +	form := &apimodel.PasswordChangeRequest{}  	if err := c.ShouldBind(form); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if form.OldPassword == "" {  		err := errors.New("password change request missing field old_password") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if form.NewPassword == "" {  		err := errors.New("password change request missing field new_password") -		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	if errWithCode := m.processor.UserChangePassword(c.Request.Context(), authed, form); errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/user/user.go b/internal/api/client/user/user.go index 86a0096e0..5e6002b40 100644 --- a/internal/api/client/user/user.go +++ b/internal/api/client/user/user.go @@ -21,32 +21,27 @@ package user  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// BasePath is the base URI path for this module -	BasePath = "/api/v1/user" +	// BasePath is the base URI path for this module, minus the 'api' prefix +	BasePath = "/v1/user"  	// PasswordChangePath is the path for POSTing a password change request.  	PasswordChangePath = BasePath + "/password_change"  ) -// Module implements the ClientAPIModule interface  type Module struct {  	processor processing.Processor  } -// New returns a new user module -func New(processor processing.Processor) api.ClientModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route attaches all routes from this module to the given router -func (m *Module) Route(r router.Router) error { -	r.AttachHandler(http.MethodPost, PasswordChangePath, m.PasswordChangePOSTHandler) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodPost, PasswordChangePath, m.PasswordChangePOSTHandler)  } diff --git a/internal/api/client/user/user_test.go b/internal/api/client/user/user_test.go index cc4fafca9..055b1f7a4 100644 --- a/internal/api/client/user/user_test.go +++ b/internal/api/client/user/user_test.go @@ -73,7 +73,7 @@ func (suite *UserStandardTestSuite) SetupTest() {  	suite.sentEmails = make(map[string]string)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", suite.sentEmails)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.userModule = user.New(suite.processor).(*user.Module) +	suite.userModule = user.New(suite.processor)  	testrig.StandardDBSetup(suite.db, suite.testAccounts)  	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/fileserver.go b/internal/api/fileserver.go new file mode 100644 index 000000000..8784a8663 --- /dev/null +++ b/internal/api/fileserver.go @@ -0,0 +1,55 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 api + +import ( +	"github.com/superseriousbusiness/gotosocial/internal/api/fileserver" +	"github.com/superseriousbusiness/gotosocial/internal/middleware" +	"github.com/superseriousbusiness/gotosocial/internal/processing" +	"github.com/superseriousbusiness/gotosocial/internal/router" +) + +type Fileserver struct { +	fileserver *fileserver.Module +} + +func (f *Fileserver) Route(r router.Router) { +	fileserverGroup := r.AttachGroup("fileserver") + +	// attach middlewares appropriate for this group +	fileserverGroup.Use( +		middleware.RateLimit(), +		// Since we'll never host different files at the same +		// URL (bc the ULIDs are generated per piece of media), +		// it's sensible and safe to use a long cache here, so +		// that clients don't keep fetching files over + over again. +		// +		// Nevertheless, we should use 'private' to indicate +		// that there might be media in there which are gated by ACLs. +		middleware.CacheControl("private", "max-age=604800"), +	) + +	f.fileserver.Route(fileserverGroup.Handle) +} + +func NewFileserver(p processing.Processor) *Fileserver { +	return &Fileserver{ +		fileserver: fileserver.New(p), +	} +} diff --git a/internal/api/client/fileserver/fileserver.go b/internal/api/fileserver/fileserver.go index dcb54f986..bbfd7d200 100644 --- a/internal/api/client/fileserver/fileserver.go +++ b/internal/api/fileserver/fileserver.go @@ -19,18 +19,13 @@  package fileserver  import ( -	"fmt"  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router" -	"github.com/superseriousbusiness/gotosocial/internal/uris"  )  const ( -	// FileServeBasePath forms the first part of the fileserver path. -	FileServeBasePath = "/" + uris.FileserverPath  	// AccountIDKey is the url key for account id (an account ulid)  	AccountIDKey = "account_id"  	// MediaTypeKey is the url key for media type (usually something like attachment or header etc) @@ -39,26 +34,21 @@ const (  	MediaSizeKey = "media_size"  	// FileNameKey is the actual filename being sought. Will usually be a UUID then something like .jpeg  	FileNameKey = "file_name" +	// FileServePath is the fileserve path minus the 'fileserver' prefix. +	FileServePath = "/:" + AccountIDKey + "/:" + MediaTypeKey + "/:" + MediaSizeKey + "/:" + FileNameKey  ) -// FileServer implements the RESTAPIModule interface. -// The goal here is to serve requested media files if the gotosocial server is configured to use local storage. -type FileServer struct { +type Module struct {  	processor processing.Processor  } -// New returns a new fileServer module -func New(processor processing.Processor) api.ClientModule { -	return &FileServer{ +func New(processor processing.Processor) *Module { +	return &Module{  		processor: processor,  	}  } -// Route satisfies the RESTAPIModule interface -func (m *FileServer) Route(s router.Router) error { -	// something like "/fileserver/:account_id/:media_type/:media_size/:file_name" -	fileServePath := fmt.Sprintf("%s/:%s/:%s/:%s/:%s", FileServeBasePath, AccountIDKey, MediaTypeKey, MediaSizeKey, FileNameKey) -	s.AttachHandler(http.MethodGet, fileServePath, m.ServeFile) -	s.AttachHandler(http.MethodHead, fileServePath, m.ServeFile) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, FileServePath, m.ServeFile) +	attachHandler(http.MethodHead, FileServePath, m.ServeFile)  } diff --git a/internal/api/client/fileserver/fileserver_test.go b/internal/api/fileserver/fileserver_test.go index f1fab5672..f6de72a27 100644 --- a/internal/api/client/fileserver/fileserver_test.go +++ b/internal/api/fileserver/fileserver_test.go @@ -22,7 +22,7 @@ import (  	"context"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver" +	"github.com/superseriousbusiness/gotosocial/internal/api/fileserver"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/email" @@ -59,7 +59,7 @@ type FileserverTestSuite struct {  	testAttachments  map[string]*gtsmodel.MediaAttachment  	// item being tested -	fileServer *fileserver.FileServer +	fileServer *fileserver.Module  }  /* @@ -75,20 +75,20 @@ func (suite *FileserverTestSuite) SetupSuite() {  	suite.db = testrig.NewTestDB()  	suite.storage = testrig.NewInMemoryStorage() -	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker) -	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil) +	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker) +	suite.emailSender = testrig.NewEmailSender("../../../web/template/", nil)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, testrig.NewTestMediaManager(suite.db, suite.storage), clientWorker, fedWorker)  	suite.tc = testrig.NewTestTypeConverter(suite.db)  	suite.mediaManager = testrig.NewTestMediaManager(suite.db, suite.storage)  	suite.oauthServer = testrig.NewTestOauthServer(suite.db) -	suite.fileServer = fileserver.New(suite.processor).(*fileserver.FileServer) +	suite.fileServer = fileserver.New(suite.processor)  }  func (suite *FileserverTestSuite) SetupTest() {  	testrig.StandardDBSetup(suite.db, nil) -	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") +	testrig.StandardStorageSetup(suite.storage, "../../../testrig/media")  	suite.testTokens = testrig.NewTestTokens()  	suite.testClients = testrig.NewTestClients()  	suite.testApplications = testrig.NewTestApplications() diff --git a/internal/api/client/fileserver/servefile.go b/internal/api/fileserver/servefile.go index d2328a5fc..08ab3110b 100644 --- a/internal/api/client/fileserver/servefile.go +++ b/internal/api/fileserver/servefile.go @@ -26,8 +26,8 @@ import (  	"strconv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/api/model" +	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/log"  	"github.com/superseriousbusiness/gotosocial/internal/oauth" @@ -37,10 +37,10 @@ import (  //  // Note: to mitigate scraping attempts, no information should be given out on a bad request except "404 page not found".  // Don't give away account ids or media ids or anything like that; callers shouldn't be able to infer anything. -func (m *FileServer) ServeFile(c *gin.Context) { +func (m *Module) ServeFile(c *gin.Context) {  	authed, err := oauth.Authed(c, false, false, false, false)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)  		return  	} @@ -50,39 +50,39 @@ func (m *FileServer) ServeFile(c *gin.Context) {  	accountID := c.Param(AccountIDKey)  	if accountID == "" {  		err := fmt.Errorf("missing %s from request", AccountIDKey) -		api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)  		return  	}  	mediaType := c.Param(MediaTypeKey)  	if mediaType == "" {  		err := fmt.Errorf("missing %s from request", MediaTypeKey) -		api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)  		return  	}  	mediaSize := c.Param(MediaSizeKey)  	if mediaSize == "" {  		err := fmt.Errorf("missing %s from request", MediaSizeKey) -		api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)  		return  	}  	fileName := c.Param(FileNameKey)  	if fileName == "" {  		err := fmt.Errorf("missing %s from request", FileNameKey) -		api.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err), m.processor.InstanceGet)  		return  	} -	content, errWithCode := m.processor.FileGet(c.Request.Context(), authed, &model.GetContentRequestForm{ +	content, errWithCode := m.processor.FileGet(c.Request.Context(), authed, &apimodel.GetContentRequestForm{  		AccountID: accountID,  		MediaType: mediaType,  		MediaSize: mediaSize,  		FileName:  fileName,  	})  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} @@ -103,18 +103,13 @@ func (m *FileServer) ServeFile(c *gin.Context) {  	// TODO: if the requester only accepts text/html we should try to serve them *something*.  	// This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will  	// attempt to look up the content to provide a preview of the link, and they ask for text/html. -	format, err := api.NegotiateAccept(c, api.MIME(content.ContentType)) +	format, err := apiutil.NegotiateAccept(c, apiutil.MIME(content.ContentType))  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	// since we'll never host different files at the same -	// URL (bc the ULIDs are generated per piece of media), -	// it's sensible and safe to use a long cache here, so -	// that clients don't keep fetching files over + over again -	c.Header("Cache-Control", "max-age=604800") - +	// if this is a head request, just return info + throw the reader away  	if c.Request.Method == http.MethodHead {  		c.Header("Content-Type", format)  		c.Header("Content-Length", strconv.FormatInt(content.ContentLength, 10)) @@ -126,7 +121,7 @@ func (m *FileServer) ServeFile(c *gin.Context) {  	b := bytes.NewBuffer(make([]byte, 0, 64))  	if _, err := io.CopyN(b, content.Content, 64); err != nil {  		err = fmt.Errorf("ServeFile: error reading from content: %w", err) -		api.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet)  		return  	} diff --git a/internal/api/client/fileserver/servefile_test.go b/internal/api/fileserver/servefile_test.go index 1ca0c60d6..efaf3896d 100644 --- a/internal/api/client/fileserver/servefile_test.go +++ b/internal/api/fileserver/servefile_test.go @@ -26,7 +26,7 @@ import (  	"testing"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/client/fileserver" +	"github.com/superseriousbusiness/gotosocial/internal/api/fileserver"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/media"  	"github.com/superseriousbusiness/gotosocial/testrig" diff --git a/internal/api/nodeinfo.go b/internal/api/nodeinfo.go new file mode 100644 index 000000000..906703434 --- /dev/null +++ b/internal/api/nodeinfo.go @@ -0,0 +1,50 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 api + +import ( +	"github.com/superseriousbusiness/gotosocial/internal/api/nodeinfo" +	"github.com/superseriousbusiness/gotosocial/internal/middleware" +	"github.com/superseriousbusiness/gotosocial/internal/processing" +	"github.com/superseriousbusiness/gotosocial/internal/router" +) + +type NodeInfo struct { +	nodeInfo *nodeinfo.Module +} + +func (w *NodeInfo) Route(r router.Router) { +	// group nodeinfo endpoints together +	nodeInfoGroup := r.AttachGroup("nodeinfo") + +	// attach middlewares appropriate for this group +	nodeInfoGroup.Use( +		middleware.Gzip(), +		middleware.RateLimit(), +		middleware.CacheControl("public", "max-age=120"), // allow cache for 2 minutes +	) + +	w.nodeInfo.Route(nodeInfoGroup.Handle) +} + +func NewNodeInfo(p processing.Processor) *NodeInfo { +	return &NodeInfo{ +		nodeInfo: nodeinfo.New(p), +	} +} diff --git a/internal/api/nodeinfo/nodeinfo.go b/internal/api/nodeinfo/nodeinfo.go new file mode 100644 index 000000000..aa6aec882 --- /dev/null +++ b/internal/api/nodeinfo/nodeinfo.go @@ -0,0 +1,46 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 nodeinfo + +import ( +	"net/http" + +	"github.com/gin-gonic/gin" +	"github.com/superseriousbusiness/gotosocial/internal/processing" +) + +const ( +	NodeInfo2Version     = "2.0" +	NodeInfo2Path        = "/" + NodeInfo2Version +	NodeInfo2ContentType = "application/json; profile=\"http://nodeinfo.diaspora.software/ns/schema/" + NodeInfo2Version + "#\"" +) + +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, NodeInfo2Path, m.NodeInfo2GETHandler) +} diff --git a/internal/api/s2s/nodeinfo/nodeinfoget.go b/internal/api/nodeinfo/nodeinfoget.go index 6cb5e1ebf..f2a3604eb 100644 --- a/internal/api/s2s/nodeinfo/nodeinfoget.go +++ b/internal/api/nodeinfo/nodeinfoget.go @@ -23,11 +23,11 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  ) -// NodeInfoGETHandler swagger:operation GET /nodeinfo/2.0 nodeInfoGet +// NodeInfo2GETHandler swagger:operation GET /nodeinfo/2.0 nodeInfoGet  //  // Returns a compliant nodeinfo response to node info queries.  // @@ -44,23 +44,23 @@ import (  //		'200':  //			schema:  //				"$ref": "#/definitions/nodeinfo" -func (m *Module) NodeInfoGETHandler(c *gin.Context) { -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +func (m *Module) NodeInfo2GETHandler(c *gin.Context) { +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	ni, errWithCode := m.processor.GetNodeInfo(c.Request.Context(), c.Request) +	nodeInfo, errWithCode := m.processor.GetNodeInfo(c.Request.Context())  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} -	b, err := json.Marshal(ni) +	b, err := json.Marshal(nodeInfo)  	if err != nil { -		api.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet) +		apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGet)  		return  	} -	c.Data(http.StatusOK, `application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"`, b) +	c.Data(http.StatusOK, NodeInfo2ContentType, b)  } diff --git a/internal/api/s2s/emoji/emoji.go b/internal/api/s2s/emoji/emoji.go deleted file mode 100644 index d448d2105..000000000 --- a/internal/api/s2s/emoji/emoji.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 emoji - -import ( -	"net/http" - -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router" -	"github.com/superseriousbusiness/gotosocial/internal/uris" -) - -const ( -	// EmojiIDKey is for emoji IDs -	EmojiIDKey = "id" -	// EmojiBasePath is the base path for serving information about Emojis eg https://example.org/emoji -	EmojiWithIDPath = "/" + uris.EmojiPath + "/:" + EmojiIDKey -) - -// Module implements the FederationModule interface -type Module struct { -	processor processing.Processor -} - -// New returns a emoji module -func New(processor processing.Processor) api.FederationModule { -	return &Module{ -		processor: processor, -	} -} - -// Route satisfies the RESTAPIModule interface -func (m *Module) Route(s router.Router) error { -	s.AttachHandler(http.MethodGet, EmojiWithIDPath, m.EmojiGetHandler) -	return nil -} diff --git a/internal/api/s2s/nodeinfo/nodeinfo.go b/internal/api/s2s/nodeinfo/nodeinfo.go deleted file mode 100644 index 539dcc2d1..000000000 --- a/internal/api/s2s/nodeinfo/nodeinfo.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 nodeinfo - -import ( -	"net/http" - -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router" -) - -const ( -	// NodeInfoWellKnownPath is the base path for serving responses to nodeinfo lookup requests. -	NodeInfoWellKnownPath = ".well-known/nodeinfo" -	// NodeInfoBasePath is the path for serving nodeinfo responses. -	NodeInfoBasePath = "/nodeinfo/2.0" -) - -// Module implements the FederationModule interface -type Module struct { -	processor processing.Processor -} - -// New returns a new nodeinfo module -func New(processor processing.Processor) api.FederationModule { -	return &Module{ -		processor: processor, -	} -} - -// Route satisfies the FederationModule interface -func (m *Module) Route(s router.Router) error { -	s.AttachHandler(http.MethodGet, NodeInfoWellKnownPath, m.NodeInfoWellKnownGETHandler) -	s.AttachHandler(http.MethodGet, NodeInfoBasePath, m.NodeInfoGETHandler) -	return nil -} diff --git a/internal/api/s2s/user/user.go b/internal/api/s2s/user/user.go deleted file mode 100644 index 1daa53ad8..000000000 --- a/internal/api/s2s/user/user.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 user - -import ( -	"net/http" - -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router" -	"github.com/superseriousbusiness/gotosocial/internal/uris" -) - -const ( -	// UsernameKey is for account usernames. -	UsernameKey = "username" -	// StatusIDKey is for status IDs -	StatusIDKey = "status" -	// OnlyOtherAccountsKey is for filtering status responses. -	OnlyOtherAccountsKey = "only_other_accounts" -	// MinIDKey is for filtering status responses. -	MinIDKey = "min_id" -	// MaxIDKey is for filtering status responses. -	MaxIDKey = "max_id" -	// PageKey is for filtering status responses. -	PageKey = "page" - -	// UsersBasePath is the base path for serving information about Users eg https://example.org/users -	UsersBasePath = "/" + uris.UsersPath -	// UsersBasePathWithUsername is just the users base path with the Username key in it. -	// Use this anywhere you need to know the username of the user being queried. -	// Eg https://example.org/users/:username -	UsersBasePathWithUsername = UsersBasePath + "/:" + UsernameKey -	// UsersPublicKeyPath is a path to a user's public key, for serving bare minimum AP representations. -	UsersPublicKeyPath = UsersBasePathWithUsername + "/" + uris.PublicKeyPath -	// UsersInboxPath is for serving POST requests to a user's inbox with the given username key. -	UsersInboxPath = UsersBasePathWithUsername + "/" + uris.InboxPath -	// UsersOutboxPath is for serving GET requests to a user's outbox with the given username key. -	UsersOutboxPath = UsersBasePathWithUsername + "/" + uris.OutboxPath -	// UsersFollowersPath is for serving GET request's to a user's followers list, with the given username key. -	UsersFollowersPath = UsersBasePathWithUsername + "/" + uris.FollowersPath -	// UsersFollowingPath is for serving GET request's to a user's following list, with the given username key. -	UsersFollowingPath = UsersBasePathWithUsername + "/" + uris.FollowingPath -	// UsersStatusPath is for serving GET requests to a particular status by a user, with the given username key and status ID -	UsersStatusPath = UsersBasePathWithUsername + "/" + uris.StatusesPath + "/:" + StatusIDKey -	// UsersStatusRepliesPath is for serving the replies collection of a status. -	UsersStatusRepliesPath = UsersStatusPath + "/replies" -) - -// Module implements the FederationAPIModule interface -type Module struct { -	processor processing.Processor -} - -// New returns a new auth module -func New(processor processing.Processor) api.FederationModule { -	return &Module{ -		processor: processor, -	} -} - -// Route satisfies the RESTAPIModule interface -func (m *Module) Route(s router.Router) error { -	s.AttachHandler(http.MethodGet, UsersBasePathWithUsername, m.UsersGETHandler) -	s.AttachHandler(http.MethodPost, UsersInboxPath, m.InboxPOSTHandler) -	s.AttachHandler(http.MethodGet, UsersFollowersPath, m.FollowersGETHandler) -	s.AttachHandler(http.MethodGet, UsersFollowingPath, m.FollowingGETHandler) -	s.AttachHandler(http.MethodGet, UsersStatusPath, m.StatusGETHandler) -	s.AttachHandler(http.MethodGet, UsersPublicKeyPath, m.PublicKeyGETHandler) -	s.AttachHandler(http.MethodGet, UsersStatusRepliesPath, m.StatusRepliesGETHandler) -	s.AttachHandler(http.MethodGet, UsersOutboxPath, m.OutboxGETHandler) -	return nil -} diff --git a/internal/api/security/extraheaders.go b/internal/api/security/extraheaders.go deleted file mode 100644 index f66ac43b4..000000000 --- a/internal/api/security/extraheaders.go +++ /dev/null @@ -1,26 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 security - -import "github.com/gin-gonic/gin" - -// ExtraHeaders adds any additional required headers to the response -func (m *Module) ExtraHeaders(c *gin.Context) { -	c.Header("Server", "gotosocial") -} diff --git a/internal/api/security/flocblock.go b/internal/api/security/flocblock.go deleted file mode 100644 index 0b61f4ef5..000000000 --- a/internal/api/security/flocblock.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 security - -import "github.com/gin-gonic/gin" - -// FlocBlock is a middleware that prevents google chrome cohort tracking by -// writing the Permissions-Policy header after all other parts of the request -// have been completed. Floc was replaced by Topics in 2022 and the spec says -// that interest-cohort will also block Topics (as of 2022-Nov). -// See: https://smartframe.io/blog/google-topics-api-everything-you-need-to-know -// See: https://github.com/patcg-individual-drafts/topics -func (m *Module) FlocBlock(c *gin.Context) { -	c.Header("Permissions-Policy", "browsing-topics=()") -} diff --git a/internal/api/security/ratelimit.go b/internal/api/security/ratelimit.go deleted file mode 100644 index 3c0d078c7..000000000 --- a/internal/api/security/ratelimit.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 security - -import ( -	"net" -	"net/http" -	"time" - -	"github.com/gin-gonic/gin" -	limiter "github.com/ulule/limiter/v3" -	mgin "github.com/ulule/limiter/v3/drivers/middleware/gin" -	memory "github.com/ulule/limiter/v3/drivers/store/memory" -) - -type RateLimitOptions struct { -	Period time.Duration -	Limit  int64 -} - -func (m *Module) LimitReachedHandler(c *gin.Context) { -	code := http.StatusTooManyRequests -	c.AbortWithStatusJSON(code, gin.H{"error": "rate limit reached"}) -} - -// returns a gin middleware that will automatically rate limit caller (by IP address) -// and enrich the response header with the following headers: -// - `x-ratelimit-limit` maximum number of requests allowed per time period (fixed) -// - `x-ratelimit-remaining` number of remaining requests that can still be performed -// - `x-ratelimit-reset` unix timestamp when the rate limit will reset -// if `x-ratelimit-limit` is exceeded an HTTP 429 error is returned -func (m *Module) RateLimit(rateOptions RateLimitOptions) func(c *gin.Context) { -	rate := limiter.Rate{ -		Period: rateOptions.Period, -		Limit:  rateOptions.Limit, -	} - -	store := memory.NewStore() - -	limiterInstance := limiter.New( -		store, -		rate, -		// apply /64 mask to IPv6 addresses -		limiter.WithIPv6Mask(net.CIDRMask(64, 128)), -	) - -	middleware := mgin.NewMiddleware( -		limiterInstance, -		// use custom rate limit reached error -		mgin.WithLimitReachedHandler(m.LimitReachedHandler), -	) - -	return middleware -} diff --git a/internal/api/security/security.go b/internal/api/security/security.go deleted file mode 100644 index 1dce111d3..000000000 --- a/internal/api/security/security.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 security - -import ( -	"net/http" -	"time" - -	"github.com/superseriousbusiness/gotosocial/internal/api" -	"github.com/superseriousbusiness/gotosocial/internal/config" -	"github.com/superseriousbusiness/gotosocial/internal/db" -	"github.com/superseriousbusiness/gotosocial/internal/oauth" -	"github.com/superseriousbusiness/gotosocial/internal/router" -) - -const robotsPath = "/robots.txt" - -// Module implements the ClientAPIModule interface for security middleware -type Module struct { -	db     db.DB -	server oauth.Server -} - -// New returns a new security module -func New(db db.DB, server oauth.Server) api.ClientModule { -	return &Module{ -		db:     db, -		server: server, -	} -} - -// Route attaches security middleware to the given router -func (m *Module) Route(s router.Router) error { -	// only enable rate limit middleware if configured -	// advanced-rate-limit-requests is greater than 0 -	if rateLimitRequests := config.GetAdvancedRateLimitRequests(); rateLimitRequests > 0 { -		s.AttachMiddleware(m.RateLimit(RateLimitOptions{ -			Period: 5 * time.Minute, -			Limit:  int64(rateLimitRequests), -		})) -	} -	s.AttachMiddleware(m.SignatureCheck) -	s.AttachMiddleware(m.FlocBlock) -	s.AttachMiddleware(m.ExtraHeaders) -	s.AttachMiddleware(m.UserAgentBlock) -	s.AttachMiddleware(m.TokenCheck) -	s.AttachHandler(http.MethodGet, robotsPath, m.RobotsGETHandler) -	return nil -} diff --git a/internal/api/security/signaturecheck.go b/internal/api/security/signaturecheck.go deleted file mode 100644 index 1c117cd1b..000000000 --- a/internal/api/security/signaturecheck.go +++ /dev/null @@ -1,51 +0,0 @@ -package security - -import ( -	"net/http" -	"net/url" - -	"github.com/superseriousbusiness/gotosocial/internal/ap" -	"github.com/superseriousbusiness/gotosocial/internal/log" - -	"github.com/gin-gonic/gin" -	"github.com/go-fed/httpsig" -) - -// SignatureCheck checks whether an incoming http request has been signed. If so, it will check if the domain -// that signed the request is permitted to access the server. If it is permitted, the handler will set the key -// verifier and the signature in the gin context for use down the line. -func (m *Module) SignatureCheck(c *gin.Context) { -	// create the verifier from the request -	// if the request is signed, it will have a signature header -	verifier, err := httpsig.NewVerifier(c.Request) -	if err == nil { -		// the request was signed! - -		// The key ID should be given in the signature so that we know where to fetch it from the remote server. -		// This will be something like https://example.org/users/whatever_requesting_user#main-key -		requestingPublicKeyID, err := url.Parse(verifier.KeyId()) -		if err == nil && requestingPublicKeyID != nil { -			// we managed to parse the url! - -			// if the domain is blocked we want to bail as early as possible -			blocked, err := m.db.IsURIBlocked(c.Request.Context(), requestingPublicKeyID) -			if err != nil { -				log.Errorf("could not tell if domain %s was blocked or not: %s", requestingPublicKeyID.Host, err) -				c.AbortWithStatus(http.StatusInternalServerError) -				return -			} -			if blocked { -				log.Infof("domain %s is blocked", requestingPublicKeyID.Host) -				c.AbortWithStatus(http.StatusForbidden) -				return -			} - -			// set the verifier and signature on the context here to save some work further down the line -			c.Set(string(ap.ContextRequestingPublicKeyVerifier), verifier) -			signature := c.GetHeader("Signature") -			if signature != "" { -				c.Set(string(ap.ContextRequestingPublicKeySignature), signature) -			} -		} -	} -} diff --git a/internal/api/security/tokencheck.go b/internal/api/security/tokencheck.go deleted file mode 100644 index 9f2b7f36e..000000000 --- a/internal/api/security/tokencheck.go +++ /dev/null @@ -1,120 +0,0 @@ -/* -   GoToSocial -   Copyright (C) 2021-2022 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 security - -import ( -	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/db" -	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -	"github.com/superseriousbusiness/gotosocial/internal/log" -	"github.com/superseriousbusiness/gotosocial/internal/oauth" -) - -// TokenCheck checks if the client has presented a valid oauth Bearer token. -// If so, it will check the User that the token belongs to, and set that in the context of -// the request. Then, it will look up the account for that user, and set that in the request too. -// If user or account can't be found, then the handler won't *fail*, in case the server wants to allow -// public requests that don't have a Bearer token set (eg., for public instance information and so on). -func (m *Module) TokenCheck(c *gin.Context) { -	ctx := c.Request.Context() -	defer c.Next() - -	if c.Request.Header.Get("Authorization") == "" { -		// no token set in the header, we can just bail -		return -	} - -	ti, err := m.server.ValidationBearerToken(c.Copy().Request) -	if err != nil { -		log.Infof("token was passed in Authorization header but we could not validate it: %s", err) -		return -	} -	c.Set(oauth.SessionAuthorizedToken, ti) - -	// check for user-level token -	if userID := ti.GetUserID(); userID != "" { -		log.Tracef("authenticated user %s with bearer token, scope is %s", userID, ti.GetScope()) - -		// fetch user for this token -		user, err := m.db.GetUserByID(ctx, userID) -		if err != nil { -			if err != db.ErrNoEntries { -				log.Errorf("database error looking for user with id %s: %s", userID, err) -				return -			} -			log.Warnf("no user found for userID %s", userID) -			return -		} - -		if user.ConfirmedAt.IsZero() { -			log.Warnf("authenticated user %s has never confirmed thier email address", userID) -			return -		} - -		if !*user.Approved { -			log.Warnf("authenticated user %s's account was never approved by an admin", userID) -			return -		} - -		if *user.Disabled { -			log.Warnf("authenticated user %s's account was disabled'", userID) -			return -		} - -		c.Set(oauth.SessionAuthorizedUser, user) - -		// fetch account for this token -		if user.Account == nil { -			acct, err := m.db.GetAccountByID(ctx, user.AccountID) -			if err != nil { -				if err != db.ErrNoEntries { -					log.Errorf("database error looking for account with id %s: %s", user.AccountID, err) -					return -				} -				log.Warnf("no account found for userID %s", userID) -				return -			} -			user.Account = acct -		} - -		if !user.Account.SuspendedAt.IsZero() { -			log.Warnf("authenticated user %s's account (accountId=%s) has been suspended", userID, user.AccountID) -			return -		} - -		c.Set(oauth.SessionAuthorizedAccount, user.Account) -	} - -	// check for application token -	if clientID := ti.GetClientID(); clientID != "" { -		log.Tracef("authenticated client %s with bearer token, scope is %s", clientID, ti.GetScope()) - -		// fetch app for this token -		app := >smodel.Application{} -		if err := m.db.GetWhere(ctx, []db.Where{{Key: "client_id", Value: clientID}}, app); err != nil { -			if err != db.ErrNoEntries { -				log.Errorf("database error looking for application with clientID %s: %s", clientID, err) -				return -			} -			log.Warnf("no app found for client %s", clientID) -			return -		} -		c.Set(oauth.SessionAuthorizedApplication, app) -	} -} diff --git a/internal/api/errorhandling.go b/internal/api/util/errorhandling.go index f6fec4168..97894dc36 100644 --- a/internal/api/errorhandling.go +++ b/internal/api/util/errorhandling.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package api +package util  import (  	"context" diff --git a/internal/api/mime.go b/internal/api/util/mime.go index d02b64ab0..396a6f9b3 100644 --- a/internal/api/mime.go +++ b/internal/api/util/mime.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package api +package util  // MIME represents a mime-type.  type MIME string diff --git a/internal/api/negotiate.go b/internal/api/util/negotiate.go index cf44f4328..2854ea09c 100644 --- a/internal/api/negotiate.go +++ b/internal/api/util/negotiate.go @@ -16,7 +16,7 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package api +package util  import (  	"errors" diff --git a/internal/api/client/auth/util.go b/internal/api/util/signaturectx.go index d59983c55..ec7d2c816 100644 --- a/internal/api/client/auth/util.go +++ b/internal/api/util/signaturectx.go @@ -16,16 +16,26 @@     along with this program.  If not, see <http://www.gnu.org/licenses/>.  */ -package auth +package util  import ( -	"github.com/gin-contrib/sessions" +	"context" + +	"github.com/gin-gonic/gin" +	"github.com/superseriousbusiness/gotosocial/internal/ap"  ) -func (m *Module) clearSession(s sessions.Session) { -	s.Clear() +// TransferSignatureContext transfers a signature verifier and signature from a gin context to a go context. +func TransferSignatureContext(c *gin.Context) context.Context { +	ctx := c.Request.Context() + +	if verifier, signed := c.Get(string(ap.ContextRequestingPublicKeyVerifier)); signed { +		ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeyVerifier, verifier) +	} -	if err := s.Save(); err != nil { -		panic(err) +	if signature, signed := c.Get(string(ap.ContextRequestingPublicKeySignature)); signed { +		ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeySignature, signature)  	} + +	return ctx  } diff --git a/internal/api/wellknown.go b/internal/api/wellknown.go new file mode 100644 index 000000000..e4fe15f94 --- /dev/null +++ b/internal/api/wellknown.go @@ -0,0 +1,55 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 api + +import ( +	"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/nodeinfo" +	"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger" +	"github.com/superseriousbusiness/gotosocial/internal/middleware" +	"github.com/superseriousbusiness/gotosocial/internal/processing" +	"github.com/superseriousbusiness/gotosocial/internal/router" +) + +type WellKnown struct { +	nodeInfo  *nodeinfo.Module +	webfinger *webfinger.Module +} + +func (w *WellKnown) Route(r router.Router) { +	// group .well-known endpoints together +	wellKnownGroup := r.AttachGroup(".well-known") + +	// attach middlewares appropriate for this group +	wellKnownGroup.Use( +		middleware.Gzip(), +		middleware.RateLimit(), +		// allow .well-known responses to be cached for 2 minutes +		middleware.CacheControl("public", "max-age=120"), +	) + +	w.nodeInfo.Route(wellKnownGroup.Handle) +	w.webfinger.Route(wellKnownGroup.Handle) +} + +func NewWellKnown(p processing.Processor) *WellKnown { +	return &WellKnown{ +		nodeInfo:  nodeinfo.New(p), +		webfinger: webfinger.New(p), +	} +} diff --git a/internal/api/wellknown/nodeinfo/nodeinfo.go b/internal/api/wellknown/nodeinfo/nodeinfo.go new file mode 100644 index 000000000..fd57ee08b --- /dev/null +++ b/internal/api/wellknown/nodeinfo/nodeinfo.go @@ -0,0 +1,47 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 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 nodeinfo + +import ( +	"net/http" + +	"github.com/gin-gonic/gin" +	"github.com/superseriousbusiness/gotosocial/internal/processing" +) + +const ( +	// NodeInfoWellKnownPath is the base path for serving responses +	// to nodeinfo lookup requests, minus the '.well-known' prefix. +	NodeInfoWellKnownPath = "/nodeinfo" +) + +type Module struct { +	processor processing.Processor +} + +// New returns a new nodeinfo module +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, NodeInfoWellKnownPath, m.NodeInfoWellKnownGETHandler) +} diff --git a/internal/api/s2s/nodeinfo/wellknownget.go b/internal/api/wellknown/nodeinfo/nodeinfoget.go index dc14e43a3..21b29ebd9 100644 --- a/internal/api/s2s/nodeinfo/wellknownget.go +++ b/internal/api/wellknown/nodeinfo/nodeinfoget.go @@ -22,20 +22,20 @@ import (  	"net/http"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  )  // NodeInfoWellKnownGETHandler swagger:operation GET /.well-known/nodeinfo nodeInfoWellKnownGet  // -// Directs callers to /nodeinfo/2.0. +// Returns a well-known response which redirects callers to `/nodeinfo/2.0`.  //  // eg. `{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"http://example.org/nodeinfo/2.0"}]}`  // See: https://nodeinfo.diaspora.software/protocol.html  //  //	---  //	tags: -//	- nodeinfo +//	- .well-known  //  //	produces:  //	- application/json @@ -45,16 +45,16 @@ import (  //			schema:  //				"$ref": "#/definitions/wellKnownResponse"  func (m *Module) NodeInfoWellKnownGETHandler(c *gin.Context) { -	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil { -		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)  		return  	} -	niRel, errWithCode := m.processor.GetNodeInfoRel(c.Request.Context(), c.Request) +	resp, errWithCode := m.processor.GetNodeInfoRel(c.Request.Context())  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} -	c.JSON(http.StatusOK, niRel) +	c.JSON(http.StatusOK, resp)  } diff --git a/internal/api/s2s/webfinger/webfinger.go b/internal/api/wellknown/webfinger/webfinger.go index c46ca7260..8d509b409 100644 --- a/internal/api/s2s/webfinger/webfinger.go +++ b/internal/api/wellknown/webfinger/webfinger.go @@ -21,30 +21,26 @@ package webfinger  import (  	"net/http" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	"github.com/gin-gonic/gin"  	"github.com/superseriousbusiness/gotosocial/internal/processing" -	"github.com/superseriousbusiness/gotosocial/internal/router"  )  const ( -	// WebfingerBasePath is the base path for serving webfinger lookup requests -	WebfingerBasePath = ".well-known/webfinger" +	// WebfingerBasePath is the base path for serving webfinger +	// lookup requests, minus the .well-known prefix +	WebfingerBasePath = "/webfinger"  ) -// Module implements the FederationModule interface  type Module struct {  	processor processing.Processor  } -// New returns a new webfinger module -func New(processor processing.Processor) api.FederationModule { +func New(processor processing.Processor) *Module {  	return &Module{  		processor: processor,  	}  } -// Route satisfies the FederationModule interface -func (m *Module) Route(s router.Router) error { -	s.AttachHandler(http.MethodGet, WebfingerBasePath, m.WebfingerGETRequest) -	return nil +func (m *Module) Route(attachHandler func(method string, path string, f ...gin.HandlerFunc) gin.IRoutes) { +	attachHandler(http.MethodGet, WebfingerBasePath, m.WebfingerGETRequest)  } diff --git a/internal/api/s2s/webfinger/webfinger_test.go b/internal/api/wellknown/webfinger/webfinger_test.go index e5d026d06..bb9f82ccb 100644 --- a/internal/api/s2s/webfinger/webfinger_test.go +++ b/internal/api/wellknown/webfinger/webfinger_test.go @@ -25,8 +25,7 @@ import (  	"github.com/stretchr/testify/suite"  	"github.com/superseriousbusiness/gotosocial/internal/ap" -	"github.com/superseriousbusiness/gotosocial/internal/api/s2s/webfinger" -	"github.com/superseriousbusiness/gotosocial/internal/api/security" +	"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/db"  	"github.com/superseriousbusiness/gotosocial/internal/email" @@ -44,15 +43,14 @@ import (  type WebfingerStandardTestSuite struct {  	// standard suite interfaces  	suite.Suite -	db             db.DB -	tc             typeutils.TypeConverter -	mediaManager   media.Manager -	federator      federation.Federator -	emailSender    email.Sender -	processor      processing.Processor -	storage        *storage.Driver -	oauthServer    oauth.Server -	securityModule *security.Module +	db           db.DB +	tc           typeutils.TypeConverter +	mediaManager media.Manager +	federator    federation.Federator +	emailSender  email.Sender +	processor    processing.Processor +	storage      *storage.Driver +	oauthServer  oauth.Server  	// standard suite models  	testTokens       map[string]*gtsmodel.Token @@ -91,9 +89,8 @@ func (suite *WebfingerStandardTestSuite) SetupTest() {  	suite.federator = testrig.NewTestFederator(suite.db, testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker), suite.storage, suite.mediaManager, fedWorker)  	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)  	suite.processor = testrig.NewTestProcessor(suite.db, suite.storage, suite.federator, suite.emailSender, suite.mediaManager, clientWorker, fedWorker) -	suite.webfingerModule = webfinger.New(suite.processor).(*webfinger.Module) +	suite.webfingerModule = webfinger.New(suite.processor)  	suite.oauthServer = testrig.NewTestOauthServer(suite.db) -	suite.securityModule = security.New(suite.db, suite.oauthServer).(*security.Module)  	testrig.StandardDBSetup(suite.db, suite.testAccounts)  	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") diff --git a/internal/api/s2s/webfinger/webfingerget.go b/internal/api/wellknown/webfinger/webfingerget.go index 9949140c1..72d011734 100644 --- a/internal/api/s2s/webfinger/webfingerget.go +++ b/internal/api/wellknown/webfinger/webfingerget.go @@ -19,16 +19,14 @@  package webfinger  import ( -	"context" +	"errors"  	"fmt"  	"net/http" -	"codeberg.org/gruf/go-kv"  	"github.com/gin-gonic/gin" -	"github.com/superseriousbusiness/gotosocial/internal/ap" -	"github.com/superseriousbusiness/gotosocial/internal/api" +	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"  	"github.com/superseriousbusiness/gotosocial/internal/config" -	"github.com/superseriousbusiness/gotosocial/internal/log" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror"  	"github.com/superseriousbusiness/gotosocial/internal/util"  ) @@ -48,7 +46,7 @@ import (  //  //	---  //	tags: -//	- webfinger +//	- .well-known  //  //	produces:  //	- application/json @@ -58,43 +56,34 @@ import (  //			schema:  //				"$ref": "#/definitions/wellKnownResponse"  func (m *Module) WebfingerGETRequest(c *gin.Context) { -	l := log.WithFields(kv.Fields{ -		{K: "user-agent", V: c.Request.UserAgent()}, -	}...) +	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { +		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet) +		return +	}  	resourceQuery, set := c.GetQuery("resource")  	if !set || resourceQuery == "" { -		l.Debug("aborting request because no resource was set in query") -		c.JSON(http.StatusBadRequest, gin.H{"error": "no 'resource' in request query"}) +		err := errors.New("no 'resource' in request query") +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	}  	requestedUsername, requestedHost, err := util.ExtractWebfingerParts(resourceQuery)  	if err != nil { -		l.Debugf("bad webfinger request with resource query %s: %s", resourceQuery, err) -		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("bad webfinger request with resource query %s", resourceQuery)}) +		err := fmt.Errorf("bad webfinger request with resource query %s: %w", resourceQuery, err) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	accountDomain := config.GetAccountDomain() -	host := config.GetHost() - -	if requestedHost != host && requestedHost != accountDomain { -		l.Debugf("aborting request because requestedHost %s does not belong to this instance", requestedHost) -		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("requested host %s does not belong to this instance", requestedHost)}) +	if requestedHost != config.GetHost() && requestedHost != config.GetAccountDomain() { +		err := fmt.Errorf("requested host %s does not belong to this instance", requestedHost) +		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)  		return  	} -	// transfer the signature verifier from the gin context to the request context -	ctx := c.Request.Context() -	verifier, signed := c.Get(string(ap.ContextRequestingPublicKeyVerifier)) -	if signed { -		ctx = context.WithValue(ctx, ap.ContextRequestingPublicKeyVerifier, verifier) -	} - -	resp, errWithCode := m.processor.GetWebfingerAccount(ctx, requestedUsername) +	resp, errWithCode := m.processor.GetWebfingerAccount(c.Request.Context(), requestedUsername)  	if errWithCode != nil { -		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet) +		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGet)  		return  	} diff --git a/internal/api/s2s/webfinger/webfingerget_test.go b/internal/api/wellknown/webfinger/webfingerget_test.go index 3e91b8f6a..70099b930 100644 --- a/internal/api/s2s/webfinger/webfingerget_test.go +++ b/internal/api/wellknown/webfinger/webfingerget_test.go @@ -28,7 +28,7 @@ import (  	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" -	"github.com/superseriousbusiness/gotosocial/internal/api/s2s/webfinger" +	"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger"  	"github.com/superseriousbusiness/gotosocial/internal/concurrency"  	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/messages" @@ -73,7 +73,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByHo  	clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1)  	fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)  	suite.processor = processing.NewProcessor(suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaManager(suite.db, suite.storage), suite.storage, suite.db, suite.emailSender, clientWorker, fedWorker) -	suite.webfingerModule = webfinger.New(suite.processor).(*webfinger.Module) +	suite.webfingerModule = webfinger.New(suite.processor)  	targetAccount := accountDomainAccount()  	if err := suite.db.Put(context.Background(), targetAccount); err != nil { @@ -110,7 +110,7 @@ func (suite *WebfingerGetTestSuite) TestFingerUserWithDifferentAccountDomainByAc  	clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1)  	fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1)  	suite.processor = processing.NewProcessor(suite.tc, suite.federator, testrig.NewTestOauthServer(suite.db), testrig.NewTestMediaManager(suite.db, suite.storage), suite.storage, suite.db, suite.emailSender, clientWorker, fedWorker) -	suite.webfingerModule = webfinger.New(suite.processor).(*webfinger.Module) +	suite.webfingerModule = webfinger.New(suite.processor)  	targetAccount := accountDomainAccount()  	if err := suite.db.Put(context.Background(), targetAccount); err != nil {  | 
