From 69785d74a8c014ee8e0ea0ffd600a5c910fcaf34 Mon Sep 17 00:00:00 2001 From: Darius klein Date: Sat, 23 Aug 2025 19:14:16 +0200 Subject: [PATCH] WIP client implementation --- .gitignore | 9 +- client/todo/clientCommon/config/commands.go | 31 +++++ client/todo/clientCommon/config/config.go | 76 +++++++++++ .../todo/clientCommon/config/createConfig.go | 61 +++++++++ client/todo/clientCommon/config/getConfig.go | 33 +++++ client/todo/constants.go | 5 + client/todo/httpClient/httpClient.go | 38 ++++++ client/todo/login.go | 121 ++++++++++++++++++ client/todo/main.go | 49 ++++++- client/todo/sync.go | 83 ++++++++++++ common/askUser.go | 25 ++++ common/bolt.go | 16 +++ common/const.go | 18 +++ common/go.mod | 1 + common/go.sum | 3 + common/jwt/verify.go | 21 ++- common/todo.go | 27 +++- common/types.go | 4 +- go.work | 7 + go.work.sum | 36 ++++++ server/handler/syncHandler.go | 10 +- 21 files changed, 651 insertions(+), 23 deletions(-) create mode 100644 client/todo/clientCommon/config/commands.go create mode 100644 client/todo/clientCommon/config/config.go create mode 100644 client/todo/clientCommon/config/createConfig.go create mode 100644 client/todo/clientCommon/config/getConfig.go create mode 100644 client/todo/constants.go create mode 100644 client/todo/httpClient/httpClient.go create mode 100644 client/todo/login.go create mode 100644 client/todo/sync.go create mode 100644 common/askUser.go create mode 100644 go.work create mode 100644 go.work.sum diff --git a/.gitignore b/.gitignore index c6bb906..6cb2bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,11 +18,14 @@ # Dependency directories (remove the comment below to include it) # vendor/ -# Go workspace file -go.work -go.work.sum # env file .env /data/ + +/client/todo/data/ +/server/data/ + +/client/todo/todo +/server/server diff --git a/client/todo/clientCommon/config/commands.go b/client/todo/clientCommon/config/commands.go new file mode 100644 index 0000000..24711bb --- /dev/null +++ b/client/todo/clientCommon/config/commands.go @@ -0,0 +1,31 @@ +package config + +import ( + "context" + + "github.com/urfave/cli/v3" +) + +// Category config +func Category() *cli.Command { + return &cli.Command{ + Name: "config", + Usage: "command to interact with config", + Action: Action, + Commands: commands(), + HideHelpCommand: true, + } +} + +// commands for Config Category +func commands() []*cli.Command { + return []*cli.Command{ + CreateConfig(), + GetConfig(), + } +} + +// Action show help command if no sub commands are given for Config +func Action(context context.Context, c *cli.Command) error { + return cli.ShowSubcommandHelp(c) +} diff --git a/client/todo/clientCommon/config/config.go b/client/todo/clientCommon/config/config.go new file mode 100644 index 0000000..4548fe9 --- /dev/null +++ b/client/todo/clientCommon/config/config.go @@ -0,0 +1,76 @@ +package config + +import ( + "errors" + "os" + "path/filepath" + "runtime" + + "gitea.kleinsense.nl/DariusKlein/kleinTodo/common" + "github.com/BurntSushi/toml" +) + +type Config struct { + Settings Settings `toml:"settings"` + Server Server `toml:"server"` +} + +type Settings struct { + Environment string `toml:"environment"` +} + +type Server struct { + Credentials common.Credentials `toml:"credentials"` + Token string `toml:"token"` + Url string `toml:"url"` +} + +var config Config + +var DefaultConfig = Config{ + Settings: Settings{Environment: "user"}, + Server: Server{ + Credentials: common.Credentials{ + Username: "user", + Password: "password", + }, + Token: "token", + Url: "https://EXAMPLE.com", + }, +} + +func GetConfigPath() (path string, configPath string, err error) { + homeDir, _ := os.UserHomeDir() + + switch runtime.GOOS { + case "windows": + path = filepath.Dir(homeDir + "\\AppData\\Local\\kleinTodo\\client\\") + case "linux": + path = filepath.Dir(homeDir + "/.config/kleinTodo/client/") + default: + return "", "", errors.New("unsupported platform") + } + + configPath = filepath.Join(path, "/config.toml") + + return path, configPath, nil +} + +func ReadConfig() (Config, error) { + _, configPath, err := GetConfigPath() + if err != nil { + return config, err + } + + file, err := os.ReadFile(configPath) + if err != nil { + return config, err + } + + _, err = toml.Decode(string(file), &config) + if err != nil { + return config, err + } + + return config, nil +} diff --git a/client/todo/clientCommon/config/createConfig.go b/client/todo/clientCommon/config/createConfig.go new file mode 100644 index 0000000..028544a --- /dev/null +++ b/client/todo/clientCommon/config/createConfig.go @@ -0,0 +1,61 @@ +package config + +import ( + "context" + "errors" + "fmt" + "log" + "os" + + "github.com/BurntSushi/toml" + "github.com/urfave/cli/v3" +) + +var force bool + +// CreateConfig Command +func CreateConfig() *cli.Command { + return &cli.Command{ + Name: "create", + Usage: "Generates a new configuration file", + Action: createConfigAction, + Flags: createConfigFlags(), + } +} + +// createConfigFlags Register cli flags +func createConfigFlags() []cli.Flag { + return []cli.Flag{ + &cli.BoolFlag{ + Name: "force", + Aliases: []string{"f"}, + Usage: "force overwrite", + Destination: &force, + }, + } +} + +// createConfigAction create config file +func createConfigAction(context context.Context, c *cli.Command) error { + path, configPath, err := GetConfigPath() + if err != nil { + return err + } + log.Println("Creating configuration file") + if err = os.MkdirAll(path, 0770); err != nil { + return err + } + + if _, err = os.Stat(configPath); errors.Is(err, os.ErrNotExist) || force { + + configBytes, err := toml.Marshal(DefaultConfig) + err = os.WriteFile(configPath, configBytes, 0644) + if err != nil { + return err + } + fmt.Println("Created: " + configPath) + } else { + fmt.Println("Configuration file already exists") + } + return nil +} diff --git a/client/todo/clientCommon/config/getConfig.go b/client/todo/clientCommon/config/getConfig.go new file mode 100644 index 0000000..20b21dc --- /dev/null +++ b/client/todo/clientCommon/config/getConfig.go @@ -0,0 +1,33 @@ +package config + +import ( + "context" + "fmt" + "os" + + "github.com/urfave/cli/v3" +) + +// GetConfig Command +func GetConfig() *cli.Command { + return &cli.Command{ + Name: "get", + Usage: "read configuration file", + Action: getConfigAction, + } +} + +// getConfigAction logic for GetConfig +func getConfigAction(context context.Context, c *cli.Command) error { + _, configPath, err := GetConfigPath() + if err != nil { + return err + } + + file, err := os.ReadFile(configPath) + if err != nil { + return err + } + fmt.Println(string(file)) + return nil +} diff --git a/client/todo/constants.go b/client/todo/constants.go new file mode 100644 index 0000000..3dec7de --- /dev/null +++ b/client/todo/constants.go @@ -0,0 +1,5 @@ +package main + +var usernameFlagName = "username" +var passwordFlagName = "password" +var serverUrlFlagName = "server-url" diff --git a/client/todo/httpClient/httpClient.go b/client/todo/httpClient/httpClient.go new file mode 100644 index 0000000..d05daa7 --- /dev/null +++ b/client/todo/httpClient/httpClient.go @@ -0,0 +1,38 @@ +package httpClient + +import ( + "fmt" + "net/http" +) + +type AuthTransport struct { + Token string +} + +type CustomClient struct { + client *http.Client +} + +// RoundTrip transport method implementation with jwt in header +func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Add("Authorization", fmt.Sprintf("Beare%s", t.Token)) + return http.DefaultTransport.RoundTrip(req) +} + +// GetHttpClient Returns httpClient with jwt in headers +func getHttpClient(token string) *http.Client { + return &http.Client{Transport: &AuthTransport{Token: token}} +} + +// GetHttpClient Returns CustomClient with jwt in headers +func GetHttpClient(token string) *CustomClient { + return &CustomClient{getHttpClient(token)} +} + +func (client CustomClient) Do(req *http.Request) (*http.Response, error) { + resp, err := client.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + return resp, nil +} diff --git a/client/todo/login.go b/client/todo/login.go new file mode 100644 index 0000000..596219e --- /dev/null +++ b/client/todo/login.go @@ -0,0 +1,121 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + + "gitea.kleinsense.nl/DariusKlein/kleinTodo/client/todo/clientCommon/config" + "gitea.kleinsense.nl/DariusKlein/kleinTodo/client/todo/httpClient" + "gitea.kleinsense.nl/DariusKlein/kleinTodo/common" + "github.com/BurntSushi/toml" + "github.com/urfave/cli/v3" +) + +// Login Command +func Login() *cli.Command { + return &cli.Command{ + Name: "login", + Usage: "Login to kleinTodo server", + Action: loginAction, + Flags: loginFlags(), + } +} + +// loginFlags Register cli flags +func loginFlags() []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: usernameFlagName, + Aliases: []string{"u"}, + Required: false, + Value: cfg.Server.Credentials.Username, + }, + &cli.StringFlag{ + Name: passwordFlagName, + Aliases: []string{"p"}, + Required: false, + Value: cfg.Server.Credentials.Password, + }, + &cli.StringFlag{ + Name: serverUrlFlagName, + Aliases: []string{"url"}, + Required: false, + Value: cfg.Server.Url, + }, + } +} + +// loginAction logic for Template +func loginAction(context context.Context, c *cli.Command) error { + var username = c.String(usernameFlagName) + var password = c.String(passwordFlagName) + var serverUrl = c.String(serverUrlFlagName) + + token, err := loginAndGetToken(serverUrl, username, password) + if err != nil { + return err + } + cfg.Server.Token = token + + if (cfg.Server.Credentials.Username != username || cfg.Server.Credentials.Password != password) && + common.AskUserBool("Do you wish to save your credentials? (WARNING: stored in plaintext)") { + cfg.Server.Credentials.Username = username + cfg.Server.Credentials.Password = password + } + + if cfg.Server.Url != serverUrl && common.AskUserBool("Do you wish to save your chosen server url?") { + cfg.Server.Url = serverUrl + } + + path, configPath, err := config.GetConfigPath() + if err != nil { + return err + } + if err = os.MkdirAll(path, 0770); err != nil { + return err + } + configBytes, err := toml.Marshal(cfg) + err = os.WriteFile(configPath, configBytes, 0644) + return nil +} + +func loginAndGetToken(url, username, password string) (string, error) { + credentials := common.Credentials{ + Username: username, + Password: password, + } + + payload, err := json.Marshal(credentials) + if err != nil { + return "", fmt.Errorf("error marshaling credentials: %w", err) + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload)) + if err != nil { + return "", fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := httpClient.GetHttpClient("").Do(req) + if err != nil { + return "", fmt.Errorf("error sending request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return "", fmt.Errorf("login failed with status %d: %s", resp.StatusCode, string(body)) + } + + token := resp.Header.Get(common.AuthHeader) + if token == "" { + return "", fmt.Errorf("auth token not found in response header") + } + + return token, nil +} diff --git a/client/todo/main.go b/client/todo/main.go index 05ac803..7b7478b 100644 --- a/client/todo/main.go +++ b/client/todo/main.go @@ -2,19 +2,31 @@ package main import ( "context" + "fmt" "log" + "log/slog" + "maps" "net/mail" "os" + "gitea.kleinsense.nl/DariusKlein/kleinTodo/client/todo/clientCommon/config" + "gitea.kleinsense.nl/DariusKlein/kleinTodo/common" "github.com/urfave/cli/v3" ) +var cfg config.Config + func main() { + var err error + cfg, err = config.ReadConfig() + if err != nil { + slog.Error(err.Error()) + } app := &cli.Command{ Name: "Todo", Usage: "kleinTodo client", - UsageText: "Todo [category] [command] [arguments...]", + UsageText: "Todo [command] [arguments...]", Version: "v0.1.0", HideVersion: true, Authors: []any{ @@ -23,11 +35,42 @@ func main() { Address: "darius.klein@dariusklein.nl", }, }, - DefaultCommand: "help", - Commands: []*cli.Command{}, + DefaultCommand: "todo", + Commands: commands(), } if err := app.Run(context.Background(), os.Args); err != nil { log.Fatal(err) } } + +func commands() []*cli.Command { + return []*cli.Command{ + config.Category(), + Login(), + Sync(), + { + Name: "todo", + Usage: "print todo items", + Action: printTodo, + HideHelpCommand: true, + }, + } +} + +func printTodo(context context.Context, c *cli.Command) error { + fmt.Printf("Todo items:\n") + store, err := common.GetTodoDataStore() + if err != nil { + return err + } + serverTodos := store.GetTodos(cfg.Server.Credentials.Username) + + var index = 1 + + for todo := range maps.Values(serverTodos) { + todo.PrintIndexed(index) + index++ + } + return nil +} diff --git a/client/todo/sync.go b/client/todo/sync.go new file mode 100644 index 0000000..074bee8 --- /dev/null +++ b/client/todo/sync.go @@ -0,0 +1,83 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log" + "maps" + "net/http" + + "gitea.kleinsense.nl/DariusKlein/kleinTodo/client/todo/httpClient" + "gitea.kleinsense.nl/DariusKlein/kleinTodo/common" + "github.com/urfave/cli/v3" +) + +// Sync Command +func Sync() *cli.Command { + return &cli.Command{ + Name: "sync", + Usage: "sync with kleinTodo server", + Action: syncAction, + Flags: loginFlags(), + } +} + +// syncAction logic for Template +func syncAction(context context.Context, c *cli.Command) error { + store, err := common.GetTodoDataStore() + if err != nil { + return err + } + serverTodos := store.GetTodos(cfg.Server.Credentials.Username) + + var todos []common.Todo + + for todo := range maps.Values(serverTodos) { + todos = append(todos, todo) + } + + payload, err := json.Marshal(common.TodoList{Todos: todos}) + if err != nil { + return fmt.Errorf("error marshaling credentials: %w", err) + } + + req, err := http.NewRequest("POST", cfg.Server.Url, bytes.NewBuffer(payload)) + if err != nil { + return fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := httpClient.GetHttpClient(cfg.Server.Token).Do(req) + if err != nil { + return fmt.Errorf("error sending request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("request failed with status %d: %s", resp.StatusCode, string(body)) + } + + var response common.SyncResponse + + if err := json.Unmarshal(body, &response); err != nil { + return fmt.Errorf("failed to decode successful response: %w", err) + } + + prettyJSON, err := json.MarshalIndent(response, "", " ") + if err != nil { + log.Fatalf("Failed to generate json: %s", err) + } + + // Print the string version of the byte slice. + fmt.Printf("%s\n", prettyJSON) + + return nil +} diff --git a/common/askUser.go b/common/askUser.go new file mode 100644 index 0000000..758704f --- /dev/null +++ b/common/askUser.go @@ -0,0 +1,25 @@ +package common + +import ( + "bufio" + "fmt" + "os" +) + +func AskUserBool(question string) bool { + switch AskUserString(fmt.Sprintf("%s (Y/N): ", question)) { + case "y", "Y", "yes": + return true + case "n", "N", "no": + return false + default: + return false + } +} + +func AskUserString(question string) string { + fmt.Printf(question) + reader := bufio.NewReader(os.Stdin) + input, _, _ := reader.ReadLine() + return string(input) +} diff --git a/common/bolt.go b/common/bolt.go index aa247c3..a31ade2 100644 --- a/common/bolt.go +++ b/common/bolt.go @@ -1,7 +1,9 @@ package common import ( + "encoding/json" "fmt" + "os" "sync" bolt "go.etcd.io/bbolt" @@ -23,6 +25,7 @@ type BoltStore struct { } func NewBoltStore(path string) (*BoltStore, error) { + os.Mkdir("data", 0755) db, err := bolt.Open(path, 0600, nil) if err != nil { return nil, fmt.Errorf("could not open db: %w", err) @@ -176,3 +179,16 @@ func (s *BoltStore) ExistsByKey(bucket, key string) (bool, error) { }) return exists, err } + +func (s *BoltStore) GetTodos(user string) map[string]Todo { + storedTodoJsons := s.GetAllFromBucket(user) + + serverTodos := make(map[string]Todo) + for key, val := range storedTodoJsons { + var todo Todo + if json.Unmarshal([]byte(val), &todo) == nil { + serverTodos[key] = todo + } + } + return serverTodos +} diff --git a/common/const.go b/common/const.go index 7aece64..fde0440 100644 --- a/common/const.go +++ b/common/const.go @@ -4,3 +4,21 @@ const UserBucket = "users" const TodoBucket = "todo" const AuthHeader = "Authorization" + +// ANSI color codes for terminal output +const ( + ColorReset = "\033[0m" + ColorRed = "\033[31m" + ColorGreen = "\033[32m" + ColorYellow = "\033[33m" + ColorBlue = "\033[34m" +) + +// statuses +const ( + Done = "done" + WIP = "work in progress" + Pending = "pending" + Blocked = "blocked" + Failed = "failed" +) diff --git a/common/go.mod b/common/go.mod index 5aa22fb..51a6800 100644 --- a/common/go.mod +++ b/common/go.mod @@ -3,6 +3,7 @@ module gitea.kleinsense.nl/DariusKlein/kleinTodo/common go 1.24.4 require ( + github.com/BurntSushi/toml v1.5.0 github.com/golang-jwt/jwt/v5 v5.3.0 go.etcd.io/bbolt v1.4.3 golang.org/x/crypto v0.41.0 diff --git a/common/go.sum b/common/go.sum index 46cd654..5fb7c27 100644 --- a/common/go.sum +++ b/common/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= @@ -11,6 +13,7 @@ go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/common/jwt/verify.go b/common/jwt/verify.go index e5ae74d..75129d1 100644 --- a/common/jwt/verify.go +++ b/common/jwt/verify.go @@ -2,6 +2,8 @@ package jwt import ( _ "context" + "errors" + "fmt" "net/http" "os" "strings" @@ -11,11 +13,22 @@ import ( ) func GetVerifiedUser(r *http.Request) (string, error) { - verifyJWT, err := VerifyJWT(strings.TrimPrefix(r.Header.Get(common.AuthHeader), "Bearer ")) - if err != nil { - return "", err + authHeader := r.Header.Get(common.AuthHeader) + if authHeader == "" { + return "", errors.New("authorization header is required") } - return verifyJWT, nil + + parts := strings.Split(authHeader, " ") + if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { + return "", errors.New("authorization header format must be Bearer {token}") + } + + verifiedUser, err := VerifyJWT(parts[1]) + if err != nil { + return "", fmt.Errorf("invalid token: %w", err) + } + + return verifiedUser, nil } // VerifyJWT verify JWT token and returns user object diff --git a/common/todo.go b/common/todo.go index b38f8f8..ccd5d8e 100644 --- a/common/todo.go +++ b/common/todo.go @@ -3,6 +3,7 @@ package common import ( "encoding/json" "fmt" + "strings" ) func (todo Todo) Store(store *BoltStore, user string) error { @@ -30,11 +31,33 @@ func (todoRequest StoreTodoRequest) Store(store *BoltStore, user string) error { return store.SaveValueToBucket(user, todo.Name, string(todoJson)) } -func (todos TodoList) FindByName(name string) (Todo, bool) { - for _, todo := range todos.Todos { +func (todoList TodoList) FindByName(name string) (Todo, bool) { + for _, todo := range todoList.Todos { if todo.Name == name { return todo, true } } return Todo{}, false } + +func (todo Todo) PrintIndexed(index int) { + var statusColor string + + // Select color based on the status (case-insensitive) + switch strings.ToLower(todo.Status) { + case Done: + statusColor = ColorGreen + case WIP: + statusColor = ColorYellow + case Pending: + statusColor = ColorBlue + case Blocked, Failed: + statusColor = ColorRed + default: + statusColor = ColorReset // No color for unknown statuses + } + + fmt.Printf("%d) %s - %s%s%s\n", index, todo.Name, statusColor, strings.ToUpper(todo.Status), ColorReset) + + fmt.Printf("\t%s\n", todo.Description) +} diff --git a/common/types.go b/common/types.go index c40eb77..182006d 100644 --- a/common/types.go +++ b/common/types.go @@ -1,8 +1,8 @@ package common type Credentials struct { - Username string `json:"username"` - Password string `json:"password"` + Username string `json:"username" toml:"username"` + Password string `json:"password" toml:"password"` } type Todo struct { diff --git a/go.work b/go.work new file mode 100644 index 0000000..897566f --- /dev/null +++ b/go.work @@ -0,0 +1,7 @@ +go 1.24.4 + +use ( + ./common + ./server + ./client/todo +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..e9c1289 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,36 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +go.etcd.io/gofail v0.2.0 h1:p19drv16FKK345a09a1iubchlw/vmRuksmRzgBIGjcA= +go.etcd.io/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/server/handler/syncHandler.go b/server/handler/syncHandler.go index 92f5e76..c6710b3 100644 --- a/server/handler/syncHandler.go +++ b/server/handler/syncHandler.go @@ -26,15 +26,7 @@ func SyncHandler(w http.ResponseWriter, r *http.Request) { return } - storedTodoJsons := store.GetAllFromBucket(user) - - serverTodos := make(map[string]common.Todo) - for key, val := range storedTodoJsons { - var todo common.Todo - if json.Unmarshal([]byte(val), &todo) == nil { - serverTodos[key] = todo - } - } + serverTodos := store.GetTodos(user) var response = common.SyncResponse{ SyncedTodos: []common.Todo{},