summaryrefslogtreecommitdiff
path: root/internal/federation/federatingdb/update.go
blob: 26ea81f72bfaf62ed160a0789885c7d507572805 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package federatingdb

import (
	"context"
	"errors"

	"codeberg.org/gruf/go-logger/v2/level"
	"github.com/superseriousbusiness/activity/streams/vocab"
	"github.com/superseriousbusiness/gotosocial/internal/ap"
	"github.com/superseriousbusiness/gotosocial/internal/config"
	"github.com/superseriousbusiness/gotosocial/internal/db"
	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
	"github.com/superseriousbusiness/gotosocial/internal/log"
	"github.com/superseriousbusiness/gotosocial/internal/messages"
)

// Update sets an existing entry to the database based on the value's
// id.
//
// Note that Activity values received from federated peers may also be
// updated in the database this way if the Federating Protocol is
// enabled. The client may freely decide to store only the id instead of
// the entire value.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
	l := log.WithContext(ctx)

	if log.Level() >= level.DEBUG {
		i, err := marshalItem(asType)
		if err != nil {
			return err
		}
		l = l.WithField("update", i)
		l.Debug("entering Update")
	}

	receivingAccount, requestingAccount, internal := extractFromCtx(ctx)
	if internal {
		return nil // Already processed.
	}

	if accountable, ok := ap.ToAccountable(asType); ok {
		return f.updateAccountable(ctx, receivingAccount, requestingAccount, accountable)
	}

	if statusable, ok := ap.ToStatusable(asType); ok {
		return f.updateStatusable(ctx, receivingAccount, requestingAccount, statusable)
	}

	return nil
}

func (f *federatingDB) updateAccountable(ctx context.Context, receivingAcct *gtsmodel.Account, requestingAcct *gtsmodel.Account, accountable ap.Accountable) error {
	// Extract AP URI of the updated Accountable model.
	idProp := accountable.GetJSONLDId()
	if idProp == nil || !idProp.IsIRI() {
		return gtserror.New("invalid id prop")
	}

	// Get the account URI string for checks
	accountURI := idProp.GetIRI()
	accountURIStr := accountURI.String()

	// Don't try to update local accounts.
	if accountURI.Host == config.GetHost() {
		return nil
	}

	// Check that update was by the account themselves.
	if accountURIStr != requestingAcct.URI {
		return gtserror.Newf("update for %s was not requested by owner", accountURIStr)
	}

	// Pass in to the processor the existing version of the requesting
	// account that we have, plus the Accountable representation that
	// was delivered along with the Update, for further asynchronous
	// updating of eg., avatar/header, emojis, etc. The actual db
	// inserts/updates will take place there.
	f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
		APObjectType:     ap.ObjectProfile,
		APActivityType:   ap.ActivityUpdate,
		GTSModel:         requestingAcct,
		APObjectModel:    accountable,
		ReceivingAccount: receivingAcct,
	})

	return nil
}

func (f *federatingDB) updateStatusable(ctx context.Context, receivingAcct *gtsmodel.Account, requestingAcct *gtsmodel.Account, statusable ap.Statusable) error {
	// Extract AP URI of the updated model.
	idProp := statusable.GetJSONLDId()
	if idProp == nil || !idProp.IsIRI() {
		return gtserror.New("invalid id prop")
	}

	// Get the status URI string for lookups.
	statusURI := idProp.GetIRI()
	statusURIStr := statusURI.String()

	// Don't try to update local statuses.
	if statusURI.Host == config.GetHost() {
		return nil
	}

	// Check if this is a forwarded object, i.e. did
	// the account making the request also create this?
	forwarded := !isSender(statusable, requestingAcct)

	// Get the status we have on file for this URI string.
	status, err := f.state.DB.GetStatusByURI(ctx, statusURIStr)
	if err != nil && !errors.Is(err, db.ErrNoEntries) {
		return gtserror.Newf("error fetching status from db: %w", err)
	}

	if status == nil {
		// We haven't seen this status before, be
		// lenient and handle as a CREATE event.
		return f.createStatusable(ctx,
			receivingAcct,
			requestingAcct,
			statusable,
			forwarded,
		)
	}

	if forwarded {
		// For forwarded updates, set a nil AS
		// status to force refresh from remote.
		statusable = nil
	}

	// Queue an UPDATE NOTE activity to our fedi API worker,
	// this will handle necessary database insertions, etc.
	f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
		APObjectType:     ap.ObjectNote,
		APActivityType:   ap.ActivityUpdate,
		GTSModel:         status, // original status
		APObjectModel:    (ap.Statusable)(statusable),
		ReceivingAccount: receivingAcct,
	})

	return nil
}