collections.go (7003B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package fedi 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "net/http" 25 "net/url" 26 27 "github.com/superseriousbusiness/gotosocial/internal/ap" 28 "github.com/superseriousbusiness/gotosocial/internal/db" 29 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 30 ) 31 32 // InboxPost handles POST requests to a user's inbox for new activitypub messages. 33 // 34 // InboxPost returns true if the request was handled as an ActivityPub POST to an actor's inbox. 35 // If false, the request was not an ActivityPub request and may still be handled by the caller in another way, such as serving a web page. 36 // 37 // If the error is nil, then the ResponseWriter's headers and response has already been written. If a non-nil error is returned, then no response has been written. 38 // 39 // If the Actor was constructed with the Federated Protocol enabled, side effects will occur. 40 // 41 // If the Federated Protocol is not enabled, writes the http.StatusMethodNotAllowed status code in the response. No side effects occur. 42 func (p *Processor) InboxPost(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { 43 return p.federator.FederatingActor().PostInbox(ctx, w, r) 44 } 45 46 // OutboxGet returns the activitypub representation of a local user's outbox. 47 // This contains links to PUBLIC posts made by this user. 48 func (p *Processor) OutboxGet(ctx context.Context, requestedUsername string, page bool, maxID string, minID string) (interface{}, gtserror.WithCode) { 49 requestedAccount, _, errWithCode := p.authenticate(ctx, requestedUsername) 50 if errWithCode != nil { 51 return nil, errWithCode 52 } 53 54 var data map[string]interface{} 55 // There are two scenarios: 56 // 1. we're asked for the whole collection and not a page -- we can just return the collection, with no items, but a link to 'first' page. 57 // 2. we're asked for a specific page; this can be either the first page or any other page 58 59 if !page { 60 /* 61 scenario 1: return the collection with no items 62 we want something that looks like this: 63 { 64 "@context": "https://www.w3.org/ns/activitystreams", 65 "id": "https://example.org/users/whatever/outbox", 66 "type": "OrderedCollection", 67 "first": "https://example.org/users/whatever/outbox?page=true", 68 "last": "https://example.org/users/whatever/outbox?min_id=0&page=true" 69 } 70 */ 71 collection, err := p.tc.OutboxToASCollection(ctx, requestedAccount.OutboxURI) 72 if err != nil { 73 return nil, gtserror.NewErrorInternalError(err) 74 } 75 76 data, err = ap.Serialize(collection) 77 if err != nil { 78 return nil, gtserror.NewErrorInternalError(err) 79 } 80 81 return data, nil 82 } 83 84 // scenario 2 -- get the requested page 85 // limit pages to 30 entries per page 86 publicStatuses, err := p.state.DB.GetAccountStatuses(ctx, requestedAccount.ID, 30, true, true, maxID, minID, false, true) 87 if err != nil && !errors.Is(err, db.ErrNoEntries) { 88 return nil, gtserror.NewErrorInternalError(err) 89 } 90 91 outboxPage, err := p.tc.StatusesToASOutboxPage(ctx, requestedAccount.OutboxURI, maxID, minID, publicStatuses) 92 if err != nil { 93 return nil, gtserror.NewErrorInternalError(err) 94 } 95 data, err = ap.Serialize(outboxPage) 96 if err != nil { 97 return nil, gtserror.NewErrorInternalError(err) 98 } 99 100 return data, nil 101 } 102 103 // FollowersGet handles the getting of a fedi/activitypub representation of a user/account's followers, performing appropriate 104 // authentication before returning a JSON serializable interface to the caller. 105 func (p *Processor) FollowersGet(ctx context.Context, requestedUsername string) (interface{}, gtserror.WithCode) { 106 requestedAccount, _, errWithCode := p.authenticate(ctx, requestedUsername) 107 if errWithCode != nil { 108 return nil, errWithCode 109 } 110 111 requestedAccountURI, err := url.Parse(requestedAccount.URI) 112 if err != nil { 113 return nil, gtserror.NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", requestedAccount.URI, err)) 114 } 115 116 requestedFollowers, err := p.federator.FederatingDB().Followers(ctx, requestedAccountURI) 117 if err != nil { 118 return nil, gtserror.NewErrorInternalError(fmt.Errorf("error fetching followers for uri %s: %s", requestedAccountURI.String(), err)) 119 } 120 121 data, err := ap.Serialize(requestedFollowers) 122 if err != nil { 123 return nil, gtserror.NewErrorInternalError(err) 124 } 125 126 return data, nil 127 } 128 129 // FollowingGet handles the getting of a fedi/activitypub representation of a user/account's following, performing appropriate 130 // authentication before returning a JSON serializable interface to the caller. 131 func (p *Processor) FollowingGet(ctx context.Context, requestedUsername string) (interface{}, gtserror.WithCode) { 132 requestedAccount, _, errWithCode := p.authenticate(ctx, requestedUsername) 133 if errWithCode != nil { 134 return nil, errWithCode 135 } 136 137 requestedAccountURI, err := url.Parse(requestedAccount.URI) 138 if err != nil { 139 return nil, gtserror.NewErrorInternalError(fmt.Errorf("error parsing url %s: %s", requestedAccount.URI, err)) 140 } 141 142 requestedFollowing, err := p.federator.FederatingDB().Following(ctx, requestedAccountURI) 143 if err != nil { 144 return nil, gtserror.NewErrorInternalError(fmt.Errorf("error fetching following for uri %s: %s", requestedAccountURI.String(), err)) 145 } 146 147 data, err := ap.Serialize(requestedFollowing) 148 if err != nil { 149 return nil, gtserror.NewErrorInternalError(err) 150 } 151 152 return data, nil 153 } 154 155 // FeaturedCollectionGet returns an ordered collection of the requested username's Pinned posts. 156 // The returned collection have an `items` property which contains an ordered list of status URIs. 157 func (p *Processor) FeaturedCollectionGet(ctx context.Context, requestedUsername string) (interface{}, gtserror.WithCode) { 158 requestedAccount, _, errWithCode := p.authenticate(ctx, requestedUsername) 159 if errWithCode != nil { 160 return nil, errWithCode 161 } 162 163 statuses, err := p.state.DB.GetAccountPinnedStatuses(ctx, requestedAccount.ID) 164 if err != nil { 165 if !errors.Is(err, db.ErrNoEntries) { 166 return nil, gtserror.NewErrorInternalError(err) 167 } 168 } 169 170 collection, err := p.tc.StatusesToASFeaturedCollection(ctx, requestedAccount.FeaturedCollectionURI, statuses) 171 if err != nil { 172 return nil, gtserror.NewErrorInternalError(err) 173 } 174 175 data, err := ap.Serialize(collection) 176 if err != nil { 177 return nil, gtserror.NewErrorInternalError(err) 178 } 179 180 return data, nil 181 }