diff --git a/internal/trans/exporter.go b/internal/trans/exporter.go new file mode 100644 index 000000000..ad7884d2d --- /dev/null +++ b/internal/trans/exporter.go @@ -0,0 +1,39 @@ +/* + GoToSocial + Copyright (C) 2021 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 . +*/ + +package trans + +import ( + "context" + + "github.com/superseriousbusiness/gotosocial/internal/db" +) + +type Exporter interface { + ExportMinimal(ctx context.Context, path string) error +} + +type exporter struct { + db db.DB +} + +func NewExporter(db db.DB) Exporter { + return &exporter{ + db: db, + } +} diff --git a/internal/trans/exportminimal.go b/internal/trans/exportminimal.go new file mode 100644 index 000000000..a000a48f9 --- /dev/null +++ b/internal/trans/exportminimal.go @@ -0,0 +1,63 @@ +/* + GoToSocial + Copyright (C) 2021 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 . +*/ + +package trans + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "os" + + "github.com/superseriousbusiness/gotosocial/internal/db" + transmodel "github.com/superseriousbusiness/gotosocial/internal/trans/model" +) + +func (e *exporter) ExportMinimal(ctx context.Context, path string) error { + f, err := os.Create(path) + if err != nil { + return err + } + + w := bufio.NewWriter(f) + encoder := json.NewEncoder(w) + + accounts := []*transmodel.Account{} + if err := e.db.GetWhere(ctx, []db.Where{{Key: "domain", Value: nil}}, &accounts); err != nil { + return fmt.Errorf("error selecting accounts: %s", err) + } + + for _, a := range accounts { + encoder.Encode(a) + } + + return neatClose(w, f) +} + +func neatClose(w *bufio.Writer, f *os.File) error { + if err := w.Flush(); err != nil { + return fmt.Errorf("error flushing writer: %s", err) + } + + if err := f.Close(); err != nil { + return fmt.Errorf("error closing file: %s", err) + } + + return nil +} diff --git a/internal/trans/exportminimal_test.go b/internal/trans/exportminimal_test.go new file mode 100644 index 000000000..0e686cde5 --- /dev/null +++ b/internal/trans/exportminimal_test.go @@ -0,0 +1,54 @@ +/* + GoToSocial + Copyright (C) 2021 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 . +*/ + +package trans_test + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/trans" +) + +type ExportMinimalTestSuite struct { + TransTestSuite +} + +func (suite *ExportMinimalTestSuite) TestExportMinimalOK() { + // use a temporary file path that will be cleaned when the test is closed + tempFilePath := fmt.Sprintf("%s/%s", suite.T().TempDir(), uuid.NewString()) + + // export to the tempFilePath + exporter := trans.NewExporter(suite.db) + err := exporter.ExportMinimal(context.Background(), tempFilePath) + suite.NoError(err) + + // we should have some bytes in that file now + b, err := os.ReadFile(tempFilePath) + suite.NoError(err) + suite.NotEmpty(b) + suite.T().Log(string(b)) +} + +func TestExportMinimalTestSuite(t *testing.T) { + suite.Run(t, &ExportMinimalTestSuite{}) +} diff --git a/internal/trans/model/account.go b/internal/trans/model/account.go new file mode 100644 index 000000000..26a06e5e3 --- /dev/null +++ b/internal/trans/model/account.go @@ -0,0 +1,48 @@ +/* + GoToSocial + Copyright (C) 2021 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 . +*/ + +package trans + +import ( + "crypto/rsa" + "time" +) + +// Account represents the minimum viable representation of an account for export/import. +type Account struct { + ID string `json:"id"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + Username string `json:"username"` + Domain string `json:"domain,omitempty"` + Locked bool `json:"locked"` + Language string `json:"language,omitempty"` + URI string `json:"uri"` + URL string `json:"url"` + InboxURI string `json:"inbox_uri"` + OutboxURI string `json:"outbox_uri"` + FollowingURI string `json:"following_uri"` + FollowersURI string `json:"followers_uri"` + FeaturedCollectionURI string `json:"featured_collection_uri"` + ActorType string `json:"actor_type"` + PrivateKey *rsa.PrivateKey `json:"private_key,omitempty"` + PublicKey *rsa.PublicKey `json:"public_key"` + PublicKeyURI string `json:"public_key_uri"` + SuspendedAt *time.Time `json:"suspended_at,omitempty"` + SuspensionOrigin string `json:"suspension_origin,omitempty"` +} diff --git a/internal/trans/model/account_test.go b/internal/trans/model/account_test.go new file mode 100644 index 000000000..aa1f37f85 --- /dev/null +++ b/internal/trans/model/account_test.go @@ -0,0 +1,57 @@ +/* + GoToSocial + Copyright (C) 2021 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 . +*/ + +package trans_test + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/suite" + trans "github.com/superseriousbusiness/gotosocial/internal/trans/model" +) + +type AccountTestSuite struct { + ModelTestSuite +} + +func (suite *AccountTestSuite) TestAccountsIdempotent() { + // we should be able to get all accounts with the simple trans.Account struct + accounts := []*trans.Account{} + err := suite.db.GetAll(context.Background(), &accounts) + suite.NoError(err) + suite.NotEmpty(accounts) + + // we should be able to marshal the accounts to json with no problems + b, err := json.Marshal(&accounts) + suite.NoError(err) + suite.NotNil(b) + suite.T().Log(string(b)) + + // the json should be idempotent + mAccounts := []*trans.Account{} + err = json.Unmarshal(b, &mAccounts) + suite.NoError(err) + suite.NotEmpty(mAccounts) + suite.EqualValues(accounts, mAccounts) +} + +func TestAccountTestSuite(t *testing.T) { + suite.Run(t, &AccountTestSuite{}) +} diff --git a/internal/trans/model/block.go b/internal/trans/model/block.go new file mode 100644 index 000000000..3306dac15 --- /dev/null +++ b/internal/trans/model/block.go @@ -0,0 +1,30 @@ +/* + GoToSocial + Copyright (C) 2021 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 . +*/ + +package trans + +import "time" + +type Block struct { + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + URI string `json:"uri"` + AccountID string `json:"account_id"` + TargetAccountID string `json:"target_account_id"` +} diff --git a/internal/trans/model/block_test.go b/internal/trans/model/block_test.go new file mode 100644 index 000000000..9a3b78e1a --- /dev/null +++ b/internal/trans/model/block_test.go @@ -0,0 +1,57 @@ +/* + GoToSocial + Copyright (C) 2021 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 . +*/ + +package trans_test + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/suite" + trans "github.com/superseriousbusiness/gotosocial/internal/trans/model" +) + +type BlockTestSuite struct { + ModelTestSuite +} + +func (suite *AccountTestSuite) TestBlocksIdempotent() { + // we should be able to get all blocks with the simple trans.Block struct + blocks := []*trans.Block{} + err := suite.db.GetAll(context.Background(), &blocks) + suite.NoError(err) + suite.NotEmpty(blocks) + + // we should be able to marshal the blocks to json with no problems + b, err := json.Marshal(&blocks) + suite.NoError(err) + suite.NotNil(b) + suite.T().Log(string(b)) + + // the json should be idempotent + mBlocks := []*trans.Block{} + err = json.Unmarshal(b, &mBlocks) + suite.NoError(err) + suite.NotEmpty(mBlocks) + suite.EqualValues(blocks, mBlocks) +} + +func TestBlockTestSuite(t *testing.T) { + suite.Run(t, &BlockTestSuite{}) +} diff --git a/internal/trans/model/domainblock.go b/internal/trans/model/domainblock.go new file mode 100644 index 000000000..cc3e8924b --- /dev/null +++ b/internal/trans/model/domainblock.go @@ -0,0 +1,3 @@ +package trans + + diff --git a/internal/trans/model/model_test.go b/internal/trans/model/model_test.go new file mode 100644 index 000000000..278ef4a9d --- /dev/null +++ b/internal/trans/model/model_test.go @@ -0,0 +1,39 @@ +/* + GoToSocial + Copyright (C) 2021 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 . +*/ + +package trans_test + +import ( + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type ModelTestSuite struct { + suite.Suite + db db.DB +} + +func (suite *ModelTestSuite) SetupTest() { + suite.db = testrig.NewTestDB() + testrig.StandardDBSetup(suite.db, nil) +} + +func (suite *ModelTestSuite) TearDownTest() { + testrig.StandardDBTeardown(suite.db) +} diff --git a/internal/trans/trans_test.go b/internal/trans/trans_test.go new file mode 100644 index 000000000..5249fc6ff --- /dev/null +++ b/internal/trans/trans_test.go @@ -0,0 +1,39 @@ +/* + GoToSocial + Copyright (C) 2021 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 . +*/ + +package trans_test + +import ( + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type TransTestSuite struct { + suite.Suite + db db.DB +} + +func (suite *TransTestSuite) SetupTest() { + suite.db = testrig.NewTestDB() + testrig.StandardDBSetup(suite.db, nil) +} + +func (suite *TransTestSuite) TearDownTest() { + testrig.StandardDBTeardown(suite.db) +} diff --git a/testrig/db.go b/testrig/db.go index 7cb4f7645..5798af5ce 100644 --- a/testrig/db.go +++ b/testrig/db.go @@ -72,6 +72,16 @@ func NewTestDB() db.DB { return testDB } +// CreateTestTables creates prerequisite test tables in the database, but doesn't populate them. +func CreateTestTables(db db.DB) { + ctx := context.Background() + for _, m := range testModels { + if err := db.CreateTable(ctx, m); err != nil { + logrus.Panicf("error creating table for %+v: %s", m, err) + } + } +} + // StandardDBSetup populates a given db with all the necessary tables/models for perfoming tests. // // The accounts parameter is provided in case the db should be populated with a certain set of accounts. @@ -85,13 +95,9 @@ func StandardDBSetup(db db.DB, accounts map[string]*gtsmodel.Account) { logrus.Panic("db setup: db was nil") } - ctx := context.Background() + CreateTestTables(db) - for _, m := range testModels { - if err := db.CreateTable(ctx, m); err != nil { - logrus.Panicf("error creating table for %+v: %s", m, err) - } - } + ctx := context.Background() for _, v := range NewTestTokens() { if err := db.Put(ctx, v); err != nil { @@ -111,6 +117,12 @@ func StandardDBSetup(db db.DB, accounts map[string]*gtsmodel.Account) { } } + for _, v := range NewTestBlocks() { + if err := db.Put(ctx, v); err != nil { + logrus.Panic(err) + } + } + for _, v := range NewTestUsers() { if err := db.Put(ctx, v); err != nil { logrus.Panic(err) diff --git a/testrig/testmodels.go b/testrig/testmodels.go index e4daed12c..45f47f46a 100644 --- a/testrig/testmodels.go +++ b/testrig/testmodels.go @@ -1215,6 +1215,19 @@ func NewTestFollows() map[string]*gtsmodel.Follow { } } +func NewTestBlocks() map[string]*gtsmodel.Block { + return map[string]*gtsmodel.Block{ + "local_account_2_block_remote_account_1": { + ID: "01FEXXET6XXMF7G2V3ASZP3YQW", + CreatedAt: time.Now().Add(-1 * time.Hour), + UpdatedAt: time.Now().Add(-1 * time.Hour), + URI: "http://localhost:8080/users/1happyturtle/blocks/01FEXXET6XXMF7G2V3ASZP3YQW", + AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", + TargetAccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX", + }, + } +} + // ActivityWithSignature wraps a pub.Activity along with its signature headers, for testing. type ActivityWithSignature struct { Activity pub.Activity