mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-29 15:02:25 -05:00
[feature] Support setting private notes on accounts (#1982)
* Support setting private notes on accounts * Reformat comment whitespace * Add missing license headers * Use apiutil.ParseID * Rename Note model and cache to AccountNote * Update golden cache config in test/envparsing.sh * Rename gtsmodel/note.go to gtsmodel/accountnote.go * Update AccountNote uniqueness constraint name Now has same prefix as other indexes on this table. --------- Co-authored-by: tobi <31960611+tsmethurst@users.noreply.github.com>
This commit is contained in:
parent
5f3e095717
commit
22ac4607a1
19 changed files with 597 additions and 2 deletions
|
|
@ -49,6 +49,7 @@ type BunDBStandardTestSuite struct {
|
|||
testFaves map[string]*gtsmodel.StatusFave
|
||||
testLists map[string]*gtsmodel.List
|
||||
testListEntries map[string]*gtsmodel.ListEntry
|
||||
testAccountNotes map[string]*gtsmodel.AccountNote
|
||||
}
|
||||
|
||||
func (suite *BunDBStandardTestSuite) SetupSuite() {
|
||||
|
|
@ -68,6 +69,7 @@ func (suite *BunDBStandardTestSuite) SetupSuite() {
|
|||
suite.testFaves = testrig.NewTestFaves()
|
||||
suite.testLists = testrig.NewTestLists()
|
||||
suite.testListEntries = testrig.NewTestListEntries()
|
||||
suite.testAccountNotes = testrig.NewTestAccountNotes()
|
||||
}
|
||||
|
||||
func (suite *BunDBStandardTestSuite) SetupTest() {
|
||||
|
|
|
|||
62
internal/db/bundb/migrations/20230711214815_account_notes.go
Normal file
62
internal/db/bundb/migrations/20230711214815_account_notes.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// 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 migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
// Account note table.
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model(>smodel.AccountNote{}).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add IDs index to the account note table.
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Model(>smodel.AccountNote{}).
|
||||
Index("account_notes_account_id_target_account_id_idx").
|
||||
Column("account_id", "target_account_id").
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +85,19 @@ func (r *relationshipDB) GetRelationship(ctx context.Context, requestingAccount
|
|||
return nil, fmt.Errorf("GetRelationship: error checking blockedBy: %w", err)
|
||||
}
|
||||
|
||||
// retrieve a note by the requesting account on the target account, if there is one
|
||||
note, err := r.GetNote(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
requestingAccount,
|
||||
targetAccount,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, fmt.Errorf("GetRelationship: error fetching note: %w", err)
|
||||
}
|
||||
if note != nil {
|
||||
rel.Note = note.Comment
|
||||
}
|
||||
|
||||
return &rel, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
99
internal/db/bundb/relationship_note.go
Normal file
99
internal/db/bundb/relationship_note.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// 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 bundb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func (r *relationshipDB) GetNote(ctx context.Context, sourceAccountID string, targetAccountID string) (*gtsmodel.AccountNote, error) {
|
||||
return r.getNote(
|
||||
ctx,
|
||||
"AccountID.TargetAccountID",
|
||||
func(note *gtsmodel.AccountNote) error {
|
||||
return r.conn.NewSelect().Model(note).
|
||||
Where("? = ?", bun.Ident("account_id"), sourceAccountID).
|
||||
Where("? = ?", bun.Ident("target_account_id"), targetAccountID).
|
||||
Scan(ctx)
|
||||
},
|
||||
sourceAccountID,
|
||||
targetAccountID,
|
||||
)
|
||||
}
|
||||
|
||||
func (r *relationshipDB) getNote(ctx context.Context, lookup string, dbQuery func(*gtsmodel.AccountNote) error, keyParts ...any) (*gtsmodel.AccountNote, error) {
|
||||
// Fetch note from cache with loader callback
|
||||
note, err := r.state.Caches.GTS.AccountNote().Load(lookup, func() (*gtsmodel.AccountNote, error) {
|
||||
var note gtsmodel.AccountNote
|
||||
|
||||
// Not cached! Perform database query
|
||||
if err := dbQuery(¬e); err != nil {
|
||||
return nil, r.conn.ProcessError(err)
|
||||
}
|
||||
|
||||
return ¬e, nil
|
||||
}, keyParts...)
|
||||
if err != nil {
|
||||
// already processed
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gtscontext.Barebones(ctx) {
|
||||
// Only a barebones model was requested.
|
||||
return note, nil
|
||||
}
|
||||
|
||||
// Set the note source account
|
||||
note.Account, err = r.state.DB.GetAccountByID(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
note.AccountID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting note source account: %w", err)
|
||||
}
|
||||
|
||||
// Set the note target account
|
||||
note.TargetAccount, err = r.state.DB.GetAccountByID(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
note.TargetAccountID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting note target account: %w", err)
|
||||
}
|
||||
|
||||
return note, nil
|
||||
}
|
||||
|
||||
func (r *relationshipDB) PutNote(ctx context.Context, note *gtsmodel.AccountNote) error {
|
||||
note.UpdatedAt = time.Now()
|
||||
return r.state.Caches.GTS.AccountNote().Store(note, func() error {
|
||||
_, err := r.conn.
|
||||
NewInsert().
|
||||
Model(note).
|
||||
On("CONFLICT (?, ?) DO UPDATE", bun.Ident("account_id"), bun.Ident("target_account_id")).
|
||||
Set("? = ?, ? = ?", bun.Ident("updated_at"), note.UpdatedAt, bun.Ident("comment"), note.Comment).
|
||||
Exec(ctx)
|
||||
return r.conn.ProcessError(err)
|
||||
})
|
||||
}
|
||||
|
|
@ -912,6 +912,53 @@ func (suite *RelationshipTestSuite) TestUpdateFollow() {
|
|||
suite.True(relationship.Notifying)
|
||||
}
|
||||
|
||||
func (suite *RelationshipTestSuite) TestGetNote() {
|
||||
ctx := context.Background()
|
||||
|
||||
// Retrieve a fixture note
|
||||
account1 := suite.testAccounts["local_account_1"].ID
|
||||
account2 := suite.testAccounts["local_account_2"].ID
|
||||
expectedNote := suite.testAccountNotes["local_account_2_note_on_1"]
|
||||
note, err := suite.db.GetNote(ctx, account2, account1)
|
||||
suite.NoError(err)
|
||||
suite.NotNil(note)
|
||||
suite.Equal(expectedNote.ID, note.ID)
|
||||
suite.Equal(expectedNote.Comment, note.Comment)
|
||||
}
|
||||
|
||||
func (suite *RelationshipTestSuite) TestPutNote() {
|
||||
ctx := context.Background()
|
||||
|
||||
// put a note in
|
||||
account1 := suite.testAccounts["local_account_1"].ID
|
||||
account2 := suite.testAccounts["local_account_2"].ID
|
||||
err := suite.db.PutNote(ctx, >smodel.AccountNote{
|
||||
ID: "01H539R2NA0M83JX15Y5RWKE97",
|
||||
AccountID: account1,
|
||||
TargetAccountID: account2,
|
||||
Comment: "foo",
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
// make sure the note is in the db
|
||||
note, err := suite.db.GetNote(ctx, account1, account2)
|
||||
suite.NoError(err)
|
||||
suite.NotNil(note)
|
||||
suite.Equal("01H539R2NA0M83JX15Y5RWKE97", note.ID)
|
||||
suite.Equal("foo", note.Comment)
|
||||
|
||||
// update the note
|
||||
note.Comment = "bar"
|
||||
err = suite.db.PutNote(ctx, note)
|
||||
suite.NoError(err)
|
||||
|
||||
// make sure the comment changes
|
||||
note, err = suite.db.GetNote(ctx, account1, account2)
|
||||
suite.NoError(err)
|
||||
suite.NotNil(note)
|
||||
suite.Equal("bar", note.Comment)
|
||||
}
|
||||
|
||||
func TestRelationshipTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(RelationshipTestSuite))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,4 +165,10 @@ type Relationship interface {
|
|||
|
||||
// CountAccountFollowerRequests returns number of follow requests originating from the given account.
|
||||
CountAccountFollowRequesting(ctx context.Context, accountID string) (int, error)
|
||||
|
||||
// GetNote gets a private note from a source account on a target account, if it exists.
|
||||
GetNote(ctx context.Context, sourceAccountID string, targetAccountID string) (*gtsmodel.AccountNote, error)
|
||||
|
||||
// PutNote creates or updates a private note.
|
||||
PutNote(ctx context.Context, note *gtsmodel.AccountNote) error
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue