From f21a1a5ecf3a4d1b84b66a502f35c521ab66b0f3 Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 19 Dec 2018 22:54:31 -0600 Subject: [PATCH 01/14] add verified func to engine to get list of verified users --- backend/app/store/engine/bolt_accessor.go | 4 ++-- backend/app/store/engine/bolt_admin.go | 17 +++++++++++++++++ backend/app/store/engine/bolt_admin_test.go | 11 +++++++++++ backend/app/store/engine/engine.go | 1 + backend/app/store/engine/mongo.go | 17 ++++++++++++++++- backend/app/store/engine/mongo_test.go | 12 ++++++++++++ 6 files changed, 59 insertions(+), 3 deletions(-) diff --git a/backend/app/store/engine/bolt_accessor.go b/backend/app/store/engine/bolt_accessor.go index 1e14c79c..e4ca1384 100644 --- a/backend/app/store/engine/bolt_accessor.go +++ b/backend/app/store/engine/bolt_accessor.go @@ -7,8 +7,8 @@ import ( "strings" "time" - "github.com/coreos/bbolt" - "github.com/hashicorp/go-multierror" + bolt "github.com/coreos/bbolt" + multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/umputun/remark/backend/app/store" diff --git a/backend/app/store/engine/bolt_admin.go b/backend/app/store/engine/bolt_admin.go index 1c221d49..5f78b69a 100644 --- a/backend/app/store/engine/bolt_admin.go +++ b/backend/app/store/engine/bolt_admin.go @@ -320,3 +320,20 @@ func (b *BoltDB) IsVerified(siteID string, userID string) (verified bool) { }) return verified } + +// Verified returns list of verified userIDs +func (b *BoltDB) Verified(siteID string) (ids []string, err error) { + bdb, err := b.db(siteID) + if err != nil { + return nil, err + } + err = bdb.View(func(tx *bolt.Tx) error { + usersBkt := tx.Bucket([]byte(verifiedBucketName)) + _ = usersBkt.ForEach(func(k, _ []byte) error { + ids = append(ids, string(k)) + return nil + }) + return nil + }) + return ids, err +} diff --git a/backend/app/store/engine/bolt_admin_test.go b/backend/app/store/engine/bolt_admin_test.go index a46fc4a5..82318ec6 100644 --- a/backend/app/store/engine/bolt_admin_test.go +++ b/backend/app/store/engine/bolt_admin_test.go @@ -214,4 +214,15 @@ func TestBoltAdmin_Verified(t *testing.T) { assert.NoError(t, b.SetVerified("radio-t", "u1xyz", false)) assert.False(t, b.IsVerified("radio-t-bad", "u1"), "nothing verified on wrong site") + + assert.NoError(t, b.SetVerified("radio-t", "u1", true)) + assert.NoError(t, b.SetVerified("radio-t", "u2", true)) + assert.NoError(t, b.SetVerified("radio-t", "u3", false)) + + ids, err := b.Verified("radio-t") + assert.NoError(t, err) + assert.Equal(t, []string{"u1", "u2"}, ids, "verified 2 ids") + + ids, err = b.Verified("radio-t-bad") + assert.Error(t, err, "site \"radio-t-bad\" not found", "fail on wrong site") } diff --git a/backend/app/store/engine/engine.go b/backend/app/store/engine/engine.go index 8d150808..7355a91c 100644 --- a/backend/app/store/engine/engine.go +++ b/backend/app/store/engine/engine.go @@ -53,6 +53,7 @@ type Admin interface { IsReadOnly(locator store.Locator) bool // check if post read-only SetVerified(siteID string, userID string, status bool) error // set/reset verified flag IsVerified(siteID string, userID string) bool // check verified status + Verified(siteID string) ([]string, error) // list of verified user ids } const ( diff --git a/backend/app/store/engine/mongo.go b/backend/app/store/engine/mongo.go index a6e50833..587e7bda 100644 --- a/backend/app/store/engine/mongo.go +++ b/backend/app/store/engine/mongo.go @@ -6,7 +6,7 @@ import ( "github.com/globalsign/mgo" "github.com/globalsign/mgo/bson" "github.com/go-pkgz/mongo" - "github.com/hashicorp/go-multierror" + multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/umputun/remark/backend/app/store" @@ -221,6 +221,21 @@ func (m *Mongo) IsVerified(siteID string, userID string) (verified bool) { return err == nil && meta.Verified } +// Verified returns list of verified user IDs +func (m *Mongo) Verified(siteID string) (ids []string, err error) { + metas := []metaUser{} + err = m.conn.WithCustomCollection(mongoMetaUsers, func(coll *mgo.Collection) error { + return coll.Find(bson.M{"site": siteID, "verified": true}).All(&metas) + }) + if err != nil { + return nil, err + } + for _, m := range metas { + ids = append(ids, m.ID) + } + return ids, nil +} + // SetBlock blocks/unblocks user for given site. ttl defines for for how long, 0 - permanent // block uses blocksBucketName with key=userID and val=TTL+now func (m *Mongo) SetBlock(siteID string, userID string, status bool, ttl time.Duration) error { diff --git a/backend/app/store/engine/mongo_test.go b/backend/app/store/engine/mongo_test.go index 0d94745b..797d2e76 100644 --- a/backend/app/store/engine/mongo_test.go +++ b/backend/app/store/engine/mongo_test.go @@ -239,6 +239,18 @@ func TestMongo_Verified(t *testing.T) { assert.NoError(t, m.SetVerified("radio-t", "u1xyz", false)) assert.False(t, m.IsVerified("radio-t-bad", "u1"), "nothing verified on wrong site") + + assert.NoError(t, m.SetVerified("radio-t", "u1", true)) + assert.NoError(t, m.SetVerified("radio-t", "u2", true)) + assert.NoError(t, m.SetVerified("radio-t", "u3", false)) + + ids, err := m.Verified("radio-t") + assert.NoError(t, err) + assert.Equal(t, []string{"u1", "u2"}, ids, "verified 2 ids") + + ids, err = m.Verified("radio-t-bad") + assert.NoError(t, err) + assert.Equal(t, 0, len(ids)) } func TestMongo_GetForUser(t *testing.T) { From 25d3d3a5f40a68a29481d588a2ad06f9c4d5254c Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 20 Dec 2018 00:02:36 -0600 Subject: [PATCH 02/14] add service level Metas accessor --- backend/app/store/service/service.go | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index 36710160..3ffea086 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -28,6 +28,22 @@ type DataStore struct { } } +// UserMetaData keeps info about user flags +type UserMetaData struct { + UserID string + Blocked struct { + Status bool + Until time.Time + } + Verified bool +} + +// PostMetaData keeps info about post flags +type PostMetaData struct { + URL string + ReadOnly bool +} + const defaultCommentMaxSize = 2000 // UnlimitedVotes doesn't restrict MaxVotes @@ -206,6 +222,55 @@ func (s *DataStore) IsAdmin(siteID string, userID string) bool { return false } +// Metas returns metadata for users and posts +func (s *DataStore) Metas(siteID string) (umetas []UserMetaData, pmetas []PostMetaData, err error) { + + // set posts meta + posts, err := s.List(siteID, 0, 0) + if err != nil { + return nil, nil, errors.Wrapf(err, "can't get list of posts for %s", siteID) + } + for _, p := range posts { + if p.ReadOnly { + pmetas = append(pmetas, PostMetaData{URL: p.URL, ReadOnly: true}) + } + } + + // set users meta + m := map[string]UserMetaData{} + + // process blocked users + blocked, err := s.Blocked(siteID) + if err != nil { + return nil, nil, errors.Wrapf(err, "can't get list of blocked users for %s", siteID) + } + for _, b := range blocked { + val, ok := m[b.ID] + if !ok { + val = UserMetaData{} + } + val.Blocked.Status = true + val.Blocked.Until = b.Until + m[b.ID] = val + } + + // process verified users + verified, err := s.Verified(siteID) + if err != nil { + return nil, nil, errors.Wrapf(err, "can't get list of verified users for %s", siteID) + } + for _, v := range verified { + val, ok := m[v] + if !ok { + val = UserMetaData{} + } + val.Verified = true + m[v] = val + } + + return umetas, pmetas, nil +} + // getsScopedLocks pull lock from the map if found or create a new one func (s *DataStore) getsScopedLocks(id string) (lock sync.Locker) { s.scopedLocks.Do(func() { s.scopedLocks.locks = map[string]sync.Locker{} }) From 10f21f78f003b5ac519720640d61bc13b7c70ee6 Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 20 Dec 2018 02:49:56 -0600 Subject: [PATCH 03/14] basic test for Metas service --- backend/app/cmd/cleanup.go | 2 +- backend/app/store/service/service.go | 11 ++++++++--- backend/app/store/service/service_test.go | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/backend/app/cmd/cleanup.go b/backend/app/cmd/cleanup.go index 3b1119dc..f1211bad 100644 --- a/backend/app/cmd/cleanup.go +++ b/backend/app/cmd/cleanup.go @@ -16,7 +16,7 @@ import ( // CleanupCommand set of flags and command for cleanup type CleanupCommand struct { Site string `short:"s" long:"site" env:"SITE" default:"remark" description:"site name"` - Dry bool `long:"dry" env:"DRY" description:"dry mode, will not remove comments"` + Dry bool `long:"dry" description:"dry mode, will not remove comments"` From string `long:"from" description:"from yyyymmdd"` To string `long:"to" description:"from yyyymmdd"` BadWords []string `short:"w" long:"bword" description:"bad word(s)"` diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index 3ffea086..d774b624 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -231,9 +231,10 @@ func (s *DataStore) Metas(siteID string) (umetas []UserMetaData, pmetas []PostMe return nil, nil, errors.Wrapf(err, "can't get list of posts for %s", siteID) } for _, p := range posts { - if p.ReadOnly { + if s.IsReadOnly(store.Locator{SiteID: siteID, URL: p.URL}) { pmetas = append(pmetas, PostMetaData{URL: p.URL, ReadOnly: true}) } + } // set users meta @@ -247,7 +248,7 @@ func (s *DataStore) Metas(siteID string) (umetas []UserMetaData, pmetas []PostMe for _, b := range blocked { val, ok := m[b.ID] if !ok { - val = UserMetaData{} + val = UserMetaData{UserID: b.ID} } val.Blocked.Status = true val.Blocked.Until = b.Until @@ -262,12 +263,16 @@ func (s *DataStore) Metas(siteID string) (umetas []UserMetaData, pmetas []PostMe for _, v := range verified { val, ok := m[v] if !ok { - val = UserMetaData{} + val = UserMetaData{UserID: v} } val.Verified = true m[v] = val } + for _, u := range m { + umetas = append(umetas, u) + } + return umetas, pmetas, nil } diff --git a/backend/app/store/service/service_test.go b/backend/app/store/service/service_test.go index b5b22297..3ede92af 100644 --- a/backend/app/store/service/service_test.go +++ b/backend/app/store/service/service_test.go @@ -374,6 +374,28 @@ func TestService_Counts(t *testing.T) { }, res) } +func TestService_Metas(t *testing.T) { + defer os.Remove(testDb) + // two comments for https://radio-t.com + b := DataStore{Interface: prepStoreEngine(t), EditDuration: 100 * time.Millisecond, + AdminStore: admin.NewStaticKeyStore("secret 123")} + + assert.NoError(t, b.SetVerified("radio-t", "user1", true)) + assert.NoError(t, b.SetReadOnly(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, true)) + + um, pm, err := b.Metas("radio-t") + require.NoError(t, err) + + assert.Equal(t, 1, len(um)) + assert.Equal(t, "user1", um[0].UserID) + assert.Equal(t, true, um[0].Verified) + assert.Equal(t, false, um[0].Blocked.Status) + + assert.Equal(t, 1, len(pm)) + assert.Equal(t, "https://radio-t.com", pm[0].URL) + assert.Equal(t, true, pm[0].ReadOnly) +} + // makes new boltdb, put two records func prepStoreEngine(t *testing.T) engine.Interface { os.Remove(testDb) From 110566a65886f651b93235b16fa900538c804744 Mon Sep 17 00:00:00 2001 From: Umputun Date: Fri, 21 Dec 2018 00:01:59 -0600 Subject: [PATCH 04/14] change native export to new format --- backend/app/migrator/migrator.go | 2 + backend/app/migrator/remark.go | 47 ++++++++++++++++++++++- backend/app/migrator/remark_test.go | 42 ++++++++++++++++---- backend/app/store/service/service.go | 21 +++++----- backend/app/store/service/service_test.go | 9 ++++- 5 files changed, 100 insertions(+), 21 deletions(-) diff --git a/backend/app/migrator/migrator.go b/backend/app/migrator/migrator.go index ae80a52b..4ff7d520 100644 --- a/backend/app/migrator/migrator.go +++ b/backend/app/migrator/migrator.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/umputun/remark/backend/app/store" + "github.com/umputun/remark/backend/app/store/service" ) // Importer defines interface to convert posts from external sources @@ -29,6 +30,7 @@ type Store interface { Find(locator store.Locator, sort string) ([]store.Comment, error) List(siteID string, limit int, skip int) ([]store.PostInfo, error) DeleteAll(siteID string) error + Metas(siteID string) (umetas []service.UserMetaData, pmetas []service.PostMetaData, err error) } // ImportParams defines everything needed to run import diff --git a/backend/app/migrator/remark.go b/backend/app/migrator/remark.go index 93b1505d..666b5fe7 100644 --- a/backend/app/migrator/remark.go +++ b/backend/app/migrator/remark.go @@ -4,27 +4,41 @@ import ( "bufio" "bytes" "encoding/json" + "fmt" "io" "log" "github.com/pkg/errors" "github.com/umputun/remark/backend/app/store" + "github.com/umputun/remark/backend/app/store/service" +) + +const ( + header = `{"version":1, "comments":[` + metaHeader = "],\n\"meta\":" + footer = `}` ) // Remark implements exporter and importer for internal store format +// {"version": 1, comments:[{...}\n,{}], meta: {meta}} type Remark struct { DataStore Store } // Export all comments to writer as json strings. Each comment is one string, separated by "\n" func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { + + if _, err := fmt.Fprintf(w, "%s\n", header); err != nil { + return 0, err + } + topics, err := r.DataStore.List(siteID, 0, 0) if err != nil { return 0, err } - log.Printf("[DEBUG] exporting %d topics", len(topics)) + log.Printf("[DEBUG] exporting %d topics", len(topics)) commentsCount := 0 for i := len(topics) - 1; i >= 0; i-- { // topics from List sorted in opposite direction topic := topics[i] @@ -33,7 +47,7 @@ func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { return commentsCount, err } - for _, comment := range comments { + for n, comment := range comments { buf := &bytes.Buffer{} enc := json.NewEncoder(buf) @@ -43,13 +57,42 @@ func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { return commentsCount, errors.Wrapf(err, "can't marshal %v", comments) } data := buf.Bytes() + data = bytes.TrimSuffix(data, []byte("\n")) if _, err := w.Write(data); err != nil { return commentsCount, errors.Wrap(err, "can't write comment data") } + if n < len(comments)-1 || i != 0 { // don't add , on last comment + w.Write([]byte(",")) + } + w.Write([]byte("\n")) + commentsCount++ } } log.Printf("[DEBUG] exported %d comments", commentsCount) + + if _, err := fmt.Fprintf(w, "%s", metaHeader); err != nil { + return 0, err + } + + meta := struct { + Users []service.UserMetaData `json:"users"` + Posts []service.PostMetaData `json:"posts"` + }{} + + meta.Users, meta.Posts, err = r.DataStore.Metas(siteID) + if err != nil { + return 0, err + } + + if err := json.NewEncoder(w).Encode(meta); err != nil { + return 0, err + } + + if _, err := fmt.Fprintf(w, "%s\n", footer); err != nil { + return 0, err + } + return commentsCount, nil } diff --git a/backend/app/migrator/remark_test.go b/backend/app/migrator/remark_test.go index 7819466a..ff31a83e 100644 --- a/backend/app/migrator/remark_test.go +++ b/backend/app/migrator/remark_test.go @@ -2,13 +2,14 @@ package migrator import ( "bytes" + "encoding/json" "fmt" "log" "os" "testing" "time" - "github.com/coreos/bbolt" + bolt "github.com/coreos/bbolt" "github.com/stretchr/testify/assert" "github.com/umputun/remark/backend/app/store" @@ -22,6 +23,9 @@ var testDb = "/tmp/test-remark.db" func TestRemark_Export(t *testing.T) { defer os.Remove(testDb) b := prep(t) + b.SetReadOnly(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, true) + b.SetBlock("radio-t", "user-2", true, time.Hour) + b.SetVerified("radio-t", "user-1", true) r := Remark{DataStore: b} buf := &bytes.Buffer{} @@ -29,11 +33,35 @@ func TestRemark_Export(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 2, size) - c1, err := buf.ReadString('\n') - assert.Nil(t, err) + c1 := buf.String() log.Print(c1) - exp := `{"id":"efbc17f177ee1a1c0ee6e1e025749966ec071adc","pid":"","text":"some text, link","user":{"name":"user name","id":"user1","picture":"","ip":"293ec5b0cf154855258824ec7fac5dc63d176915","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:22-06:00"}` + "\n" - assert.Equal(t, exp, c1) + + res := struct { + Version int `json:"version"` + Comments []store.Comment `json:"comments"` + Meta struct { + Users []service.UserMetaData `json:"users"` + Posts []service.PostMetaData `json:"posts"` + } `json:"meta"` + }{} + + err = json.Unmarshal([]byte(c1), &res) + assert.NoError(t, err) + assert.Equal(t, 2, len(res.Comments)) + assert.Equal(t, "some text, link", res.Comments[0].Text) + + assert.Equal(t, 2, len(res.Meta.Users)) + assert.Equal(t, "user-2", res.Meta.Users[0].ID) + assert.Equal(t, true, res.Meta.Users[0].Blocked.Status) + assert.Equal(t, false, res.Meta.Users[0].Verified) + assert.Equal(t, "user-1", res.Meta.Users[1].ID) + assert.Equal(t, false, res.Meta.Users[1].Blocked.Status) + assert.Equal(t, true, res.Meta.Users[1].Verified) + + assert.Equal(t, 1, len(res.Meta.Posts)) + assert.Equal(t, "https://radio-t.com", res.Meta.Posts[0].URL) + assert.Equal(t, true, res.Meta.Posts[0].ReadOnly) + } func TestRemark_Import(t *testing.T) { @@ -113,8 +141,8 @@ func prep(t *testing.T) *service.DataStore { comment = store.Comment{ Text: "some text2", Timestamp: time.Date(2017, 12, 20, 15, 18, 23, 0, time.Local), - Locator: store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, - User: store.User{ID: "user1", Name: "user name"}, + Locator: store.Locator{URL: "https://radio-t.com/2", SiteID: "radio-t"}, + User: store.User{ID: "user2", Name: "user name"}, } _, err = b.Create(comment) assert.Nil(t, err) diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index d774b624..7f3e6f47 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -30,18 +30,18 @@ type DataStore struct { // UserMetaData keeps info about user flags type UserMetaData struct { - UserID string + ID string `json:"id"` Blocked struct { - Status bool - Until time.Time - } - Verified bool + Status bool `json:"status"` + Until time.Time `json:"until"` + } `json:"blocked"` + Verified bool `json:"verified"` } // PostMetaData keeps info about post flags type PostMetaData struct { - URL string - ReadOnly bool + URL string `json:"url"` + ReadOnly bool `json:"read_only"` } const defaultCommentMaxSize = 2000 @@ -224,7 +224,8 @@ func (s *DataStore) IsAdmin(siteID string, userID string) bool { // Metas returns metadata for users and posts func (s *DataStore) Metas(siteID string) (umetas []UserMetaData, pmetas []PostMetaData, err error) { - + umetas = []UserMetaData{} + pmetas = []PostMetaData{} // set posts meta posts, err := s.List(siteID, 0, 0) if err != nil { @@ -248,7 +249,7 @@ func (s *DataStore) Metas(siteID string) (umetas []UserMetaData, pmetas []PostMe for _, b := range blocked { val, ok := m[b.ID] if !ok { - val = UserMetaData{UserID: b.ID} + val = UserMetaData{ID: b.ID} } val.Blocked.Status = true val.Blocked.Until = b.Until @@ -263,7 +264,7 @@ func (s *DataStore) Metas(siteID string) (umetas []UserMetaData, pmetas []PostMe for _, v := range verified { val, ok := m[v] if !ok { - val = UserMetaData{UserID: v} + val = UserMetaData{ID: v} } val.Verified = true m[v] = val diff --git a/backend/app/store/service/service_test.go b/backend/app/store/service/service_test.go index 3ede92af..ac741074 100644 --- a/backend/app/store/service/service_test.go +++ b/backend/app/store/service/service_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/coreos/bbolt" + bolt "github.com/coreos/bbolt" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -380,10 +380,15 @@ func TestService_Metas(t *testing.T) { b := DataStore{Interface: prepStoreEngine(t), EditDuration: 100 * time.Millisecond, AdminStore: admin.NewStaticKeyStore("secret 123")} + um, pm, err := b.Metas("radio-t") + require.NoError(t, err) + assert.Equal(t, 0, len(um)) + assert.Equal(t, 0, len(pm)) + assert.NoError(t, b.SetVerified("radio-t", "user1", true)) assert.NoError(t, b.SetReadOnly(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, true)) - um, pm, err := b.Metas("radio-t") + um, pm, err = b.Metas("radio-t") require.NoError(t, err) assert.Equal(t, 1, len(um)) From a0dff0a4f04aadb4d6f2e1dd9648f43c49966e9b Mon Sep 17 00:00:00 2001 From: Umputun Date: Fri, 21 Dec 2018 00:13:13 -0600 Subject: [PATCH 05/14] adjust export tests --- backend/app/migrator/remark_test.go | 3 +-- backend/app/rest/api/admin_test.go | 4 ++-- backend/app/rest/api/migrator_test.go | 6 +++--- backend/app/store/service/service_test.go | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/backend/app/migrator/remark_test.go b/backend/app/migrator/remark_test.go index ff31a83e..1439fa6a 100644 --- a/backend/app/migrator/remark_test.go +++ b/backend/app/migrator/remark_test.go @@ -22,7 +22,7 @@ var testDb = "/tmp/test-remark.db" func TestRemark_Export(t *testing.T) { defer os.Remove(testDb) - b := prep(t) + b := prep(t) // write 2 comments b.SetReadOnly(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, true) b.SetBlock("radio-t", "user-2", true, time.Hour) b.SetVerified("radio-t", "user-1", true) @@ -61,7 +61,6 @@ func TestRemark_Export(t *testing.T) { assert.Equal(t, 1, len(res.Meta.Posts)) assert.Equal(t, "https://radio-t.com", res.Meta.Posts[0].URL) assert.Equal(t, true, res.Meta.Posts[0].ReadOnly) - } func TestRemark_Import(t *testing.T) { diff --git a/backend/app/rest/api/admin_test.go b/backend/app/rest/api/admin_test.go index 5a45573d..24fed171 100644 --- a/backend/app/rest/api/admin_test.go +++ b/backend/app/rest/api/admin_test.go @@ -455,7 +455,7 @@ func TestAdmin_ExportStream(t *testing.T) { body, code := getWithAuth(t, ts.URL+"/api/v1/admin/export?site=radio-t&mode=stream") assert.Equal(t, 200, code) - assert.Equal(t, 2, strings.Count(body, "\n")) + assert.Equal(t, 6, strings.Count(body, "\n")) assert.Equal(t, 2, strings.Count(body, "\"text\"")) t.Logf("%s", body) } @@ -487,7 +487,7 @@ func TestAdmin_ExportFile(t *testing.T) { assert.NoError(t, err) ungzBody, err := ioutil.ReadAll(ungzReader) assert.NoError(t, err) - assert.Equal(t, 2, strings.Count(string(ungzBody), "\n")) + assert.Equal(t, 6, strings.Count(string(ungzBody), "\n")) assert.Equal(t, 2, strings.Count(string(ungzBody), "\"text\"")) t.Logf("%s", string(ungzBody)) } diff --git a/backend/app/rest/api/migrator_test.go b/backend/app/rest/api/migrator_test.go index 031e8d04..0d659c0e 100644 --- a/backend/app/rest/api/migrator_test.go +++ b/backend/app/rest/api/migrator_test.go @@ -15,7 +15,7 @@ import ( "testing" "time" - "github.com/coreos/bbolt" + bolt "github.com/coreos/bbolt" "github.com/go-chi/chi" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -234,7 +234,7 @@ func TestMigrator_Export(t *testing.T) { assert.NoError(t, err) ungzBody, err := ioutil.ReadAll(ungzReader) assert.NoError(t, err) - assert.Equal(t, 2, strings.Count(string(ungzBody), "\n")) + assert.Equal(t, 6, strings.Count(string(ungzBody), "\n")) assert.Equal(t, 2, strings.Count(string(ungzBody), "\"text\"")) t.Logf("%s", string(ungzBody)) @@ -248,7 +248,7 @@ func TestMigrator_Export(t *testing.T) { body, err := ioutil.ReadAll(resp.Body) assert.NoError(t, err) - assert.Equal(t, 2, strings.Count(string(body), "\n")) + assert.Equal(t, 6, strings.Count(string(body), "\n")) assert.Equal(t, 2, strings.Count(string(body), "\"text\"")) t.Logf("%s", string(body)) diff --git a/backend/app/store/service/service_test.go b/backend/app/store/service/service_test.go index ac741074..9e0edd3a 100644 --- a/backend/app/store/service/service_test.go +++ b/backend/app/store/service/service_test.go @@ -392,7 +392,7 @@ func TestService_Metas(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, len(um)) - assert.Equal(t, "user1", um[0].UserID) + assert.Equal(t, "user1", um[0].ID) assert.Equal(t, true, um[0].Verified) assert.Equal(t, false, um[0].Blocked.Status) From ca5948ab319ddafc311d95ef74ac9003454a9c15 Mon Sep 17 00:00:00 2001 From: Umputun Date: Fri, 21 Dec 2018 00:28:30 -0600 Subject: [PATCH 06/14] lint: errors not handled in export --- backend/app/migrator/remark.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/backend/app/migrator/remark.go b/backend/app/migrator/remark.go index 666b5fe7..6cad5cfb 100644 --- a/backend/app/migrator/remark.go +++ b/backend/app/migrator/remark.go @@ -29,7 +29,7 @@ type Remark struct { // Export all comments to writer as json strings. Each comment is one string, separated by "\n" func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { - if _, err := fmt.Fprintf(w, "%s\n", header); err != nil { + if _, err = fmt.Fprintf(w, "%s\n", header); err != nil { return 0, err } @@ -62,9 +62,14 @@ func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { return commentsCount, errors.Wrap(err, "can't write comment data") } if n < len(comments)-1 || i != 0 { // don't add , on last comment - w.Write([]byte(",")) + if _, err = w.Write([]byte(",")); err != nil { + return commentsCount, errors.Wrap(err, "can't write comment separator") + } + } + + if _, err = w.Write([]byte("\n")); err != nil { + return commentsCount, errors.Wrap(err, "can't write comment eol") } - w.Write([]byte("\n")) commentsCount++ } @@ -72,7 +77,7 @@ func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { log.Printf("[DEBUG] exported %d comments", commentsCount) if _, err := fmt.Fprintf(w, "%s", metaHeader); err != nil { - return 0, err + return 0, errors.Wrap(err, "can't write meta header") } meta := struct { @@ -82,15 +87,15 @@ func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { meta.Users, meta.Posts, err = r.DataStore.Metas(siteID) if err != nil { - return 0, err + return 0, errors.Wrap(err, "can't get meta") } if err := json.NewEncoder(w).Encode(meta); err != nil { - return 0, err + return 0, errors.Wrap(err, "can't encode meta") } if _, err := fmt.Fprintf(w, "%s\n", footer); err != nil { - return 0, err + return 0, errors.Wrap(err, "can't write footer") } return commentsCount, nil From a5265bc82a64698f5823e940184d68c6c1550993 Mon Sep 17 00:00:00 2001 From: Umputun Date: Fri, 21 Dec 2018 00:39:26 -0600 Subject: [PATCH 07/14] lint: more shadowing --- backend/app/migrator/remark.go | 25 ++++++++++++++----------- backend/app/rest/api/rest.go | 2 +- backend/app/rest/api/ssl.go | 4 ++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/backend/app/migrator/remark.go b/backend/app/migrator/remark.go index 6cad5cfb..b2964c75 100644 --- a/backend/app/migrator/remark.go +++ b/backend/app/migrator/remark.go @@ -42,9 +42,9 @@ func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { commentsCount := 0 for i := len(topics) - 1; i >= 0; i-- { // topics from List sorted in opposite direction topic := topics[i] - comments, err := r.DataStore.Find(store.Locator{SiteID: siteID, URL: topic.URL}, "time") + comments, e := r.DataStore.Find(store.Locator{SiteID: siteID, URL: topic.URL}, "time") if err != nil { - return commentsCount, err + return commentsCount, e } for n, comment := range comments { @@ -53,12 +53,12 @@ func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { enc := json.NewEncoder(buf) enc.SetEscapeHTML(false) - if err := enc.Encode(comment); err != nil { + if err = enc.Encode(comment); err != nil { return commentsCount, errors.Wrapf(err, "can't marshal %v", comments) } data := buf.Bytes() data = bytes.TrimSuffix(data, []byte("\n")) - if _, err := w.Write(data); err != nil { + if _, err = w.Write(data); err != nil { return commentsCount, errors.Wrap(err, "can't write comment data") } if n < len(comments)-1 || i != 0 { // don't add , on last comment @@ -75,9 +75,13 @@ func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { } } log.Printf("[DEBUG] exported %d comments", commentsCount) + err = r.exportMeta(siteID, w) + return commentsCount, err +} - if _, err := fmt.Fprintf(w, "%s", metaHeader); err != nil { - return 0, errors.Wrap(err, "can't write meta header") +func (r *Remark) exportMeta(siteID string, w io.Writer) (err error) { + if _, err = fmt.Fprintf(w, "%s", metaHeader); err != nil { + return errors.Wrap(err, "can't write meta header") } meta := struct { @@ -87,18 +91,17 @@ func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { meta.Users, meta.Posts, err = r.DataStore.Metas(siteID) if err != nil { - return 0, errors.Wrap(err, "can't get meta") + return errors.Wrap(err, "can't get meta") } if err := json.NewEncoder(w).Encode(meta); err != nil { - return 0, errors.Wrap(err, "can't encode meta") + return errors.Wrap(err, "can't encode meta") } if _, err := fmt.Fprintf(w, "%s\n", footer); err != nil { - return 0, errors.Wrap(err, "can't write footer") + return errors.Wrap(err, "can't write footer") } - - return commentsCount, nil + return nil } // Import comments from json strings produced by Remark.Export diff --git a/backend/app/rest/api/rest.go b/backend/app/rest/api/rest.go index 2e32e69b..74ba5afe 100644 --- a/backend/app/rest/api/rest.go +++ b/backend/app/rest/api/rest.go @@ -142,7 +142,7 @@ func (s *Rest) Shutdown() { s.lock.Unlock() } -func (s *Rest) makeHTTPServer(port int, router chi.Router) *http.Server { +func (s *Rest) makeHTTPServer(port int, router http.Handler) *http.Server { return &http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: router, diff --git a/backend/app/rest/api/ssl.go b/backend/app/rest/api/ssl.go index a087a613..14a4b728 100644 --- a/backend/app/rest/api/ssl.go +++ b/backend/app/rest/api/ssl.go @@ -82,7 +82,7 @@ func (s *Rest) makeAutocertManager() *autocert.Manager { } // makeHTTPSAutoCertServer makes https server with autocert mode (LE support) -func (s *Rest) makeHTTPSAutocertServer(port int, router chi.Router, m *autocert.Manager) *http.Server { +func (s *Rest) makeHTTPSAutocertServer(port int, router http.Handler, m *autocert.Manager) *http.Server { server := s.makeHTTPServer(port, router) cfg := makeTLSConfig() cfg.GetCertificate = m.GetCertificate @@ -91,7 +91,7 @@ func (s *Rest) makeHTTPSAutocertServer(port int, router chi.Router, m *autocert. } // makeHTTPSServer makes https server for static mode -func (s *Rest) makeHTTPSServer(port int, router chi.Router) *http.Server { +func (s *Rest) makeHTTPSServer(port int, router http.Handler) *http.Server { server := s.makeHTTPServer(port, router) server.TLSConfig = makeTLSConfig() return server From 616e4e2c1c9b6d2d9ebe030e09dbfe4bc0453a74 Mon Sep 17 00:00:00 2001 From: Umputun Date: Fri, 21 Dec 2018 02:21:58 -0600 Subject: [PATCH 08/14] sort metas for consistency --- backend/app/migrator/remark_test.go | 16 ++++++++-------- backend/app/store/service/service.go | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/backend/app/migrator/remark_test.go b/backend/app/migrator/remark_test.go index 1439fa6a..9d759094 100644 --- a/backend/app/migrator/remark_test.go +++ b/backend/app/migrator/remark_test.go @@ -24,8 +24,8 @@ func TestRemark_Export(t *testing.T) { defer os.Remove(testDb) b := prep(t) // write 2 comments b.SetReadOnly(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, true) - b.SetBlock("radio-t", "user-2", true, time.Hour) - b.SetVerified("radio-t", "user-1", true) + b.SetVerified("radio-t", "user1", true) + b.SetBlock("radio-t", "user2", true, time.Hour) r := Remark{DataStore: b} buf := &bytes.Buffer{} @@ -51,12 +51,12 @@ func TestRemark_Export(t *testing.T) { assert.Equal(t, "some text, link", res.Comments[0].Text) assert.Equal(t, 2, len(res.Meta.Users)) - assert.Equal(t, "user-2", res.Meta.Users[0].ID) - assert.Equal(t, true, res.Meta.Users[0].Blocked.Status) - assert.Equal(t, false, res.Meta.Users[0].Verified) - assert.Equal(t, "user-1", res.Meta.Users[1].ID) - assert.Equal(t, false, res.Meta.Users[1].Blocked.Status) - assert.Equal(t, true, res.Meta.Users[1].Verified) + assert.Equal(t, "user1", res.Meta.Users[0].ID) + assert.Equal(t, false, res.Meta.Users[0].Blocked.Status) + assert.Equal(t, true, res.Meta.Users[0].Verified) + assert.Equal(t, "user2", res.Meta.Users[1].ID) + assert.Equal(t, true, res.Meta.Users[1].Blocked.Status) + assert.Equal(t, false, res.Meta.Users[1].Verified) assert.Equal(t, 1, len(res.Meta.Posts)) assert.Equal(t, "https://radio-t.com", res.Meta.Posts[0].URL) diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index 7f3e6f47..eda24d0e 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -1,6 +1,7 @@ package service import ( + "sort" "sync" "time" @@ -273,6 +274,7 @@ func (s *DataStore) Metas(siteID string) (umetas []UserMetaData, pmetas []PostMe for _, u := range m { umetas = append(umetas, u) } + sort.Slice(umetas, func(i, j int) bool { return umetas[i].ID < umetas[j].ID }) return umetas, pmetas, nil } From 094f4c66a1b6d76b633bc41d0837b06736e29449 Mon Sep 17 00:00:00 2001 From: Umputun Date: Fri, 21 Dec 2018 20:01:03 -0600 Subject: [PATCH 09/14] replace confusing name for native import/export provider --- backend/app/cmd/server.go | 8 ++++---- backend/app/migrator/migrator.go | 2 +- backend/app/migrator/{remark.go => native.go} | 10 +++++----- .../app/migrator/{remark_test.go => native_test.go} | 12 ++++++------ backend/app/rest/api/migrator.go | 4 ++-- backend/app/rest/api/migrator_test.go | 4 ++-- backend/app/rest/api/rest_test.go | 6 +++--- 7 files changed, 23 insertions(+), 23 deletions(-) rename backend/app/migrator/{remark.go => native.go} (92%) rename backend/app/migrator/{remark_test.go => native_test.go} (94%) diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go index 77f980f7..025e9c03 100644 --- a/backend/app/cmd/server.go +++ b/backend/app/cmd/server.go @@ -12,7 +12,7 @@ import ( "syscall" "time" - "github.com/coreos/bbolt" + bolt "github.com/coreos/bbolt" "github.com/go-pkgz/mongo" "github.com/pkg/errors" @@ -230,14 +230,14 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) { RemarkURL: strings.TrimSuffix(s.RemarkURL, "/"), } - exporter := &migrator.Remark{DataStore: dataService} + exporter := &migrator.Native{DataStore: dataService} migr := &api.Migrator{ Cache: loadingCache, - NativeImporter: &migrator.Remark{DataStore: dataService}, + NativeImporter: &migrator.Native{DataStore: dataService}, DisqusImporter: &migrator.Disqus{DataStore: dataService}, WordPressImporter: &migrator.WordPress{DataStore: dataService}, - NativeExported: &migrator.Remark{DataStore: dataService}, + NativeExporter: &migrator.Native{DataStore: dataService}, KeyStore: adminStore, } diff --git a/backend/app/migrator/migrator.go b/backend/app/migrator/migrator.go index 4ff7d520..18569a5c 100644 --- a/backend/app/migrator/migrator.go +++ b/backend/app/migrator/migrator.go @@ -52,7 +52,7 @@ func ImportComments(p ImportParams) (int, error) { case "wordpress": importer = &WordPress{DataStore: p.DataStore} case "native": - importer = &Remark{DataStore: p.DataStore} + importer = &Native{DataStore: p.DataStore} default: return 0, errors.Errorf("unsupported import provider %s", p.Provider) } diff --git a/backend/app/migrator/remark.go b/backend/app/migrator/native.go similarity index 92% rename from backend/app/migrator/remark.go rename to backend/app/migrator/native.go index b2964c75..b242cf17 100644 --- a/backend/app/migrator/remark.go +++ b/backend/app/migrator/native.go @@ -20,14 +20,14 @@ const ( footer = `}` ) -// Remark implements exporter and importer for internal store format +// Native implements exporter and importer for internal store format // {"version": 1, comments:[{...}\n,{}], meta: {meta}} -type Remark struct { +type Native struct { DataStore Store } // Export all comments to writer as json strings. Each comment is one string, separated by "\n" -func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { +func (r *Native) Export(w io.Writer, siteID string) (size int, err error) { if _, err = fmt.Fprintf(w, "%s\n", header); err != nil { return 0, err @@ -79,7 +79,7 @@ func (r *Remark) Export(w io.Writer, siteID string) (size int, err error) { return commentsCount, err } -func (r *Remark) exportMeta(siteID string, w io.Writer) (err error) { +func (r *Native) exportMeta(siteID string, w io.Writer) (err error) { if _, err = fmt.Fprintf(w, "%s", metaHeader); err != nil { return errors.Wrap(err, "can't write meta header") } @@ -105,7 +105,7 @@ func (r *Remark) exportMeta(siteID string, w io.Writer) (err error) { } // Import comments from json strings produced by Remark.Export -func (r *Remark) Import(reader io.Reader, siteID string) (size int, err error) { +func (r *Native) Import(reader io.Reader, siteID string) (size int, err error) { if err := r.DataStore.DeleteAll(siteID); err != nil { return 0, err diff --git a/backend/app/migrator/remark_test.go b/backend/app/migrator/native_test.go similarity index 94% rename from backend/app/migrator/remark_test.go rename to backend/app/migrator/native_test.go index 9d759094..dc3ac555 100644 --- a/backend/app/migrator/remark_test.go +++ b/backend/app/migrator/native_test.go @@ -20,13 +20,13 @@ import ( var testDb = "/tmp/test-remark.db" -func TestRemark_Export(t *testing.T) { +func TestNative_Export(t *testing.T) { defer os.Remove(testDb) b := prep(t) // write 2 comments b.SetReadOnly(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, true) b.SetVerified("radio-t", "user1", true) b.SetBlock("radio-t", "user2", true, time.Hour) - r := Remark{DataStore: b} + r := Native{DataStore: b} buf := &bytes.Buffer{} size, err := r.Export(buf, "radio-t") @@ -63,7 +63,7 @@ func TestRemark_Export(t *testing.T) { assert.Equal(t, true, res.Meta.Posts[0].ReadOnly) } -func TestRemark_Import(t *testing.T) { +func TestNative_Import(t *testing.T) { defer os.Remove(testDb) r1 := `{"id":"efbc17f177ee1a1c0ee6e1e025749966ec071adc","pid":"","text":"some text, link","user":{"name":"user name","id":"user1","picture":"","profile":"","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:22-06:00"}` + "\n" @@ -76,7 +76,7 @@ func TestRemark_Import(t *testing.T) { buf.WriteString("{}") b := prep(t) // write some recs - r := Remark{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}} + r := Native{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}} size, err := r.Import(buf, "radio-t") assert.Nil(t, err) assert.Equal(t, 2, size) @@ -97,7 +97,7 @@ func TestRemark_Import(t *testing.T) { assert.Equal(t, 2, size) } -func TestRemark_ImportManyWithError(t *testing.T) { +func TestNative_ImportManyWithError(t *testing.T) { defer os.Remove(testDb) goodRec := `{"id":"%d","pid":"","text":"some text, link","user":{"name":"user name","id":"user1","picture":"","profile":"","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:22-06:00"}` + "\n" @@ -110,7 +110,7 @@ func TestRemark_ImportManyWithError(t *testing.T) { buf.WriteString("bad2\n") b := prep(t) // write some recs - r := Remark{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}} + r := Native{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}} n, err := r.Import(buf, "radio-t") assert.EqualError(t, err, "failed to save 2 comments") assert.Equal(t, 1200, n) diff --git a/backend/app/rest/api/migrator.go b/backend/app/rest/api/migrator.go index 2c489023..bcf5fc9f 100644 --- a/backend/app/rest/api/migrator.go +++ b/backend/app/rest/api/migrator.go @@ -27,7 +27,7 @@ type Migrator struct { NativeImporter migrator.Importer DisqusImporter migrator.Importer WordPressImporter migrator.Importer - NativeExported migrator.Exporter + NativeExporter migrator.Exporter KeyStore KeyStore busy map[string]bool @@ -153,7 +153,7 @@ func (m *Migrator) exportCtrl(w http.ResponseWriter, r *http.Request) { writer = gzWriter } - if _, err := m.NativeExported.Export(writer, siteID); err != nil { + if _, err := m.NativeExporter.Export(writer, siteID); err != nil { rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "export failed") return } diff --git a/backend/app/rest/api/migrator_test.go b/backend/app/rest/api/migrator_test.go index 0d659c0e..da860b25 100644 --- a/backend/app/rest/api/migrator_test.go +++ b/backend/app/rest/api/migrator_test.go @@ -267,8 +267,8 @@ func prepImportSrv(t *testing.T) (svc *Migrator, ds *service.DataStore, ts *http svc = &Migrator{ DisqusImporter: &migrator.Disqus{DataStore: dataStore}, WordPressImporter: &migrator.WordPress{DataStore: dataStore}, - NativeImporter: &migrator.Remark{DataStore: dataStore}, - NativeExported: &migrator.Remark{DataStore: dataStore}, + NativeImporter: &migrator.Native{DataStore: dataStore}, + NativeExporter: &migrator.Native{DataStore: dataStore}, Cache: &cache.Nop{}, KeyStore: adminStore, } diff --git a/backend/app/rest/api/rest_test.go b/backend/app/rest/api/rest_test.go index ebf563e5..a1146651 100644 --- a/backend/app/rest/api/rest_test.go +++ b/backend/app/rest/api/rest_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/coreos/bbolt" + bolt "github.com/coreos/bbolt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -209,8 +209,8 @@ func prep(t *testing.T) (srv *Rest, ts *httptest.Server) { Migrator: &Migrator{ DisqusImporter: &migrator.Disqus{DataStore: dataStore}, WordPressImporter: &migrator.WordPress{DataStore: dataStore}, - NativeImporter: &migrator.Remark{DataStore: dataStore}, - NativeExported: &migrator.Remark{DataStore: dataStore}, + NativeImporter: &migrator.Native{DataStore: dataStore}, + NativeExporter: &migrator.Native{DataStore: dataStore}, Cache: &cache.Nop{}, KeyStore: adminStore, }, From be3d1bdf3ee58f979a913ccfde37020df7791054 Mon Sep 17 00:00:00 2001 From: Umputun Date: Sun, 23 Dec 2018 02:23:54 -0600 Subject: [PATCH 10/14] simplify import & export --- backend/app/migrator/migrator.go | 1 + backend/app/migrator/native.go | 109 ++++++++++++--------------- backend/app/migrator/native_test.go | 90 +++++++++++----------- backend/app/store/service/service.go | 26 +++++++ 4 files changed, 121 insertions(+), 105 deletions(-) diff --git a/backend/app/migrator/migrator.go b/backend/app/migrator/migrator.go index 18569a5c..2737c278 100644 --- a/backend/app/migrator/migrator.go +++ b/backend/app/migrator/migrator.go @@ -31,6 +31,7 @@ type Store interface { List(siteID string, limit int, skip int) ([]store.PostInfo, error) DeleteAll(siteID string) error Metas(siteID string) (umetas []service.UserMetaData, pmetas []service.PostMetaData, err error) + SetMetas(siteID string, umetas []service.UserMetaData, pmetas []service.PostMetaData) error } // ImportParams defines everything needed to run import diff --git a/backend/app/migrator/native.go b/backend/app/migrator/native.go index b242cf17..05e73ce5 100644 --- a/backend/app/migrator/native.go +++ b/backend/app/migrator/native.go @@ -1,10 +1,8 @@ package migrator import ( - "bufio" "bytes" "encoding/json" - "fmt" "io" "log" @@ -14,26 +12,28 @@ import ( "github.com/umputun/remark/backend/app/store/service" ) -const ( - header = `{"version":1, "comments":[` - metaHeader = "],\n\"meta\":" - footer = `}` -) - // Native implements exporter and importer for internal store format // {"version": 1, comments:[{...}\n,{}], meta: {meta}} +// each comments starts from the new line type Native struct { DataStore Store } -// Export all comments to writer as json strings. Each comment is one string, separated by "\n" -func (r *Native) Export(w io.Writer, siteID string) (size int, err error) { +type meta struct { + Version int `json:"version"` + Users []service.UserMetaData `json:"users"` + Posts []service.PostMetaData `json:"posts"` +} - if _, err = fmt.Fprintf(w, "%s\n", header); err != nil { - return 0, err +// Export all comments to writer as json strings. Each comment is one string, separated by "\n" +// The final file is a valid json +func (n *Native) Export(w io.Writer, siteID string) (size int, err error) { + + if err = n.exportMeta(siteID, w); err != nil { + return 0, errors.Wrapf(err, "failed to export meta for site %s", siteID) } - topics, err := r.DataStore.List(siteID, 0, 0) + topics, err := n.DataStore.List(siteID, 0, 0) if err != nil { return 0, err } @@ -42,12 +42,12 @@ func (r *Native) Export(w io.Writer, siteID string) (size int, err error) { commentsCount := 0 for i := len(topics) - 1; i >= 0; i-- { // topics from List sorted in opposite direction topic := topics[i] - comments, e := r.DataStore.Find(store.Locator{SiteID: siteID, URL: topic.URL}, "time") + comments, e := n.DataStore.Find(store.Locator{SiteID: siteID, URL: topic.URL}, "time") if err != nil { return commentsCount, e } - for n, comment := range comments { + for _, comment := range comments { buf := &bytes.Buffer{} enc := json.NewEncoder(buf) @@ -56,77 +56,61 @@ func (r *Native) Export(w io.Writer, siteID string) (size int, err error) { if err = enc.Encode(comment); err != nil { return commentsCount, errors.Wrapf(err, "can't marshal %v", comments) } - data := buf.Bytes() - data = bytes.TrimSuffix(data, []byte("\n")) - if _, err = w.Write(data); err != nil { + if _, err = w.Write(buf.Bytes()); err != nil { return commentsCount, errors.Wrap(err, "can't write comment data") } - if n < len(comments)-1 || i != 0 { // don't add , on last comment - if _, err = w.Write([]byte(",")); err != nil { - return commentsCount, errors.Wrap(err, "can't write comment separator") - } - } - - if _, err = w.Write([]byte("\n")); err != nil { - return commentsCount, errors.Wrap(err, "can't write comment eol") - } - commentsCount++ } } log.Printf("[DEBUG] exported %d comments", commentsCount) - err = r.exportMeta(siteID, w) - return commentsCount, err + return commentsCount, nil } -func (r *Native) exportMeta(siteID string, w io.Writer) (err error) { - if _, err = fmt.Fprintf(w, "%s", metaHeader); err != nil { - return errors.Wrap(err, "can't write meta header") - } - - meta := struct { - Users []service.UserMetaData `json:"users"` - Posts []service.PostMetaData `json:"posts"` - }{} - - meta.Users, meta.Posts, err = r.DataStore.Metas(siteID) +// exportMeta appends user and post metas to exported stream +func (n *Native) exportMeta(siteID string, w io.Writer) (err error) { + m := meta{Version: 1} + m.Users, m.Posts, err = n.DataStore.Metas(siteID) if err != nil { return errors.Wrap(err, "can't get meta") } - if err := json.NewEncoder(w).Encode(meta); err != nil { + if err := json.NewEncoder(w).Encode(m); err != nil { return errors.Wrap(err, "can't encode meta") } - - if _, err := fmt.Fprintf(w, "%s\n", footer); err != nil { - return errors.Wrap(err, "can't write footer") - } return nil } // Import comments from json strings produced by Remark.Export -func (r *Native) Import(reader io.Reader, siteID string) (size int, err error) { +func (n *Native) Import(reader io.Reader, siteID string) (size int, err error) { - if err := r.DataStore.DeleteAll(siteID); err != nil { + m := meta{} + dec := json.NewDecoder(reader) + if err = dec.Decode(&m); err != nil { + return 0, errors.Wrapf(err, "failed to import meta for site %s", siteID) + } + + if err := n.DataStore.DeleteAll(siteID); err != nil { return 0, err } failed := 0 total, comments := 0, 0 - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - rec := scanner.Bytes() - if len(rec) < 3 { - continue - } - total++ + + for { comment := store.Comment{} - if err := json.Unmarshal(rec, &comment); err != nil { + err = dec.Decode(&comment) + if err == io.EOF { + break + } + + total++ + + if err != nil { failed++ - log.Printf("[WARN] unmarshal failed for %s, %s", string(rec), err) continue } - if _, err := r.DataStore.Create(comment); err != nil { + + if _, err := n.DataStore.Create(comment); err != nil { failed++ log.Printf("[WARN] can't write %+v to store, %s", comment, err) continue @@ -136,12 +120,13 @@ func (r *Native) Import(reader io.Reader, siteID string) (size int, err error) { log.Printf("[DEBUG] imported %d comments", comments) } } - if scanner.Err() != nil { - return comments, errors.Wrap(scanner.Err(), "error in scan") - } + if failed > 0 { return comments, errors.Errorf("failed to save %d comments", failed) } log.Printf("[INFO] imported %d comments from %d records", comments, total) - return comments, nil + + err = n.DataStore.SetMetas(siteID, m.Users, m.Posts) + + return comments, err } diff --git a/backend/app/migrator/native_test.go b/backend/app/migrator/native_test.go index dc3ac555..8cadfdfa 100644 --- a/backend/app/migrator/native_test.go +++ b/backend/app/migrator/native_test.go @@ -6,11 +6,13 @@ import ( "fmt" "log" "os" + "strings" "testing" "time" bolt "github.com/coreos/bbolt" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/umputun/remark/backend/app/store" "github.com/umputun/remark/backend/app/store/admin" @@ -36,65 +38,66 @@ func TestNative_Export(t *testing.T) { c1 := buf.String() log.Print(c1) - res := struct { - Version int `json:"version"` - Comments []store.Comment `json:"comments"` - Meta struct { - Users []service.UserMetaData `json:"users"` - Posts []service.PostMetaData `json:"posts"` - } `json:"meta"` + dec := json.NewDecoder(strings.NewReader(c1)) + + meta := struct { + Version int `json:"version"` + Users []service.UserMetaData `json:"users"` + Posts []service.PostMetaData `json:"posts"` }{} - err = json.Unmarshal([]byte(c1), &res) - assert.NoError(t, err) - assert.Equal(t, 2, len(res.Comments)) - assert.Equal(t, "some text, link", res.Comments[0].Text) + require.NoError(t, dec.Decode(&meta), "decode meta") - assert.Equal(t, 2, len(res.Meta.Users)) - assert.Equal(t, "user1", res.Meta.Users[0].ID) - assert.Equal(t, false, res.Meta.Users[0].Blocked.Status) - assert.Equal(t, true, res.Meta.Users[0].Verified) - assert.Equal(t, "user2", res.Meta.Users[1].ID) - assert.Equal(t, true, res.Meta.Users[1].Blocked.Status) - assert.Equal(t, false, res.Meta.Users[1].Verified) + assert.Equal(t, 2, len(meta.Users)) + assert.Equal(t, "user1", meta.Users[0].ID) + assert.Equal(t, false, meta.Users[0].Blocked.Status) + assert.Equal(t, true, meta.Users[0].Verified) + assert.Equal(t, "user2", meta.Users[1].ID) + assert.Equal(t, true, meta.Users[1].Blocked.Status) + assert.Equal(t, false, meta.Users[1].Verified) - assert.Equal(t, 1, len(res.Meta.Posts)) - assert.Equal(t, "https://radio-t.com", res.Meta.Posts[0].URL) - assert.Equal(t, true, res.Meta.Posts[0].ReadOnly) + assert.Equal(t, 1, len(meta.Posts)) + assert.Equal(t, "https://radio-t.com", meta.Posts[0].URL) + assert.Equal(t, true, meta.Posts[0].ReadOnly) + + comments := [3]store.Comment{} + + assert.NoError(t, dec.Decode(&comments[0]), "decode comment 0") + assert.NoError(t, dec.Decode(&comments[1]), "decode comment 0") + assert.Error(t, dec.Decode(&comments[2]), "EOF") + + assert.Equal(t, "some text, link", comments[0].Text) } func TestNative_Import(t *testing.T) { defer os.Remove(testDb) - r1 := `{"id":"efbc17f177ee1a1c0ee6e1e025749966ec071adc","pid":"","text":"some text, link","user":{"name":"user name","id":"user1","picture":"","profile":"","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:22-06:00"}` + "\n" - - r2 := `{"id":"afbc17f177ee1a1c0ee6e1e025749966ec071adc","pid":"efbc17f177ee1a1c0ee6e1e025749966ec071adc","text":"some text2, link","user":{"name":"user name","id":"user1","picture":"","profile":"","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:23-06:00"}` + "\n" - - buf := &bytes.Buffer{} - buf.WriteString(r1) - buf.WriteString(r2) - buf.WriteString("{}") + inp := `{"version":1,"users":[{"id":"user1","blocked":{"status":false,"until":"0001-01-01T00:00:00Z"},"verified":true},{"id":"user2","blocked":{"status":true,"until":"2018-12-23T02:55:22.472041-06:00"},"verified":false}],"posts":[{"url":"https://radio-t.com","read_only":true}]} + {"id":"efbc17f177ee1a1c0ee6e1e025749966ec071adc","pid":"","text":"some text, link","user":{"name":"user name","id":"user1","picture":"","ip":"293ec5b0cf154855258824ec7fac5dc63d176915","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:22-06:00"} + {"id":"f863bd79-fec6-4a75-b308-61fe5dd02aa1","pid":"1234","text":"some text2","user":{"name":"user name","id":"user2","picture":"","ip":"293ec5b0cf154855258824ec7fac5dc63d176915","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com/2"},"score":0,"votes":{},"time":"2017-12-20T15:18:23-06:00"}` b := prep(t) // write some recs r := Native{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}} - size, err := r.Import(buf, "radio-t") + size, err := r.Import(strings.NewReader(inp), "radio-t") assert.Nil(t, err) assert.Equal(t, 2, size) - comments, err := b.Find(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"}, "time") + comments, err := b.Last("radio-t", 10) assert.Nil(t, err) assert.Equal(t, 2, len(comments)) - assert.Equal(t, "efbc17f177ee1a1c0ee6e1e025749966ec071adc", comments[0].ID) - assert.Equal(t, "afbc17f177ee1a1c0ee6e1e025749966ec071adc", comments[1].ID) - assert.Equal(t, "efbc17f177ee1a1c0ee6e1e025749966ec071adc", comments[1].ParentID) + assert.Equal(t, "f863bd79-fec6-4a75-b308-61fe5dd02aa1", comments[0].ID) + assert.Equal(t, "1234", comments[0].ParentID) + assert.Equal(t, false, b.IsReadOnly(comments[0].Locator)) - // try import again - buf.WriteString(r1) - buf.WriteString(r2) - buf.WriteString("{}") - size, err = r.Import(buf, "radio-t") - assert.Nil(t, err) - assert.Equal(t, 2, size) + assert.Equal(t, "efbc17f177ee1a1c0ee6e1e025749966ec071adc", comments[1].ID) + assert.Equal(t, "https://radio-t.com", comments[1].Locator.URL) + assert.Equal(t, true, b.IsReadOnly(comments[1].Locator)) + + assert.Equal(t, false, b.IsBlocked("radio-t", "user1")) + assert.Equal(t, true, b.IsVerified("radio-t", "user1")) + + assert.Equal(t, true, b.IsBlocked("radio-t", "user2")) + assert.Equal(t, false, b.IsVerified("radio-t", "user2")) } func TestNative_ImportManyWithError(t *testing.T) { @@ -103,11 +106,12 @@ func TestNative_ImportManyWithError(t *testing.T) { goodRec := `{"id":"%d","pid":"","text":"some text, link","user":{"name":"user name","id":"user1","picture":"","profile":"","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:22-06:00"}` + "\n" buf := &bytes.Buffer{} + buf.WriteString(`{"version":1, "users":[], "posts":[]}` + "\n") for i := 0; i < 1200; i++ { buf.WriteString(fmt.Sprintf(goodRec, i)) } - buf.WriteString("bad1\n") - buf.WriteString("bad2\n") + buf.WriteString("{}\n") + buf.WriteString("{}\n") b := prep(t) // write some recs r := Native{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}} diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index eda24d0e..56ab2f58 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -5,6 +5,8 @@ import ( "sync" "time" + multierror "github.com/hashicorp/go-multierror" + "github.com/google/uuid" "github.com/pkg/errors" @@ -279,6 +281,30 @@ func (s *DataStore) Metas(siteID string) (umetas []UserMetaData, pmetas []PostMe return umetas, pmetas, nil } +// SetMetas saves metadata for users and posts +func (s *DataStore) SetMetas(siteID string, umetas []UserMetaData, pmetas []PostMetaData) (err error) { + errs := new(multierror.Error) + + // save posts metas + for _, pm := range pmetas { + if pm.ReadOnly { + errs = multierror.Append(errs, s.SetReadOnly(store.Locator{SiteID: siteID, URL: pm.URL}, true)) + } + } + + // save users metas + for _, um := range umetas { + if um.Blocked.Status { + errs = multierror.Append(errs, s.SetBlock(siteID, um.ID, true, um.Blocked.Until.Sub(time.Now()))) + } + if um.Verified { + errs = multierror.Append(errs, s.SetVerified(siteID, um.ID, true)) + } + } + + return errs.ErrorOrNil() +} + // getsScopedLocks pull lock from the map if found or create a new one func (s *DataStore) getsScopedLocks(id string) (lock sync.Locker) { s.scopedLocks.Do(func() { s.scopedLocks.locks = map[string]sync.Locker{} }) From f3619b4fc3f9b0b5ff29da843b8b8d89e8588403 Mon Sep 17 00:00:00 2001 From: Umputun Date: Sun, 23 Dec 2018 03:01:44 -0600 Subject: [PATCH 11/14] adjust tests for v1 native format --- backend/app/migrator/migrator_test.go | 6 +++--- backend/app/migrator/native.go | 12 +++++++++--- backend/app/migrator/native_test.go | 12 ++++++++++++ backend/app/rest/api/admin_test.go | 4 ++-- backend/app/rest/api/migrator_test.go | 16 ++++++++-------- backend/app/store/service/service.go | 2 +- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/backend/app/migrator/migrator_test.go b/backend/app/migrator/migrator_test.go index 0ebcd09e..75bc40b5 100644 --- a/backend/app/migrator/migrator_test.go +++ b/backend/app/migrator/migrator_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "github.com/coreos/bbolt" + bolt "github.com/coreos/bbolt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -66,13 +66,13 @@ func TestMigrator_ImportWordPress(t *testing.T) { assert.Equal(t, 3, len(last), "3 comments imported") } -func TestMigrator_ImportRemark(t *testing.T) { +func TestMigrator_ImportNative(t *testing.T) { defer func() { os.Remove("/tmp/remark-test.db") os.Remove("/tmp/disqus-test.r42") }() - data := `{"id":"efbc17f177ee1a1c0ee6e1e025749966ec071adc","pid":"","text":"some text, link","user":{"name":"user name","id":"user1","picture":"","profile":"","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:22-06:00"}` + "\n" + + data := `{"version":1} {"id":"efbc17f177ee1a1c0ee6e1e025749966ec071adc","pid":"","text":"some text, link","user":{"name":"user name","id":"user1","picture":"","profile":"","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:22-06:00"}` + "\n" + `{"id":"afbc17f177ee1a1c0ee6e1e025749966ec071adc","pid":"efbc17f177ee1a1c0ee6e1e025749966ec071adc","text":"some text2, link","user":{"name":"user name","id":"user1","picture":"","profile":"","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:23-06:00"}` + "\n" err := ioutil.WriteFile("/tmp/disqus-test.r42", []byte(data), 0600) diff --git a/backend/app/migrator/native.go b/backend/app/migrator/native.go index 05e73ce5..abb06eb2 100644 --- a/backend/app/migrator/native.go +++ b/backend/app/migrator/native.go @@ -12,6 +12,8 @@ import ( "github.com/umputun/remark/backend/app/store/service" ) +const natvieVersion = 1 + // Native implements exporter and importer for internal store format // {"version": 1, comments:[{...}\n,{}], meta: {meta}} // each comments starts from the new line @@ -68,7 +70,7 @@ func (n *Native) Export(w io.Writer, siteID string) (size int, err error) { // exportMeta appends user and post metas to exported stream func (n *Native) exportMeta(siteID string, w io.Writer) (err error) { - m := meta{Version: 1} + m := meta{Version: natvieVersion} m.Users, m.Posts, err = n.DataStore.Metas(siteID) if err != nil { return errors.Wrap(err, "can't get meta") @@ -89,7 +91,11 @@ func (n *Native) Import(reader io.Reader, siteID string) (size int, err error) { return 0, errors.Wrapf(err, "failed to import meta for site %s", siteID) } - if err := n.DataStore.DeleteAll(siteID); err != nil { + if m.Version != natvieVersion { + return 0, errors.Errorf("unexpected import file version %d", m.Version) + } + + if err = n.DataStore.DeleteAll(siteID); err != nil { return 0, err } @@ -110,7 +116,7 @@ func (n *Native) Import(reader io.Reader, siteID string) (size int, err error) { continue } - if _, err := n.DataStore.Create(comment); err != nil { + if _, err = n.DataStore.Create(comment); err != nil { failed++ log.Printf("[WARN] can't write %+v to store, %s", comment, err) continue diff --git a/backend/app/migrator/native_test.go b/backend/app/migrator/native_test.go index 8cadfdfa..46ebcc36 100644 --- a/backend/app/migrator/native_test.go +++ b/backend/app/migrator/native_test.go @@ -100,6 +100,18 @@ func TestNative_Import(t *testing.T) { assert.Equal(t, false, b.IsVerified("radio-t", "user2")) } +func TestNative_ImportWrongVersion(t *testing.T) { + inp := `{"version":2,"users":[{"id":"user1","blocked":{"status":false,"until":"0001-01-01T00:00:00Z"},"verified":true},{"id":"user2","blocked":{"status":true,"until":"2018-12-23T02:55:22.472041-06:00"},"verified":false}],"posts":[{"url":"https://radio-t.com","read_only":true}]} + {"id":"efbc17f177ee1a1c0ee6e1e025749966ec071adc","pid":"","text":"some text, link","user":{"name":"user name","id":"user1","picture":"","ip":"293ec5b0cf154855258824ec7fac5dc63d176915","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com"},"score":0,"votes":{},"time":"2017-12-20T15:18:22-06:00"} + {"id":"f863bd79-fec6-4a75-b308-61fe5dd02aa1","pid":"1234","text":"some text2","user":{"name":"user name","id":"user2","picture":"","ip":"293ec5b0cf154855258824ec7fac5dc63d176915","admin":false},"locator":{"site":"radio-t","url":"https://radio-t.com/2"},"score":0,"votes":{},"time":"2017-12-20T15:18:23-06:00"}` + + b := prep(t) // write some recs + r := Native{DataStore: &service.DataStore{Interface: b, AdminStore: admin.NewStaticStore("12345", []string{}, "")}} + size, err := r.Import(strings.NewReader(inp), "radio-t") + assert.EqualError(t, err, "unexpected import file version 2") + assert.Equal(t, 0, size) + +} func TestNative_ImportManyWithError(t *testing.T) { defer os.Remove(testDb) diff --git a/backend/app/rest/api/admin_test.go b/backend/app/rest/api/admin_test.go index 24fed171..8258cadc 100644 --- a/backend/app/rest/api/admin_test.go +++ b/backend/app/rest/api/admin_test.go @@ -455,7 +455,7 @@ func TestAdmin_ExportStream(t *testing.T) { body, code := getWithAuth(t, ts.URL+"/api/v1/admin/export?site=radio-t&mode=stream") assert.Equal(t, 200, code) - assert.Equal(t, 6, strings.Count(body, "\n")) + assert.Equal(t, 3, strings.Count(body, "\n")) assert.Equal(t, 2, strings.Count(body, "\"text\"")) t.Logf("%s", body) } @@ -487,7 +487,7 @@ func TestAdmin_ExportFile(t *testing.T) { assert.NoError(t, err) ungzBody, err := ioutil.ReadAll(ungzReader) assert.NoError(t, err) - assert.Equal(t, 6, strings.Count(string(ungzBody), "\n")) + assert.Equal(t, 3, strings.Count(string(ungzBody), "\n")) assert.Equal(t, 2, strings.Count(string(ungzBody), "\"text\"")) t.Logf("%s", string(ungzBody)) } diff --git a/backend/app/rest/api/migrator_test.go b/backend/app/rest/api/migrator_test.go index da860b25..83fa652a 100644 --- a/backend/app/rest/api/migrator_test.go +++ b/backend/app/rest/api/migrator_test.go @@ -34,7 +34,7 @@ func TestMigrator_Import(t *testing.T) { assert.NotNil(t, srv) defer cleanupImportSrv(srv, ts) - r := strings.NewReader(`{"id":"2aa0478c-df1b-46b1-b561-03d507cf482c","pid":"","text":"

test test #1

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah1"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.849053725-05:00"} + r := strings.NewReader(`{"version":1} {"id":"2aa0478c-df1b-46b1-b561-03d507cf482c","pid":"","text":"

test test #1

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah1"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.849053725-05:00"} {"id":"83fd97fd-ff64-48d1-9fb7-ca7769c77037","pid":"p1","text":"

test test #2

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah2"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.861387771-05:00"}`) client := &http.Client{Timeout: 1 * time.Second} @@ -61,7 +61,7 @@ func TestMigrator_ImportForm(t *testing.T) { assert.NotNil(t, srv) defer cleanupImportSrv(srv, ts) - r := strings.NewReader(`{"id":"2aa0478c-df1b-46b1-b561-03d507cf482c","pid":"","text":"

test test #1

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah1"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.849053725-05:00"} + r := strings.NewReader(`{"version":1} {"id":"2aa0478c-df1b-46b1-b561-03d507cf482c","pid":"","text":"

test test #1

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah1"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.849053725-05:00"} {"id":"83fd97fd-ff64-48d1-9fb7-ca7769c77037","pid":"p1","text":"

test test #2

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah2"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.861387771-05:00"}`) bodyBuf := &bytes.Buffer{} @@ -136,7 +136,7 @@ func TestMigrator_ImportRejected(t *testing.T) { assert.NotNil(t, srv) defer cleanupImportSrv(srv, ts) - r := strings.NewReader(`{"id":"2aa0478c-df1b-46b1-b561-03d507cf482c","pid":"","text":"

test test #1

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah1"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.849053725-05:00"} + r := strings.NewReader(`{"version":1} {"id":"2aa0478c-df1b-46b1-b561-03d507cf482c","pid":"","text":"

test test #1

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah1"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.849053725-05:00"} {"id":"83fd97fd-ff64-48d1-9fb7-ca7769c77037","pid":"p1","text":"

test test #2

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah2"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.861387771-05:00"}`) client := &http.Client{Timeout: 1 * time.Second} @@ -157,7 +157,7 @@ func TestMigrator_ImportDouble(t *testing.T) { for i := 0; i < 1000; i++ { recs = append(recs, fmt.Sprintf(tmpl, i)) } - r := strings.NewReader(strings.Join(recs, "\n")) // reader with 10k records + r := strings.NewReader(`{"version":1}` + strings.Join(recs, "\n")) // reader with 10k records client := &http.Client{Timeout: 1 * time.Second} req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native&secret=123456", r) assert.Nil(t, err) @@ -184,7 +184,7 @@ func TestMigrator_ImportWaitExpired(t *testing.T) { for i := 0; i < 1000; i++ { recs = append(recs, fmt.Sprintf(tmpl, i)) } - r := strings.NewReader(strings.Join(recs, "\n")) // reader with 10k records + r := strings.NewReader(`{"version":1}` + strings.Join(recs, "\n")) // reader with 10k records client := &http.Client{Timeout: 1 * time.Second} req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native&secret=123456", r) require.Nil(t, err) @@ -205,7 +205,7 @@ func TestMigrator_Export(t *testing.T) { assert.NotNil(t, srv) defer cleanupImportSrv(srv, ts) - r := strings.NewReader(`{"id":"2aa0478c-df1b-46b1-b561-03d507cf482c","pid":"","text":"

test test #1

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah1"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.849053725-05:00"} + r := strings.NewReader(`{"version":1} {"id":"2aa0478c-df1b-46b1-b561-03d507cf482c","pid":"","text":"

test test #1

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah1"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.849053725-05:00"} {"id":"83fd97fd-ff64-48d1-9fb7-ca7769c77037","pid":"p1","text":"

test test #2

","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah2"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.861387771-05:00"}`) // import comments first @@ -234,7 +234,7 @@ func TestMigrator_Export(t *testing.T) { assert.NoError(t, err) ungzBody, err := ioutil.ReadAll(ungzReader) assert.NoError(t, err) - assert.Equal(t, 6, strings.Count(string(ungzBody), "\n")) + assert.Equal(t, 3, strings.Count(string(ungzBody), "\n")) assert.Equal(t, 2, strings.Count(string(ungzBody), "\"text\"")) t.Logf("%s", string(ungzBody)) @@ -248,7 +248,7 @@ func TestMigrator_Export(t *testing.T) { body, err := ioutil.ReadAll(resp.Body) assert.NoError(t, err) - assert.Equal(t, 6, strings.Count(string(body), "\n")) + assert.Equal(t, 3, strings.Count(string(body), "\n")) assert.Equal(t, 2, strings.Count(string(body), "\"text\"")) t.Logf("%s", string(body)) diff --git a/backend/app/store/service/service.go b/backend/app/store/service/service.go index 56ab2f58..6a5d94c3 100644 --- a/backend/app/store/service/service.go +++ b/backend/app/store/service/service.go @@ -295,7 +295,7 @@ func (s *DataStore) SetMetas(siteID string, umetas []UserMetaData, pmetas []Post // save users metas for _, um := range umetas { if um.Blocked.Status { - errs = multierror.Append(errs, s.SetBlock(siteID, um.ID, true, um.Blocked.Until.Sub(time.Now()))) + errs = multierror.Append(errs, s.SetBlock(siteID, um.ID, true, time.Until(um.Blocked.Until))) } if um.Verified { errs = multierror.Append(errs, s.SetVerified(siteID, um.ID, true)) From 74846496217d46a107c2ce741f534a0fe756626d Mon Sep 17 00:00:00 2001 From: Umputun Date: Sun, 23 Dec 2018 03:26:19 -0600 Subject: [PATCH 12/14] ass test for metas set --- backend/app/store/service/service_test.go | 26 ++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/backend/app/store/service/service_test.go b/backend/app/store/service/service_test.go index 9e0edd3a..257d5891 100644 --- a/backend/app/store/service/service_test.go +++ b/backend/app/store/service/service_test.go @@ -374,7 +374,7 @@ func TestService_Counts(t *testing.T) { }, res) } -func TestService_Metas(t *testing.T) { +func TestService_GetMetas(t *testing.T) { defer os.Remove(testDb) // two comments for https://radio-t.com b := DataStore{Interface: prepStoreEngine(t), EditDuration: 100 * time.Millisecond, @@ -401,6 +401,30 @@ func TestService_Metas(t *testing.T) { assert.Equal(t, true, pm[0].ReadOnly) } +func TestService_SetMetas(t *testing.T) { + defer os.Remove(testDb) + // two comments for https://radio-t.com + b := DataStore{Interface: prepStoreEngine(t), EditDuration: 100 * time.Millisecond, + AdminStore: admin.NewStaticKeyStore("secret 123")} + umetas := []UserMetaData{} + pmetas := []PostMetaData{} + err := b.SetMetas("radio-t", umetas, pmetas) + assert.NoError(t, err, "empty metas") + + um1 := UserMetaData{ID: "user1", Verified: true} + um2 := UserMetaData{ID: "user2"} + um2.Blocked.Status = true + um2.Blocked.Until = time.Now().AddDate(0, 1, 1) + + pmetas = []PostMetaData{{URL: "https://radio-t.com", ReadOnly: true}} + err = b.SetMetas("radio-t", []UserMetaData{um1, um2}, pmetas) + assert.NoError(t, err) + + assert.True(t, b.IsReadOnly(store.Locator{SiteID: "radio-t", URL: "https://radio-t.com"})) + assert.True(t, b.IsVerified("radio-t", "user1")) + assert.True(t, b.IsBlocked("radio-t", "user2")) +} + // makes new boltdb, put two records func prepStoreEngine(t *testing.T) engine.Interface { os.Remove(testDb) From b8ed722b2eb16d2107965e377f819eae8d7affe4 Mon Sep 17 00:00:00 2001 From: Umputun Date: Mon, 24 Dec 2018 00:19:12 -0600 Subject: [PATCH 13/14] increase wrtie timeout to allow long exports --- backend/app/cmd/backup.go | 2 +- backend/app/migrator/native.go | 2 +- backend/app/rest/api/rest.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/app/cmd/backup.go b/backend/app/cmd/backup.go index 3a41944e..584dab33 100644 --- a/backend/app/cmd/backup.go +++ b/backend/app/cmd/backup.go @@ -18,7 +18,7 @@ type BackupCommand struct { ExportPath string `short:"p" long:"path" env:"BACKUP_PATH" default:"./var/backup" description:"export path"` ExportFile string `short:"f" long:"file" default:"userbackup-{{.SITE}}-{{.TS}}.gz" description:"file name"` Site string `short:"s" long:"site" env:"SITE" default:"remark" description:"site name"` - Timeout time.Duration `long:"timeout" default:"15m" description:"import timeout"` + Timeout time.Duration `long:"timeout" default:"15m" description:"export (backup) timeout"` CommonOpts } diff --git a/backend/app/migrator/native.go b/backend/app/migrator/native.go index abb06eb2..79a5b342 100644 --- a/backend/app/migrator/native.go +++ b/backend/app/migrator/native.go @@ -91,7 +91,7 @@ func (n *Native) Import(reader io.Reader, siteID string) (size int, err error) { return 0, errors.Wrapf(err, "failed to import meta for site %s", siteID) } - if m.Version != natvieVersion { + if m.Version != natvieVersion && m.Version != 0 { // this version allows back compatibility with 0 version return 0, errors.Errorf("unexpected import file version %d", m.Version) } diff --git a/backend/app/rest/api/rest.go b/backend/app/rest/api/rest.go index 74ba5afe..37845468 100644 --- a/backend/app/rest/api/rest.go +++ b/backend/app/rest/api/rest.go @@ -147,7 +147,7 @@ func (s *Rest) makeHTTPServer(port int, router http.Handler) *http.Server { Addr: fmt.Sprintf(":%d", port), Handler: router, ReadHeaderTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, + WriteTimeout: 120 * time.Second, // TODO: such a long timeout needed for blocking export (backup) request IdleTimeout: 30 * time.Second, } } From 2aaae35717ace1a765a84b01e8e882083837f3bd Mon Sep 17 00:00:00 2001 From: Umputun Date: Mon, 24 Dec 2018 00:39:31 -0600 Subject: [PATCH 14/14] lint: lack to err checks in tests --- backend/app/migrator/native_test.go | 6 +++--- backend/app/store/engine/bolt_admin_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/app/migrator/native_test.go b/backend/app/migrator/native_test.go index 46ebcc36..9aeccd56 100644 --- a/backend/app/migrator/native_test.go +++ b/backend/app/migrator/native_test.go @@ -25,9 +25,9 @@ var testDb = "/tmp/test-remark.db" func TestNative_Export(t *testing.T) { defer os.Remove(testDb) b := prep(t) // write 2 comments - b.SetReadOnly(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, true) - b.SetVerified("radio-t", "user1", true) - b.SetBlock("radio-t", "user2", true, time.Hour) + assert.NoError(t, b.SetReadOnly(store.Locator{URL: "https://radio-t.com", SiteID: "radio-t"}, true)) + assert.NoError(t, b.SetVerified("radio-t", "user1", true)) + assert.NoError(t, b.SetBlock("radio-t", "user2", true, time.Hour)) r := Native{DataStore: b} buf := &bytes.Buffer{} diff --git a/backend/app/store/engine/bolt_admin_test.go b/backend/app/store/engine/bolt_admin_test.go index 82318ec6..d9bf0ffe 100644 --- a/backend/app/store/engine/bolt_admin_test.go +++ b/backend/app/store/engine/bolt_admin_test.go @@ -223,6 +223,6 @@ func TestBoltAdmin_Verified(t *testing.T) { assert.NoError(t, err) assert.Equal(t, []string{"u1", "u2"}, ids, "verified 2 ids") - ids, err = b.Verified("radio-t-bad") + _, err = b.Verified("radio-t-bad") assert.Error(t, err, "site \"radio-t-bad\" not found", "fail on wrong site") }