create.go (11294B)
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 federatingdb 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "strings" 25 26 "codeberg.org/gruf/go-kv" 27 "codeberg.org/gruf/go-logger/v2/level" 28 "github.com/superseriousbusiness/activity/streams/vocab" 29 "github.com/superseriousbusiness/gotosocial/internal/ap" 30 "github.com/superseriousbusiness/gotosocial/internal/db" 31 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 32 "github.com/superseriousbusiness/gotosocial/internal/id" 33 "github.com/superseriousbusiness/gotosocial/internal/log" 34 "github.com/superseriousbusiness/gotosocial/internal/messages" 35 ) 36 37 // Create adds a new entry to the database which must be able to be 38 // keyed by its id. 39 // 40 // Note that Activity values received from federated peers may also be 41 // created in the database this way if the Federating Protocol is 42 // enabled. The client may freely decide to store only the id instead of 43 // the entire value. 44 // 45 // The library makes this call only after acquiring a lock first. 46 // 47 // Under certain conditions and network activities, Create may be called 48 // multiple times for the same ActivityStreams object. 49 func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { 50 if log.Level() >= level.DEBUG { 51 i, err := marshalItem(asType) 52 if err != nil { 53 return err 54 } 55 l := log.WithContext(ctx). 56 WithField("create", i) 57 l.Trace("entering Create") 58 } 59 60 receivingAccount, requestingAccount, internal := extractFromCtx(ctx) 61 if internal { 62 return nil // Already processed. 63 } 64 65 switch asType.GetTypeName() { 66 case ap.ActivityBlock: 67 // BLOCK SOMETHING 68 return f.activityBlock(ctx, asType, receivingAccount, requestingAccount) 69 case ap.ActivityCreate: 70 // CREATE SOMETHING 71 return f.activityCreate(ctx, asType, receivingAccount, requestingAccount) 72 case ap.ActivityFollow: 73 // FOLLOW SOMETHING 74 return f.activityFollow(ctx, asType, receivingAccount, requestingAccount) 75 case ap.ActivityLike: 76 // LIKE SOMETHING 77 return f.activityLike(ctx, asType, receivingAccount, requestingAccount) 78 case ap.ActivityFlag: 79 // FLAG / REPORT SOMETHING 80 return f.activityFlag(ctx, asType, receivingAccount, requestingAccount) 81 } 82 return nil 83 } 84 85 /* 86 BLOCK HANDLERS 87 */ 88 89 func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, receiving *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { 90 blockable, ok := asType.(vocab.ActivityStreamsBlock) 91 if !ok { 92 return errors.New("activityBlock: could not convert type to block") 93 } 94 95 block, err := f.typeConverter.ASBlockToBlock(ctx, blockable) 96 if err != nil { 97 return fmt.Errorf("activityBlock: could not convert Block to gts model block") 98 } 99 100 block.ID = id.NewULID() 101 102 if err := f.state.DB.PutBlock(ctx, block); err != nil { 103 return fmt.Errorf("activityBlock: database error inserting block: %s", err) 104 } 105 106 f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{ 107 APObjectType: ap.ActivityBlock, 108 APActivityType: ap.ActivityCreate, 109 GTSModel: block, 110 ReceivingAccount: receiving, 111 }) 112 return nil 113 } 114 115 /* 116 CREATE HANDLERS 117 */ 118 119 func (f *federatingDB) activityCreate(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { 120 create, ok := asType.(vocab.ActivityStreamsCreate) 121 if !ok { 122 return errors.New("activityCreate: could not convert type to create") 123 } 124 125 // create should have an object 126 object := create.GetActivityStreamsObject() 127 if object == nil { 128 return errors.New("Create had no Object") 129 } 130 131 errs := []string{} 132 // iterate through the object(s) to see what we're meant to be creating 133 for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() { 134 asObjectType := objectIter.GetType() 135 if asObjectType == nil { 136 // currently we can't do anything with just a Create of something that's not an Object with a type 137 // TODO: process a Create with an Object that's just a URI or something 138 errs = append(errs, "object of Create was not a Type") 139 continue 140 } 141 142 // we have a type -- what is it? 143 asObjectTypeName := asObjectType.GetTypeName() 144 switch asObjectTypeName { 145 case ap.ObjectNote: 146 // CREATE A NOTE 147 if err := f.createNote(ctx, objectIter.GetActivityStreamsNote(), receivingAccount, requestingAccount); err != nil { 148 errs = append(errs, err.Error()) 149 } 150 default: 151 errs = append(errs, fmt.Sprintf("received an object on a Create that we couldn't handle: %s", asObjectType.GetTypeName())) 152 } 153 } 154 155 if len(errs) != 0 { 156 return fmt.Errorf("activityCreate: one or more errors while processing activity: %s", strings.Join(errs, "; ")) 157 } 158 159 return nil 160 } 161 162 // createNote handles a Create activity with a Note type. 163 func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStreamsNote, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { 164 l := log.WithContext(ctx). 165 WithFields(kv.Fields{ 166 {"receivingAccount", receivingAccount.URI}, 167 {"requestingAccount", requestingAccount.URI}, 168 }...) 169 170 // Check if we have a forward. 171 // In other words, was the note posted to our inbox by at least one actor who actually created the note, or are they just forwarding it? 172 forward := true 173 174 // note should have an attributedTo 175 noteAttributedTo := note.GetActivityStreamsAttributedTo() 176 if noteAttributedTo == nil { 177 return errors.New("createNote: note had no attributedTo") 178 } 179 180 // compare the attributedTo(s) with the actor who posted this to our inbox 181 for attributedToIter := noteAttributedTo.Begin(); attributedToIter != noteAttributedTo.End(); attributedToIter = attributedToIter.Next() { 182 if !attributedToIter.IsIRI() { 183 continue 184 } 185 iri := attributedToIter.GetIRI() 186 if requestingAccount.URI == iri.String() { 187 // at least one creator of the note, and the actor who posted the note to our inbox, are the same, so it's not a forward 188 forward = false 189 } 190 } 191 192 // If we do have a forward, we should ignore the content for now and just dereference based on the URL/ID of the note instead, to get the note straight from the horse's mouth 193 if forward { 194 l.Trace("note is a forward") 195 id := note.GetJSONLDId() 196 if !id.IsIRI() { 197 // if the note id isn't an IRI, there's nothing we can do here 198 return nil 199 } 200 // pass the note iri into the processor and have it do the dereferencing instead of doing it here 201 f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{ 202 APObjectType: ap.ObjectNote, 203 APActivityType: ap.ActivityCreate, 204 APIri: id.GetIRI(), 205 APObjectModel: nil, 206 GTSModel: nil, 207 ReceivingAccount: receivingAccount, 208 }) 209 return nil 210 } 211 212 // if we reach this point, we know it's not a forwarded status, so proceed with processing it as normal 213 214 status, err := f.typeConverter.ASStatusToStatus(ctx, note) 215 if err != nil { 216 return fmt.Errorf("createNote: error converting note to status: %s", err) 217 } 218 219 // id the status based on the time it was created 220 statusID, err := id.NewULIDFromTime(status.CreatedAt) 221 if err != nil { 222 return err 223 } 224 status.ID = statusID 225 226 if err := f.state.DB.PutStatus(ctx, status); err != nil { 227 if errors.Is(err, db.ErrAlreadyExists) { 228 // the status already exists in the database, which means we've already handled everything else, 229 // so we can just return nil here and be done with it. 230 return nil 231 } 232 // an actual error has happened 233 return fmt.Errorf("createNote: database error inserting status: %s", err) 234 } 235 236 f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{ 237 APObjectType: ap.ObjectNote, 238 APActivityType: ap.ActivityCreate, 239 APObjectModel: note, 240 GTSModel: status, 241 ReceivingAccount: receivingAccount, 242 }) 243 244 return nil 245 } 246 247 /* 248 FOLLOW HANDLERS 249 */ 250 251 func (f *federatingDB) activityFollow(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { 252 follow, ok := asType.(vocab.ActivityStreamsFollow) 253 if !ok { 254 return errors.New("activityFollow: could not convert type to follow") 255 } 256 257 followRequest, err := f.typeConverter.ASFollowToFollowRequest(ctx, follow) 258 if err != nil { 259 return fmt.Errorf("activityFollow: could not convert Follow to follow request: %s", err) 260 } 261 262 followRequest.ID = id.NewULID() 263 264 if err := f.state.DB.PutFollowRequest(ctx, followRequest); err != nil { 265 return fmt.Errorf("activityFollow: database error inserting follow request: %s", err) 266 } 267 268 f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{ 269 APObjectType: ap.ActivityFollow, 270 APActivityType: ap.ActivityCreate, 271 GTSModel: followRequest, 272 ReceivingAccount: receivingAccount, 273 }) 274 275 return nil 276 } 277 278 /* 279 LIKE HANDLERS 280 */ 281 282 func (f *federatingDB) activityLike(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { 283 like, ok := asType.(vocab.ActivityStreamsLike) 284 if !ok { 285 return errors.New("activityLike: could not convert type to like") 286 } 287 288 fave, err := f.typeConverter.ASLikeToFave(ctx, like) 289 if err != nil { 290 return fmt.Errorf("activityLike: could not convert Like to fave: %w", err) 291 } 292 293 fave.ID = id.NewULID() 294 295 if err := f.state.DB.PutStatusFave(ctx, fave); err != nil { 296 if errors.Is(err, db.ErrAlreadyExists) { 297 // The Like already exists in the database, which 298 // means we've already handled side effects. We can 299 // just return nil here and be done with it. 300 return nil 301 } 302 return fmt.Errorf("activityLike: database error inserting fave: %w", err) 303 } 304 305 f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{ 306 APObjectType: ap.ActivityLike, 307 APActivityType: ap.ActivityCreate, 308 GTSModel: fave, 309 ReceivingAccount: receivingAccount, 310 }) 311 312 return nil 313 } 314 315 /* 316 FLAG HANDLERS 317 */ 318 319 func (f *federatingDB) activityFlag(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { 320 flag, ok := asType.(vocab.ActivityStreamsFlag) 321 if !ok { 322 return errors.New("activityFlag: could not convert type to flag") 323 } 324 325 report, err := f.typeConverter.ASFlagToReport(ctx, flag) 326 if err != nil { 327 return fmt.Errorf("activityFlag: could not convert Flag to report: %w", err) 328 } 329 330 report.ID = id.NewULID() 331 332 if err := f.state.DB.PutReport(ctx, report); err != nil { 333 return fmt.Errorf("activityFlag: database error inserting report: %w", err) 334 } 335 336 f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{ 337 APObjectType: ap.ActivityFlag, 338 APActivityType: ap.ActivityCreate, 339 GTSModel: report, 340 ReceivingAccount: receivingAccount, 341 }) 342 343 return nil 344 }