From 50cfd9923d5480032ced90c51c38001d08837787 Mon Sep 17 00:00:00 2001 From: Sung Won Cho Date: Sun, 26 Aug 2018 15:04:34 +1000 Subject: [PATCH] Use actions package and add view command (#95) * Use actions package * Add view command * Upgrade dependencies * Bump * Check update less frequently * Simplify doc --- COMMANDS.md | 107 +++++++---------- Gopkg.lock | 60 ++++++++-- README.md | 12 +- cmd/cat/cat.go | 20 ++-- cmd/ls/ls.go | 20 ++-- cmd/sync/sync.go | 9 +- cmd/view/view.go | 59 ++++++++++ core/action.go | 63 ++++------ core/core.go | 17 +-- core/reducer.go | 145 ++++++++++++++++------- core/reducer_test.go | 180 +++++++++++++++++++++++++---- infra/main.go | 1 + main.go | 2 + main_test.go | 67 +++++------ migrate/fixtures/6-post-dnote.json | 37 ++++++ migrate/fixtures/6-pre-dnote.json | 34 ++++++ migrate/migrate.go | 4 + migrate/migrate_test.go | 32 +++++ migrate/migrations.go | 53 +++++++++ migrate/snapshots.go | 26 +++++ testutils/main.go | 14 +++ upgrade/upgrade.go | 4 +- 22 files changed, 720 insertions(+), 246 deletions(-) create mode 100644 cmd/view/view.go create mode 100644 migrate/fixtures/6-post-dnote.json create mode 100644 migrate/fixtures/6-pre-dnote.json diff --git a/COMMANDS.md b/COMMANDS.md index e283e88d..b168e9b3 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -1,13 +1,12 @@ # Commands -* [add](#dnote-add) -* [edit](#dnote-edit) -* [remove](#dnote-remove) -* [ls](#dnote-ls) -* [cat](#dnote-cat) -* [upgrade](#dnote-upgrade) -* [login](#dnote-login) -* [sync](#dnote-sync) +- [add](#dnote-add) +- [view](#dnote-view) +- [edit](#dnote-edit) +- [remove](#dnote-remove) +- [upgrade](#dnote-upgrade) +- [login](#dnote-login) +- [sync](#dnote-sync) ## dnote add @@ -15,17 +14,31 @@ _alias: a, n, new_ Add a new note to a book. -### `dnote add [book name]` +```bash +# Launch a text editor to add a new note to the specified book. +$ dnote add linux -Launch a text editor to add a new note to the specified book. +# Write a new note with a content to the specified book. +$ dnote add linux -c "find - recursively walk the directory" +``` -### `dnote add [book name] -c "[content]"` +## dnote view -Write a new note with a content to the specified book. +_alias: v_ -e.g. +- List books or notes. +- View a note detail. - $ dnote add linux -c "find - recursively walk the directory" +```bash +# List all books. +$ dnote view + +# List all notes in a book. +$ dnote view golang + +# See details of a note +$ dnote view golang 12 +``` ## dnote edit @@ -33,17 +46,13 @@ _alias: e_ Edit a note -### `dnote edit [book name] [note index]` +```bash +# Launch a text editor to edit a note with the given index. +$ dnote edit linux 1 -Launch a text editor to edit a note with the given index. - -### `dnote edit [book name] [note index] -c "[note content]"` - -Edit a note with the given index in the specified book with a content. - -e.g - - $ dnote edit linux 1 "New Content" +# Edit a note with the given index in the specified book with a content. +$ dnote edit linux 1 "New Content" +``` ## dnote remove @@ -51,49 +60,13 @@ _alias: d_ Remove either a note or a book -### `dnote remove [book name] [index]` +```bash +# Remove the note with `index` in the specified book. +$ dnote remove JS 1 -Removes the note with `index` in the specified book. - -### `dnote remove -b [book name]` - -Removes the book with the `book name`. - -e.g - - $ dnote remove JS 1 - $ dnote remove -b JS - -## dnote ls - -_alias: l, notes_ - -List books or notes - -### `dnote ls` - -List all books. - -### `dnote ls [book name]` - -List all notes in the book. - -e.g - - $ dnote ls - $ dnote ls golang - -## dnote cat - -_alias: c_ - -See details of a note - -### `dnote cat [book name] [note index]` - -e.g - - $ dnote cat golang 12 +# Remove the book with the `book name`. +$ dnote remove -b JS +``` ## dnote upgrade @@ -103,6 +76,8 @@ Upgrade the Dnote if newer release is available _Dnote Cloud only_ +_alias: s_ + Sync notes with Dnote cloud ## dnote login diff --git a/Gopkg.lock b/Gopkg.lock index c0c692d3..55733507 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,80 +2,120 @@ [[projects]] + digest = "1:97242fd82dcd5574a59c31685652a4de519934674a9fa21159604fdd5a005e7c" + name = "github.com/dnote/actions" + packages = ["."] + pruneopts = "" + revision = "e646839669907194077733897c26ce2bb9856896" + version = "v0.1.0" + +[[projects]] + digest = "1:e988ed0ca0d81f4d28772760c02ee95084961311291bdfefc1b04617c178b722" name = "github.com/fatih/color" packages = ["."] - revision = "507f6050b8568533fb3f5504de8e5205fa62a114" - version = "v1.6.0" + pruneopts = "" + revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" + version = "v1.7.0" [[projects]] branch = "master" + digest = "1:9f100ae40cada79ca20c068dc8510ad2d8decc49d84f27f9a45892cef3504557" name = "github.com/google/go-github" packages = ["github"] - revision = "0c3b302de2a6de84a2511db47ea1bb2ff8146830" + pruneopts = "" + revision = "d7732128a00e8e95e8fe896017da18ee20b2180d" [[projects]] branch = "master" + digest = "1:9abc49f39e3e23e262594bb4fb70abf74c0c99e94f99153f43b143805e850719" name = "github.com/google/go-querystring" packages = ["query"] + pruneopts = "" revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a" [[projects]] + digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" name = "github.com/inconshreveable/mousetrap" packages = ["."] + pruneopts = "" revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" version = "v1.0" [[projects]] + digest = "1:9ea83adf8e96d6304f394d40436f2eb44c1dc3250d223b74088cc253a6cd0a1c" name = "github.com/mattn/go-colorable" packages = ["."] + pruneopts = "" revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" version = "v0.0.9" [[projects]] + digest = "1:78229b46ddb7434f881390029bd1af7661294af31f6802e0e1bedaad4ab0af3c" name = "github.com/mattn/go-isatty" packages = ["."] + pruneopts = "" revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" version = "v0.0.3" [[projects]] + digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] + digest = "1:6b55df4b0517a459af9d3879c99330af4367adcf45f3d0d37ded80a6272ae057" name = "github.com/satori/go.uuid" packages = ["."] + pruneopts = "" revision = "879c5887cd475cd7864858769793b2ceb0d44feb" version = "v1.1.0" [[projects]] + digest = "1:a1403cc8a94b8d7956ee5e9694badef0e7b051af289caad1cf668331e3ffa4f6" name = "github.com/spf13/cobra" packages = ["."] - revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" - version = "v0.0.1" + pruneopts = "" + revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" + version = "v0.0.3" [[projects]] + digest = "1:0a52bcb568386d98f4894575d53ce3e456f56471de6897bb8b9de13c33d9340e" name = "github.com/spf13/pflag" packages = ["."] - revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" - version = "v1.0.0" + pruneopts = "" + revision = "9a97c102cda95a86cec2345a6f09f55a939babf5" + version = "v1.0.2" [[projects]] branch = "master" + digest = "1:7a5f7a1206de6b90f67cb465e489eac3298e95afa7262813b542df4fab38952f" name = "golang.org/x/sys" packages = ["unix"] - revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd" + pruneopts = "" + revision = "4910a1d54f876d7b22162a85f4d066d3ee649450" [[projects]] branch = "v2" + digest = "1:f0620375dd1f6251d9973b5f2596228cc8042e887cd7f827e4220bc1ce8c30e2" name = "gopkg.in/yaml.v2" packages = ["."] - revision = "287cf08546ab5e7e37d55a84f7ed3fd1db036de5" + pruneopts = "" + revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "78f6d9536359b04aae8697ac22a2f0284223dea0c28a3c5287122dca041e0ef6" + input-imports = [ + "github.com/dnote/actions", + "github.com/fatih/color", + "github.com/google/go-github/github", + "github.com/pkg/errors", + "github.com/satori/go.uuid", + "github.com/spf13/cobra", + "gopkg.in/yaml.v2", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/README.md b/README.md index 66bb97ab..7aff8ab8 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ On Windows, download [binary](https://github.com/dnote/cli/releases). Write technical notes without getting distracted from programming. The reasons are: -* We forget exponentially unless we write down what we learn and come back. -* Ideas cannot be grokked unless we can put them down in clear words. +- We forget exponentially unless we write down what we learn and come back. +- Ideas cannot be grokked unless we can put them down in clear words. ## Examples @@ -34,7 +34,7 @@ $ dnote add linux -c "find - recursively walk the directory" - See the notes in a book ``` -$ dnote ls linux +$ dnote view linux • on book linux (0) find - recursively walk the directory ``` @@ -45,9 +45,9 @@ Please refer to [commands](/COMMANDS.md). ## Links -* [Dnote](https://dnote.io) -* [Dnote Cloud](https://dnote.io/cloud) -* [Browser Extension](https://github.com/dnote/browser-extension) +- [Dnote](https://dnote.io) +- [Dnote Cloud](https://dnote.io/cloud) +- [Browser Extension](https://github.com/dnote/browser-extension) ## License diff --git a/cmd/cat/cat.go b/cmd/cat/cat.go index 622f4e9d..3d5a7b97 100644 --- a/cmd/cat/cat.go +++ b/cmd/cat/cat.go @@ -17,6 +17,11 @@ var example = ` dnote cat javascript 2 ` +var deprecationWarning = `and "view" will replace it in v0.5.0. + + Run "dnote view --help" for more information. +` + func preRun(cmd *cobra.Command, args []string) error { if len(args) != 2 { return errors.New("Incorrect number of arguments") @@ -27,18 +32,19 @@ func preRun(cmd *cobra.Command, args []string) error { func NewCmd(ctx infra.DnoteCtx) *cobra.Command { cmd := &cobra.Command{ - Use: "cat ", - Aliases: []string{"c"}, - Short: "See a note", - Example: example, - RunE: newRun(ctx), - PreRunE: preRun, + Use: "cat ", + Aliases: []string{"c"}, + Short: "See a note", + Example: example, + RunE: NewRun(ctx), + PreRunE: preRun, + Deprecated: deprecationWarning, } return cmd } -func newRun(ctx infra.DnoteCtx) core.RunEFunc { +func NewRun(ctx infra.DnoteCtx) core.RunEFunc { return func(cmd *cobra.Command, args []string) error { dnote, err := core.GetDnote(ctx) if err != nil { diff --git a/cmd/ls/ls.go b/cmd/ls/ls.go index 6c4b3546..65a9f95b 100644 --- a/cmd/ls/ls.go +++ b/cmd/ls/ls.go @@ -20,6 +20,11 @@ var example = ` dnote ls javascript ` +var deprecationWarning = `and "view" will replace it in v0.5.0. + +Run "dnote view --help" for more information. +` + func preRun(cmd *cobra.Command, args []string) error { if len(args) > 1 { return errors.New("Incorrect number of argument") @@ -30,18 +35,19 @@ func preRun(cmd *cobra.Command, args []string) error { func NewCmd(ctx infra.DnoteCtx) *cobra.Command { cmd := &cobra.Command{ - Use: "ls ", - Aliases: []string{"l", "notes"}, - Short: "List all notes", - Example: example, - RunE: newRun(ctx), - PreRunE: preRun, + Use: "ls ", + Aliases: []string{"l", "notes"}, + Short: "List all notes", + Example: example, + RunE: NewRun(ctx), + PreRunE: preRun, + Deprecated: deprecationWarning, } return cmd } -func newRun(ctx infra.DnoteCtx) core.RunEFunc { +func NewRun(ctx infra.DnoteCtx) core.RunEFunc { return func(cmd *cobra.Command, args []string) error { dnote, err := core.GetDnote(ctx) if err != nil { diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index 3a056ceb..2a56176c 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net/http" + "github.com/dnote/actions" "github.com/dnote/cli/core" "github.com/dnote/cli/infra" "github.com/dnote/cli/log" @@ -32,8 +33,8 @@ func NewCmd(ctx infra.DnoteCtx) *cobra.Command { } type responseData struct { - Actions []core.Action `json:"actions"` - Bookmark int `json:"bookmark"` + Actions []actions.Action `json:"actions"` + Bookmark int `json:"bookmark"` } type syncPayload struct { @@ -120,7 +121,7 @@ func newRun(ctx infra.DnoteCtx) core.RunEFunc { } } -func getPayload(actions []core.Action, timestamp infra.Timestamp) (*bytes.Buffer, error) { +func getPayload(actions []actions.Action, timestamp infra.Timestamp) (*bytes.Buffer, error) { compressedActions, err := compressActions(actions) if err != nil { return &bytes.Buffer{}, errors.Wrap(err, "Failed to compress actions") @@ -140,7 +141,7 @@ func getPayload(actions []core.Action, timestamp infra.Timestamp) (*bytes.Buffer return ret, nil } -func compressActions(actions []core.Action) ([]byte, error) { +func compressActions(actions []actions.Action) ([]byte, error) { b, err := json.Marshal(&actions) if err != nil { return nil, errors.Wrap(err, "failed to marshal actions into JSON") diff --git a/cmd/view/view.go b/cmd/view/view.go new file mode 100644 index 00000000..aba68c64 --- /dev/null +++ b/cmd/view/view.go @@ -0,0 +1,59 @@ +package view + +import ( + "github.com/dnote/cli/core" + "github.com/dnote/cli/infra" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/dnote/cli/cmd/cat" + "github.com/dnote/cli/cmd/ls" +) + +var example = ` + * View all books + dnote view + + * List notes in a book + dnote view javascript + + * View a particular note in a book + dnote view javascript 0 + ` + +func preRun(cmd *cobra.Command, args []string) error { + if len(args) > 2 { + return errors.New("Incorrect number of argument") + } + + return nil +} + +func NewCmd(ctx infra.DnoteCtx) *cobra.Command { + cmd := &cobra.Command{ + Use: "view ", + Aliases: []string{"v"}, + Short: "List books, notes or view a content", + Example: example, + RunE: newRun(ctx), + PreRunE: preRun, + } + + return cmd +} + +func newRun(ctx infra.DnoteCtx) core.RunEFunc { + return func(cmd *cobra.Command, args []string) error { + var run core.RunEFunc + + if len(args) <= 1 { + run = ls.NewRun(ctx) + } else if len(args) == 2 { + run = cat.NewRun(ctx) + } else { + return errors.New("Incorrect number of arguments") + } + + return run(cmd, args) + } +} diff --git a/core/action.go b/core/action.go index 86efd454..e382464f 100644 --- a/core/action.go +++ b/core/action.go @@ -4,54 +4,41 @@ import ( "encoding/json" "time" + "github.com/dnote/actions" "github.com/dnote/cli/infra" "github.com/pkg/errors" "github.com/satori/go.uuid" ) -var ( - ActionAddNote = "add_note" - ActionRemoveNote = "remove_note" - ActionEditNote = "edit_note" - ActionAddBook = "add_book" - ActionRemoveBook = "remove_book" -) - -type Action struct { - UUID string `json:"uuid"` - Schema int `json:"schema"` - Type string `json:"type"` - Data json.RawMessage `json:"data"` - Timestamp int64 `json:"timestamp"` -} - func LogActionAddNote(ctx infra.DnoteCtx, noteUUID, bookName, content string, timestamp int64) error { - b, err := json.Marshal(AddNoteData{ + b, err := json.Marshal(actions.AddNoteDataV2{ NoteUUID: noteUUID, BookName: bookName, Content: content, + // TODO: support adding a public note + Public: false, }) if err != nil { return errors.Wrap(err, "Failed to marshal data into JSON") } - action := Action{ + action := actions.Action{ UUID: uuid.NewV4().String(), - Schema: 1, - Type: ActionAddNote, + Schema: 2, + Type: actions.ActionAddNote, Data: b, Timestamp: timestamp, } if err := LogAction(ctx, action); err != nil { - return errors.Wrapf(err, "Failed to log action type %s", ActionAddNote) + return errors.Wrapf(err, "Failed to log action type %s", actions.ActionAddNote) } return nil } func LogActionRemoveNote(ctx infra.DnoteCtx, noteUUID, bookName string) error { - b, err := json.Marshal(RemoveNoteData{ + b, err := json.Marshal(actions.RemoveNoteDataV1{ NoteUUID: noteUUID, BookName: bookName, }) @@ -59,23 +46,23 @@ func LogActionRemoveNote(ctx infra.DnoteCtx, noteUUID, bookName string) error { return errors.Wrap(err, "Failed to marshal data into JSON") } - action := Action{ + action := actions.Action{ UUID: uuid.NewV4().String(), Schema: 1, - Type: ActionRemoveNote, + Type: actions.ActionRemoveNote, Data: b, Timestamp: time.Now().Unix(), } if err := LogAction(ctx, action); err != nil { - return errors.Wrapf(err, "Failed to log action type %s", ActionRemoveNote) + return errors.Wrapf(err, "Failed to log action type %s", actions.ActionRemoveNote) } return nil } func LogActionEditNote(ctx infra.DnoteCtx, noteUUID, bookName, content string, ts int64) error { - b, err := json.Marshal(EditNoteData{ + b, err := json.Marshal(actions.EditNoteDataV1{ NoteUUID: noteUUID, FromBook: bookName, Content: content, @@ -84,60 +71,60 @@ func LogActionEditNote(ctx infra.DnoteCtx, noteUUID, bookName, content string, t return errors.Wrap(err, "Failed to marshal data into JSON") } - action := Action{ + action := actions.Action{ UUID: uuid.NewV4().String(), - Schema: 1, - Type: ActionEditNote, + Schema: 2, + Type: actions.ActionEditNote, Data: b, Timestamp: ts, } if err := LogAction(ctx, action); err != nil { - return errors.Wrapf(err, "Failed to log action type %s", ActionEditNote) + return errors.Wrapf(err, "Failed to log action type %s", actions.ActionEditNote) } return nil } func LogActionAddBook(ctx infra.DnoteCtx, name string) error { - b, err := json.Marshal(AddBookData{ + b, err := json.Marshal(actions.AddBookDataV1{ BookName: name, }) if err != nil { return errors.Wrap(err, "Failed to marshal data into JSON") } - action := Action{ + action := actions.Action{ UUID: uuid.NewV4().String(), Schema: 1, - Type: ActionAddBook, + Type: actions.ActionAddBook, Data: b, Timestamp: time.Now().Unix(), } if err := LogAction(ctx, action); err != nil { - return errors.Wrapf(err, "Failed to log action type %s", ActionAddBook) + return errors.Wrapf(err, "Failed to log action type %s", actions.ActionAddBook) } return nil } func LogActionRemoveBook(ctx infra.DnoteCtx, name string) error { - b, err := json.Marshal(RemoveBookData{BookName: name}) + b, err := json.Marshal(actions.RemoveBookDataV1{BookName: name}) if err != nil { return errors.Wrap(err, "Failed to marshal data into JSON") } - action := Action{ + action := actions.Action{ UUID: uuid.NewV4().String(), Schema: 1, - Type: ActionRemoveBook, + Type: actions.ActionRemoveBook, Data: b, Timestamp: time.Now().Unix(), } if err := LogAction(ctx, action); err != nil { - return errors.Wrapf(err, "Failed to log action type %s", ActionRemoveBook) + return errors.Wrapf(err, "Failed to log action type %s", actions.ActionRemoveBook) } return nil diff --git a/core/core.go b/core/core.go index e9eb4dec..48f20acc 100644 --- a/core/core.go +++ b/core/core.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/dnote/actions" "github.com/dnote/cli/infra" "github.com/dnote/cli/utils" "github.com/pkg/errors" @@ -18,7 +19,7 @@ import ( const ( // Version is the current version of dnote - Version = "0.3.1" + Version = "0.4.0" // TimestampFilename is the name of the file containing upgrade info TimestampFilename = "timestamps" @@ -67,7 +68,7 @@ func InitActionFile(ctx infra.DnoteCtx) error { return nil } - b, err := json.Marshal(&[]Action{}) + b, err := json.Marshal(&[]actions.Action{}) if err != nil { return errors.Wrap(err, "Failed to get initial action content") } @@ -277,7 +278,7 @@ func WriteConfig(ctx infra.DnoteCtx, config infra.Config) error { // LogAction appends the action to the action log and updates the last_action // timestamp -func LogAction(ctx infra.DnoteCtx, action Action) error { +func LogAction(ctx infra.DnoteCtx, action actions.Action) error { actions, err := ReadActionLog(ctx) if err != nil { return errors.Wrap(err, "Failed to read the action log") @@ -298,10 +299,10 @@ func LogAction(ctx infra.DnoteCtx, action Action) error { return nil } -func WriteActionLog(ctx infra.DnoteCtx, actions []Action) error { +func WriteActionLog(ctx infra.DnoteCtx, ats []actions.Action) error { path := GetActionPath(ctx) - d, err := json.Marshal(actions) + d, err := json.Marshal(ats) if err != nil { return errors.Wrap(err, "Failed to marshal newly generated actions to JSON") } @@ -315,7 +316,7 @@ func WriteActionLog(ctx infra.DnoteCtx, actions []Action) error { } func ClearActionLog(ctx infra.DnoteCtx) error { - var content []Action + var content []actions.Action if err := WriteActionLog(ctx, content); err != nil { return errors.Wrap(err, "Failed to write action log") @@ -336,8 +337,8 @@ func ReadActionLogContent(ctx infra.DnoteCtx) ([]byte, error) { } // ReadActionLog returns the action log content -func ReadActionLog(ctx infra.DnoteCtx) ([]Action, error) { - var ret []Action +func ReadActionLog(ctx infra.DnoteCtx) ([]actions.Action, error) { + var ret []actions.Action b, err := ReadActionLogContent(ctx) if err != nil { diff --git a/core/reducer.go b/core/reducer.go index 8b11b225..70f4ab42 100644 --- a/core/reducer.go +++ b/core/reducer.go @@ -4,39 +4,14 @@ import ( "encoding/json" "sort" + "github.com/dnote/actions" "github.com/dnote/cli/infra" "github.com/pkg/errors" ) -type AddNoteData struct { - NoteUUID string `json:"note_uuid"` - BookName string `json:"book_name"` - Content string `json:"content"` -} - -type EditNoteData struct { - NoteUUID string `json:"note_uuid"` - FromBook string `json:"from_book"` - ToBook string `json:"to_book"` - Content string `json:"content"` -} - -type RemoveNoteData struct { - NoteUUID string `json:"note_uuid"` - BookName string `json:"book_name"` -} - -type AddBookData struct { - BookName string `json:"book_name"` -} - -type RemoveBookData struct { - BookName string `json:"book_name"` -} - // ReduceAll reduces all actions -func ReduceAll(ctx infra.DnoteCtx, actions []Action) error { - for _, action := range actions { +func ReduceAll(ctx infra.DnoteCtx, ats []actions.Action) error { + for _, action := range ats { if err := Reduce(ctx, action); err != nil { return errors.Wrap(err, "Failed to reduce action") } @@ -47,19 +22,19 @@ func ReduceAll(ctx infra.DnoteCtx, actions []Action) error { // Reduce transitions the local dnote state by consuming the action returned // from the server -func Reduce(ctx infra.DnoteCtx, action Action) error { +func Reduce(ctx infra.DnoteCtx, action actions.Action) error { var err error switch action.Type { - case ActionAddNote: + case actions.ActionAddNote: err = handleAddNote(ctx, action) - case ActionRemoveNote: + case actions.ActionRemoveNote: err = handleRemoveNote(ctx, action) - case ActionEditNote: + case actions.ActionEditNote: err = handleEditNote(ctx, action) - case ActionAddBook: + case actions.ActionAddBook: err = handleAddBook(ctx, action) - case ActionRemoveBook: + case actions.ActionRemoveBook: err = handleRemoveBook(ctx, action) default: return errors.Errorf("Unsupported action %s", action.Type) @@ -72,8 +47,8 @@ func Reduce(ctx infra.DnoteCtx, action Action) error { return nil } -func handleAddNote(ctx infra.DnoteCtx, action Action) error { - var data AddNoteData +func handleAddNote(ctx infra.DnoteCtx, action actions.Action) error { + var data actions.AddNoteDataV1 err := json.Unmarshal(action.Data, &data) if err != nil { return errors.Wrap(err, "Failed to parse the action data") @@ -117,8 +92,8 @@ func handleAddNote(ctx infra.DnoteCtx, action Action) error { return nil } -func handleRemoveNote(ctx infra.DnoteCtx, action Action) error { - var data RemoveNoteData +func handleRemoveNote(ctx infra.DnoteCtx, action actions.Action) error { + var data actions.RemoveNoteDataV1 err := json.Unmarshal(action.Data, &data) if err != nil { return errors.Wrap(err, "Failed to parse the action data") @@ -146,8 +121,8 @@ func handleRemoveNote(ctx infra.DnoteCtx, action Action) error { return nil } -func handleEditNote(ctx infra.DnoteCtx, action Action) error { - var data EditNoteData +func handleEditNoteV1(ctx infra.DnoteCtx, action actions.Action) error { + var data actions.EditNoteDataV1 err := json.Unmarshal(action.Data, &data) if err != nil { return errors.Wrap(err, "Failed to parse the action data") @@ -205,8 +180,90 @@ func handleEditNote(ctx infra.DnoteCtx, action Action) error { return nil } -func handleAddBook(ctx infra.DnoteCtx, action Action) error { - var data AddBookData +func handleEditNoteV2(ctx infra.DnoteCtx, action actions.Action) error { + var data actions.EditNoteDataV2 + err := json.Unmarshal(action.Data, &data) + if err != nil { + return errors.Wrap(err, "Failed to parse the action data") + } + + dnote, err := GetDnote(ctx) + if err != nil { + return errors.Wrap(err, "Failed to get dnote") + } + + fromBook, ok := dnote[data.FromBook] + if !ok { + return errors.Errorf("Origin book with a name %s is not found", data.FromBook) + } + + if data.ToBook == nil { + for idx, note := range fromBook.Notes { + if note.UUID == data.NoteUUID { + if data.Content != nil { + note.Content = *data.Content + } + if data.Public != nil { + note.Public = *data.Public + } + + note.EditedOn = action.Timestamp + dnote[fromBook.Name].Notes[idx] = note + } + } + } else { + // Change the book + toBook := *data.ToBook + + dstBook, ok := dnote[toBook] + if !ok { + return errors.Errorf("Destination book with a name %s is not found", toBook) + } + + var index int + var note infra.Note + + // Find the note + for idx := range fromBook.Notes { + note = fromBook.Notes[idx] + + if note.UUID == data.NoteUUID { + index = idx + } + } + + if data.Content != nil { + note.Content = *data.Content + } + if data.Public != nil { + note.Public = *data.Public + } + note.EditedOn = action.Timestamp + + dnote[fromBook.Name] = GetUpdatedBook(dnote[fromBook.Name], append(fromBook.Notes[:index], fromBook.Notes[index+1:]...)) + dnote[toBook] = GetUpdatedBook(dnote[toBook], append(dstBook.Notes, note)) + } + + err = WriteDnote(ctx, dnote) + if err != nil { + return errors.Wrap(err, "Failed to write dnote") + } + + return nil +} + +func handleEditNote(ctx infra.DnoteCtx, action actions.Action) error { + if action.Schema == 1 { + return handleEditNoteV1(ctx, action) + } else if action.Schema == 2 { + return handleEditNoteV2(ctx, action) + } + + return errors.Errorf("Unsupported schema version for editing note: %d", action.Schema) +} + +func handleAddBook(ctx infra.DnoteCtx, action actions.Action) error { + var data actions.AddBookDataV1 err := json.Unmarshal(action.Data, &data) if err != nil { return errors.Wrap(err, "Failed to parse the action data") @@ -238,8 +295,8 @@ func handleAddBook(ctx infra.DnoteCtx, action Action) error { return nil } -func handleRemoveBook(ctx infra.DnoteCtx, action Action) error { - var data RemoveBookData +func handleRemoveBook(ctx infra.DnoteCtx, action actions.Action) error { + var data actions.RemoveBookDataV1 err := json.Unmarshal(action.Data, &data) if err != nil { return errors.Wrap(err, "Failed to parse the action data") diff --git a/core/reducer_test.go b/core/reducer_test.go index 6527f2ac..5b0dd1df 100644 --- a/core/reducer_test.go +++ b/core/reducer_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "testing" + "github.com/dnote/actions" "github.com/dnote/cli/testutils" "github.com/pkg/errors" ) @@ -17,13 +18,13 @@ func TestReduceAddNote(t *testing.T) { testutils.WriteFile(ctx, "../testutils/fixtures/dnote4.json", "dnote") // Execute - b, err := json.Marshal(&AddNoteData{ + b, err := json.Marshal(&actions.AddNoteDataV1{ Content: "new content", BookName: "js", NoteUUID: "06896551-8a06-4996-89cc-0d866308b0f6", }) - action := Action{ - Type: ActionAddNote, + action := actions.Action{ + Type: actions.ActionAddNote, Data: b, Timestamp: 1517629805, } @@ -60,13 +61,13 @@ func TestReduceAddNote_SortByAddedOn(t *testing.T) { testutils.WriteFile(ctx, "../testutils/fixtures/dnote3.json", "dnote") // Execute - b, err := json.Marshal(&AddNoteData{ + b, err := json.Marshal(&actions.AddNoteDataV1{ Content: "new content", BookName: "js", NoteUUID: "06896551-8a06-4996-89cc-0d866308b0f6", }) - action := Action{ - Type: ActionAddNote, + action := actions.Action{ + Type: actions.ActionAddNote, Data: b, Timestamp: 1515199944, } @@ -106,12 +107,12 @@ func TestReduceRemoveNote(t *testing.T) { testutils.WriteFile(ctx, "../testutils/fixtures/dnote3.json", "dnote") // Execute - b, err := json.Marshal(&RemoveNoteData{ + b, err := json.Marshal(&actions.RemoveNoteDataV1{ BookName: "js", NoteUUID: "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", }) - action := Action{ - Type: ActionRemoveNote, + action := actions.Action{ + Type: actions.ActionRemoveNote, Data: b, Timestamp: 1517629805, } @@ -146,14 +147,15 @@ func TestReduceEditNote(t *testing.T) { testutils.WriteFile(ctx, "../testutils/fixtures/dnote3.json", "dnote") // Execute - b, err := json.Marshal(&EditNoteData{ + b, err := json.Marshal(&actions.EditNoteDataV1{ FromBook: "js", NoteUUID: "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", Content: "updated content", }) - action := Action{ - Type: ActionEditNote, + action := actions.Action{ + Type: actions.ActionEditNote, Data: b, + Schema: 1, Timestamp: 1517629805, } err = Reduce(ctx, action) @@ -191,15 +193,16 @@ func TestReduceEditNote_changeBook(t *testing.T) { testutils.WriteFile(ctx, "../testutils/fixtures/dnote3.json", "dnote") // Execute - b, err := json.Marshal(&EditNoteData{ + b, err := json.Marshal(&actions.EditNoteDataV1{ FromBook: "js", ToBook: "linux", NoteUUID: "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", Content: "updated content", }) - action := Action{ - Type: ActionEditNote, + action := actions.Action{ + Type: actions.ActionEditNote, Data: b, + Schema: 1, Timestamp: 1517629805, } err = Reduce(ctx, action) @@ -233,6 +236,141 @@ func TestReduceEditNote_changeBook(t *testing.T) { testutils.AssertEqual(t, otherBook.Notes[1].EditedOn, int64(1517629805), "edited note edited_on mismatch") } +func TestReduceEditNote_V2_Content(t *testing.T) { + // Setup + ctx := testutils.InitCtx("../tmp") + + testutils.SetupTmp(ctx) + defer testutils.ClearTmp(ctx) + testutils.WriteFile(ctx, "../testutils/fixtures/dnote3.json", "dnote") + + // Execute + b := json.RawMessage(`{"note_uuid": "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "from_book": "js", "content": "updated content"}`) + + action := actions.Action{ + Type: actions.ActionEditNote, + Data: b, + Schema: 2, + Timestamp: 1517629805, + } + err := Reduce(ctx, action) + if err != nil { + t.Fatal(errors.Wrap(err, "Failed to process action")) + } + + // Test + dnote, err := GetDnote(ctx) + if err != nil { + t.Fatal(errors.Wrap(err, "Failed to get dnote")) + } + + targetBook := dnote["js"] + otherBook := dnote["linux"] + + testutils.AssertEqual(t, len(dnote), 2, "number of books mismatch") + testutils.AssertEqual(t, len(targetBook.Notes), 2, "target book notes length mismatch") + testutils.AssertEqual(t, targetBook.Notes[0].UUID, "43827b9a-c2b0-4c06-a290-97991c896653", "remaining note uuid mismatch") + testutils.AssertEqual(t, targetBook.Notes[0].Content, "Booleans have toString()", "remaining note content mismatch") + testutils.AssertEqual(t, targetBook.Notes[1].UUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "edited note uuid mismatch") + testutils.AssertEqual(t, targetBook.Notes[1].Content, "updated content", "edited note content mismatch") + testutils.AssertEqual(t, targetBook.Notes[1].EditedOn, int64(1517629805), "edited note edited_on mismatch") + testutils.AssertEqual(t, targetBook.Notes[1].Public, false, "edited note public mismatch") + testutils.AssertEqual(t, len(otherBook.Notes), 1, "other book notes length mismatch") + testutils.AssertEqual(t, otherBook.Notes[0].UUID, "3e065d55-6d47-42f2-a6bf-f5844130b2d2", "other book remaining note uuid mismatch") + testutils.AssertEqual(t, otherBook.Notes[0].Content, "wc -l to count words", "other book remaining note content mismatch") +} + +func TestReduceEditNote_V2_public(t *testing.T) { + // Setup + ctx := testutils.InitCtx("../tmp") + + testutils.SetupTmp(ctx) + defer testutils.ClearTmp(ctx) + testutils.WriteFile(ctx, "../testutils/fixtures/dnote3.json", "dnote") + + // Execute + b := json.RawMessage(`{"note_uuid": "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "from_book": "js", "public": true}`) + + action := actions.Action{ + Type: actions.ActionEditNote, + Data: b, + Schema: 2, + Timestamp: 1517629805, + } + err := Reduce(ctx, action) + if err != nil { + t.Fatal(errors.Wrap(err, "Failed to process action")) + } + + // Test + dnote, err := GetDnote(ctx) + if err != nil { + t.Fatal(errors.Wrap(err, "Failed to get dnote")) + } + + targetBook := dnote["js"] + otherBook := dnote["linux"] + + testutils.AssertEqual(t, len(dnote), 2, "number of books mismatch") + testutils.AssertEqual(t, len(targetBook.Notes), 2, "target book notes length mismatch") + testutils.AssertEqual(t, targetBook.Notes[0].UUID, "43827b9a-c2b0-4c06-a290-97991c896653", "remaining note uuid mismatch") + testutils.AssertEqual(t, targetBook.Notes[0].Content, "Booleans have toString()", "remaining note content mismatch") + testutils.AssertEqual(t, targetBook.Notes[1].UUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "edited note uuid mismatch") + testutils.AssertEqual(t, targetBook.Notes[1].Content, "Date object implements mathematical comparisons", "edited note content mismatch") + testutils.AssertEqual(t, targetBook.Notes[1].EditedOn, int64(1517629805), "edited note edited_on mismatch") + testutils.AssertEqual(t, targetBook.Notes[1].Public, true, "edited note public mismatch") + testutils.AssertEqual(t, len(otherBook.Notes), 1, "other book notes length mismatch") + testutils.AssertEqual(t, otherBook.Notes[0].UUID, "3e065d55-6d47-42f2-a6bf-f5844130b2d2", "other book remaining note uuid mismatch") + testutils.AssertEqual(t, otherBook.Notes[0].Content, "wc -l to count words", "other book remaining note content mismatch") +} + +func TestReduceEditNote_V2_changeBook(t *testing.T) { + // Setup + ctx := testutils.InitCtx("../tmp") + + testutils.SetupTmp(ctx) + defer testutils.ClearTmp(ctx) + testutils.WriteFile(ctx, "../testutils/fixtures/dnote3.json", "dnote") + + // Execute + b := json.RawMessage(`{"note_uuid": "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "from_book": "js", "to_book": "linux", "content": "updated content"}`) + action := actions.Action{ + Type: actions.ActionEditNote, + Data: b, + Schema: 2, + Timestamp: 1517629805, + } + err := Reduce(ctx, action) + if err != nil { + t.Fatal(errors.Wrap(err, "Failed to process action")) + } + + // Test + dnote, err := GetDnote(ctx) + if err != nil { + t.Fatal(errors.Wrap(err, "Failed to get dnote")) + } + + targetBook := dnote["js"] + otherBook := dnote["linux"] + + if len(targetBook.Notes) != 1 { + t.Fatalf("target book length mismatch. Got %d", len(targetBook.Notes)) + } + if len(otherBook.Notes) != 2 { + t.Fatalf("other book length mismatch. Got %d", len(targetBook.Notes)) + } + + testutils.AssertEqual(t, len(dnote), 2, "number of books mismatch") + testutils.AssertEqual(t, targetBook.Notes[0].UUID, "43827b9a-c2b0-4c06-a290-97991c896653", "remaining note uuid mismatch") + testutils.AssertEqual(t, targetBook.Notes[0].Content, "Booleans have toString()", "remaining note content mismatch") + testutils.AssertEqual(t, otherBook.Notes[0].UUID, "3e065d55-6d47-42f2-a6bf-f5844130b2d2", "other book remaining note uuid mismatch") + testutils.AssertEqual(t, otherBook.Notes[0].Content, "wc -l to count words", "other book remaining note content mismatch") + testutils.AssertEqual(t, otherBook.Notes[1].UUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "edited note uuid mismatch") + testutils.AssertEqual(t, otherBook.Notes[1].Content, "updated content", "edited note content mismatch") + testutils.AssertEqual(t, otherBook.Notes[1].EditedOn, int64(1517629805), "edited note edited_on mismatch") +} + func TestReduceAddBook(t *testing.T) { // Setup ctx := testutils.InitCtx("../tmp") @@ -242,9 +380,9 @@ func TestReduceAddBook(t *testing.T) { testutils.WriteFile(ctx, "../testutils/fixtures/dnote4.json", "dnote") // Execute - b, err := json.Marshal(&AddBookData{BookName: "new_book"}) - action := Action{ - Type: ActionAddBook, + b, err := json.Marshal(&actions.AddBookDataV1{BookName: "new_book"}) + action := actions.Action{ + Type: actions.ActionAddBook, Data: b, Timestamp: 1517629805, } @@ -274,9 +412,9 @@ func TestReduceRemoveBook(t *testing.T) { testutils.WriteFile(ctx, "../testutils/fixtures/dnote3.json", "dnote") // Execute - b, err := json.Marshal(&RemoveBookData{BookName: "linux"}) - action := Action{ - Type: ActionRemoveBook, + b, err := json.Marshal(&actions.RemoveBookDataV1{BookName: "linux"}) + action := actions.Action{ + Type: actions.ActionRemoveBook, Data: b, Timestamp: 1517629805, } diff --git a/infra/main.go b/infra/main.go index cfa9ee25..03f22a19 100644 --- a/infra/main.go +++ b/infra/main.go @@ -29,6 +29,7 @@ type Note struct { Content string `json:"content"` AddedOn int64 `json:"added_on"` EditedOn int64 `json:"edited_on"` + Public bool `json:"public"` } // Timestamp holds time information diff --git a/main.go b/main.go index c030af2e..eea765c5 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "github.com/dnote/cli/cmd/edit" "github.com/dnote/cli/cmd/login" "github.com/dnote/cli/cmd/ls" + "github.com/dnote/cli/cmd/view" "github.com/dnote/cli/cmd/remove" "github.com/dnote/cli/cmd/sync" "github.com/dnote/cli/cmd/upgrade" @@ -46,6 +47,7 @@ func main() { root.Register(version.NewCmd(ctx)) root.Register(upgrade.NewCmd(ctx)) root.Register(cat.NewCmd(ctx)) + root.Register(view.NewCmd(ctx)) if err := root.Execute(); err != nil { log.Errorf("%s\n", err.Error()) diff --git a/main_test.go b/main_test.go index 01becc16..ac074070 100644 --- a/main_test.go +++ b/main_test.go @@ -13,6 +13,7 @@ import ( "github.com/pkg/errors" + "github.com/dnote/actions" "github.com/dnote/cli/core" "github.com/dnote/cli/infra" "github.com/dnote/cli/testutils" @@ -97,22 +98,22 @@ func TestAdd_NewBook_ContentFlag(t *testing.T) { if err != nil { t.Fatal(errors.Wrap(err, "Failed to get dnote")) } - actions, err := core.ReadActionLog(ctx) + actionSlice, err := core.ReadActionLog(ctx) if err != nil { t.Fatal(errors.Wrap(err, "Failed to read actions")) } - if len(actions) != 2 { - t.Fatalf("action log length mismatch. got %d", len(actions)) + if len(actionSlice) != 2 { + t.Fatalf("action log length mismatch. got %d", len(actionSlice)) } book := dnote["js"] note := book.Notes[0] - bookAction := actions[0] - noteAction := actions[1] + bookAction := actionSlice[0] + noteAction := actionSlice[1] - var noteActionData core.AddNoteData - var bookActionData core.AddBookData + var noteActionData actions.AddNoteDataV1 + var bookActionData actions.AddBookDataV1 err = json.Unmarshal(bookAction.Data, &bookActionData) if err != nil { log.Fatalf("Failed to unmarshal the action data: %s", err) @@ -122,10 +123,10 @@ func TestAdd_NewBook_ContentFlag(t *testing.T) { log.Fatalf("Failed to unmarshal the action data: %s", err) } - testutils.AssertEqual(t, bookAction.Type, core.ActionAddBook, "bookAction type mismatch") + testutils.AssertEqual(t, bookAction.Type, actions.ActionAddBook, "bookAction type mismatch") testutils.AssertNotEqual(t, bookActionData.BookName, "", "bookAction data note_uuid mismatch") testutils.AssertNotEqual(t, bookAction.Timestamp, 0, "bookAction timestamp mismatch") - testutils.AssertEqual(t, noteAction.Type, core.ActionAddNote, "noteAction type mismatch") + testutils.AssertEqual(t, noteAction.Type, actions.ActionAddNote, "noteAction type mismatch") testutils.AssertEqual(t, noteActionData.Content, "foo", "noteAction data name mismatch") testutils.AssertNotEqual(t, noteActionData.NoteUUID, nil, "noteAction data note_uuid mismatch") testutils.AssertNotEqual(t, noteActionData.BookName, "", "noteAction data note_uuid mismatch") @@ -154,22 +155,22 @@ func TestAdd_ExistingBook_ContentFlag(t *testing.T) { if err != nil { t.Fatal(errors.Wrap(err, "Failed to get dnote")) } - actions, err := core.ReadActionLog(ctx) + actionSlice, err := core.ReadActionLog(ctx) if err != nil { t.Fatal(errors.Wrap(err, "Failed to read actions")) } book := dnote["js"] - action := actions[0] + action := actionSlice[0] - var actionData core.AddNoteData + var actionData actions.AddNoteDataV1 err = json.Unmarshal(action.Data, &actionData) if err != nil { log.Fatalf("Failed to unmarshal the action data: %s", err) } - testutils.AssertEqual(t, len(actions), 1, "There should be 1 action") - testutils.AssertEqual(t, action.Type, core.ActionAddNote, "action type mismatch") + testutils.AssertEqual(t, len(actionSlice), 1, "There should be 1 action") + testutils.AssertEqual(t, action.Type, actions.ActionAddNote, "action type mismatch") testutils.AssertEqual(t, actionData.Content, "foo", "action data name mismatch") testutils.AssertNotEqual(t, actionData.NoteUUID, "", "action data note_uuid mismatch") testutils.AssertEqual(t, actionData.BookName, "js", "action data book_name mismatch") @@ -199,22 +200,22 @@ func TestEdit_ContentFlag(t *testing.T) { if err != nil { t.Fatal(errors.Wrap(err, "Failed to get dnote")) } - actions, err := core.ReadActionLog(ctx) + actionSlice, err := core.ReadActionLog(ctx) if err != nil { t.Fatal(errors.Wrap(err, "Failed to read actions")) } book := dnote["js"] - action := actions[0] + action := actionSlice[0] - var actionData core.EditNoteData + var actionData actions.EditNoteDataV1 err = json.Unmarshal(action.Data, &actionData) if err != nil { log.Fatalf("Failed to unmarshal the action data: %s", err) } - testutils.AssertEqual(t, len(actions), 1, "There should be 1 action") - testutils.AssertEqual(t, action.Type, core.ActionEditNote, "action type mismatch") + testutils.AssertEqual(t, len(actionSlice), 1, "There should be 1 action") + testutils.AssertEqual(t, action.Type, actions.ActionEditNote, "action type mismatch") testutils.AssertEqual(t, actionData.Content, "foo bar", "action data name mismatch") testutils.AssertEqual(t, actionData.FromBook, "js", "action data from_book mismatch") testutils.AssertEqual(t, actionData.NoteUUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "action data note_uuis mismatch") @@ -270,27 +271,27 @@ func TestRemoveNote(t *testing.T) { if err != nil { t.Fatal(errors.Wrap(err, "Failed to get dnote")) } - actions, err := core.ReadActionLog(ctx) + actionSlice, err := core.ReadActionLog(ctx) if err != nil { t.Fatal(errors.Wrap(err, "Failed to read actions")) } - if len(actions) != 1 { - t.Fatalf("action log length mismatch. got %d", len(actions)) + if len(actionSlice) != 1 { + t.Fatalf("action log length mismatch. got %d", len(actionSlice)) } book := dnote["js"] otherBook := dnote["linux"] - action := actions[0] + action := actionSlice[0] - var actionData core.RemoveNoteData + var actionData actions.RemoveNoteDataV1 err = json.Unmarshal(action.Data, &actionData) if err != nil { log.Fatalf("Failed to unmarshal the action data: %s", err) } - testutils.AssertEqual(t, len(actions), 1, "There should be 1 action") - testutils.AssertEqual(t, action.Type, core.ActionRemoveNote, "action type mismatch") + testutils.AssertEqual(t, len(actionSlice), 1, "There should be 1 action") + testutils.AssertEqual(t, action.Type, actions.ActionRemoveNote, "action type mismatch") testutils.AssertEqual(t, actionData.NoteUUID, "f0d0fbb7-31ff-45ae-9f0f-4e429c0c797f", "action data note_uuid mismatch") testutils.AssertEqual(t, actionData.BookName, "js", "action data book_name mismatch") testutils.AssertNotEqual(t, action.Timestamp, 0, "action timestamp mismatch") @@ -343,26 +344,26 @@ func TestRemoveBook(t *testing.T) { if err != nil { t.Fatal(errors.Wrap(err, "Failed to get dnote")) } - actions, err := core.ReadActionLog(ctx) + actionSlice, err := core.ReadActionLog(ctx) if err != nil { t.Fatal(errors.Wrap(err, "Failed to read actions")) } - if len(actions) != 1 { - t.Fatalf("action log length mismatch. got %d", len(actions)) + if len(actionSlice) != 1 { + t.Fatalf("action log length mismatch. got %d", len(actionSlice)) } book := dnote["linux"] - action := actions[0] + action := actionSlice[0] - var actionData core.RemoveBookData + var actionData actions.RemoveBookDataV1 err = json.Unmarshal(action.Data, &actionData) if err != nil { log.Fatalf("Failed to unmarshal the action data: %s", err) } - testutils.AssertEqual(t, len(actions), 1, "There should be 1 action") - testutils.AssertEqual(t, action.Type, core.ActionRemoveBook, "action type mismatch") + testutils.AssertEqual(t, len(actionSlice), 1, "There should be 1 action") + testutils.AssertEqual(t, action.Type, actions.ActionRemoveBook, "action type mismatch") testutils.AssertEqual(t, actionData.BookName, "js", "action data name mismatch") testutils.AssertNotEqual(t, action.Timestamp, 0, "action timestamp mismatch") testutils.AssertEqual(t, len(dnote), 1, "There should be 1 book") diff --git a/migrate/fixtures/6-post-dnote.json b/migrate/fixtures/6-post-dnote.json new file mode 100644 index 00000000..a334e7ae --- /dev/null +++ b/migrate/fixtures/6-post-dnote.json @@ -0,0 +1,37 @@ +{ + "css": { + "name": "css", + "notes": [ + { + "uuid": "a7206776-cc2f-4439-995d-1d5c934c7758", + "content": "asdf", + "added_on": 1534888848, + "edited_on": 0, + "public": false + } + ] + }, + "js": { + "name": "js", + "notes": [ + { + "uuid": "d3eb7b64-cf51-48e3-935e-211be1af63fa", + "content": "blah", + "added_on": 1534888872, + "edited_on": 0, + "public": false + }, + { + "uuid": "16463315-1a89-4f82-ab25-06a384ef98b1", + "content": "blah blah edited", + "added_on": 1534888875, + "edited_on": 1534888884, + "public": false + } + ] + }, + "linux": { + "name": "linux", + "notes": [] + } +} diff --git a/migrate/fixtures/6-pre-dnote.json b/migrate/fixtures/6-pre-dnote.json new file mode 100644 index 00000000..10bbe06e --- /dev/null +++ b/migrate/fixtures/6-pre-dnote.json @@ -0,0 +1,34 @@ +{ + "css": { + "name": "css", + "notes": [ + { + "uuid": "a7206776-cc2f-4439-995d-1d5c934c7758", + "content": "asdf", + "added_on": 1534888848, + "edited_on": 0 + } + ] + }, + "js": { + "name": "js", + "notes": [ + { + "uuid": "d3eb7b64-cf51-48e3-935e-211be1af63fa", + "content": "blah", + "added_on": 1534888872, + "edited_on": 0 + }, + { + "uuid": "16463315-1a89-4f82-ab25-06a384ef98b1", + "content": "blah blah edited", + "added_on": 1534888875, + "edited_on": 1534888884 + } + ] + }, + "linux": { + "name": "linux", + "notes": [] + } +} diff --git a/migrate/migrate.go b/migrate/migrate.go index 51bd3818..c38aed1e 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -26,6 +26,7 @@ const ( migrationV3 migrationV4 migrationV5 + migrationV6 ) var migrationSequence = []int{ @@ -34,6 +35,7 @@ var migrationSequence = []int{ migrationV3, migrationV4, migrationV5, + migrationV6, } type schema struct { @@ -89,6 +91,8 @@ func performMigration(ctx infra.DnoteCtx, migrationID int) error { migrationError = migrateToV4(ctx) case migrationV5: migrationError = migrateToV5(ctx) + case migrationV6: + migrationError = migrateToV6(ctx) default: return errors.Errorf("Unrecognized migration id %d", migrationID) } diff --git a/migrate/migrate_test.go b/migrate/migrate_test.go index a0306bbe..1e2feb35 100644 --- a/migrate/migrate_test.go +++ b/migrate/migrate_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "testing" "github.com/dnote/cli/testutils" @@ -292,3 +293,34 @@ func TestMigrateToV5(t *testing.T) { } } } + +func TestMigrateToV6(t *testing.T) { + ctx := testutils.InitCtx("../tmp") + + // set up + testutils.SetupTmp(ctx) + testutils.WriteFile(ctx, "./fixtures/6-pre-dnote.json", "dnote") + defer testutils.ClearTmp(ctx) + + // execute + if err := migrateToV6(ctx); err != nil { + t.Fatal(errors.Wrap(err, "Failed to migrate").Error()) + } + + // test + b := testutils.ReadFile(ctx, "dnote") + var got migrateToV6PostDnote + if err := json.Unmarshal(b, &got); err != nil { + t.Fatal(errors.Wrap(err, "Failed to unmarshal the result into Dnote").Error()) + } + + b = testutils.ReadFileAbs("./fixtures/6-post-dnote.json") + var expected migrateToV6PostDnote + if err := json.Unmarshal(b, &expected); err != nil { + t.Fatal(errors.Wrap(err, "Failed to unmarshal the result into Dnote").Error()) + } + + if ok := reflect.DeepEqual(expected, got); !ok { + t.Errorf("Payload does not match.\nActual: %+v\nExpected: %+v", got, expected) + } +} diff --git a/migrate/migrations.go b/migrate/migrations.go index 38fd1b4d..abef759c 100644 --- a/migrate/migrations.go +++ b/migrate/migrations.go @@ -262,3 +262,56 @@ func migrateToV5(ctx infra.DnoteCtx) error { return nil } + +// migrateToV6 adds a 'public' field to notes +func migrateToV6(ctx infra.DnoteCtx) error { + notePath := fmt.Sprintf("%s/dnote", ctx.DnoteDir) + + b, err := ioutil.ReadFile(notePath) + if err != nil { + return errors.Wrap(err, "Failed to read the note file") + } + + var preDnote migrateToV6PreDnote + postDnote := migrateToV6PostDnote{} + + err = json.Unmarshal(b, &preDnote) + if err != nil { + return errors.Wrap(err, "Failed to unmarshal existing dnote into JSON") + } + + for bookName, book := range preDnote { + var notes = make([]migrateToV6PostNote, 0, len(book.Notes)) + public := false + for _, note := range book.Notes { + newNote := migrateToV6PostNote{ + UUID: note.UUID, + Content: note.Content, + AddedOn: note.AddedOn, + EditedOn: note.EditedOn, + Public: &public, + } + + notes = append(notes, newNote) + } + + b := migrateToV6PostBook{ + Name: bookName, + Notes: notes, + } + + postDnote[bookName] = b + } + + d, err := json.MarshalIndent(postDnote, "", " ") + if err != nil { + return errors.Wrap(err, "Failed to marshal new dnote into JSON") + } + + err = ioutil.WriteFile(notePath, d, 0644) + if err != nil { + return errors.Wrap(err, "Failed to write the new dnote into the file") + } + + return nil +} diff --git a/migrate/snapshots.go b/migrate/snapshots.go index 4ec40b98..537fb68f 100644 --- a/migrate/snapshots.go +++ b/migrate/snapshots.go @@ -104,3 +104,29 @@ var ( migrateToV5ActionAddBook = "add_book" migrateToV5ActionRemoveBook = "remove_book" ) + +// v6 +type migrateToV6PreNote struct { + UUID string `json:"uuid"` + Content string `json:"content"` + AddedOn int64 `json:"added_on"` + EditedOn int64 `json:"edited_on"` +} +type migrateToV6PostNote struct { + UUID string `json:"uuid"` + Content string `json:"content"` + AddedOn int64 `json:"added_on"` + EditedOn int64 `json:"edited_on"` + // Make a pointer to test absent values + Public *bool `json:"public"` +} +type migrateToV6PreBook struct { + Name string `json:"name"` + Notes []migrateToV6PreNote `json:"notes"` +} +type migrateToV6PostBook struct { + Name string `json:"name"` + Notes []migrateToV6PostNote `json:"notes"` +} +type migrateToV6PreDnote map[string]migrateToV6PreBook +type migrateToV6PostDnote map[string]migrateToV6PostBook diff --git a/testutils/main.go b/testutils/main.go index ca35eee9..bad5b4b8 100644 --- a/testutils/main.go +++ b/testutils/main.go @@ -56,6 +56,20 @@ func ReadFile(ctx infra.DnoteCtx, filename string) []byte { return b } +func ReadFileAbs(filename string) []byte { + fp, err := filepath.Abs(filename) + if err != nil { + panic(err) + } + + b, err := ioutil.ReadFile(fp) + if err != nil { + panic(err) + } + + return b +} + func SetupTmp(ctx infra.DnoteCtx) { if err := os.MkdirAll(ctx.DnoteDir, 0755); err != nil { panic(err) diff --git a/upgrade/upgrade.go b/upgrade/upgrade.go index 08a60eb1..66ed102a 100644 --- a/upgrade/upgrade.go +++ b/upgrade/upgrade.go @@ -19,8 +19,8 @@ import ( "github.com/pkg/errors" ) -// upgradeInterval is 7 days -var upgradeInterval int64 = 86400 * 7 +// upgradeInterval is 3 weeks +var upgradeInterval int64 = 86400 * 7 * 3 // getAsset finds the asset to download from the liast of assets in a release func getAsset(release *github.RepositoryRelease) *github.ReleaseAsset {