WIP client implementation
Some checks failed
build and deploy kleinTodo / build (push) Failing after 11s

This commit is contained in:
Darius klein 2025-08-23 19:14:16 +02:00
parent be6164d57f
commit 69785d74a8
21 changed files with 651 additions and 23 deletions

9
.gitignore vendored
View File

@ -18,11 +18,14 @@
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
# Go workspace file
go.work
go.work.sum
# env file # env file
.env .env
/data/ /data/
/client/todo/data/
/server/data/
/client/todo/todo
/server/server

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

5
client/todo/constants.go Normal file
View File

@ -0,0 +1,5 @@
package main
var usernameFlagName = "username"
var passwordFlagName = "password"
var serverUrlFlagName = "server-url"

View File

@ -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
}

121
client/todo/login.go Normal file
View File

@ -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
}

View File

@ -2,19 +2,31 @@ package main
import ( import (
"context" "context"
"fmt"
"log" "log"
"log/slog"
"maps"
"net/mail" "net/mail"
"os" "os"
"gitea.kleinsense.nl/DariusKlein/kleinTodo/client/todo/clientCommon/config"
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var cfg config.Config
func main() { func main() {
var err error
cfg, err = config.ReadConfig()
if err != nil {
slog.Error(err.Error())
}
app := &cli.Command{ app := &cli.Command{
Name: "Todo", Name: "Todo",
Usage: "kleinTodo client", Usage: "kleinTodo client",
UsageText: "Todo [category] [command] [arguments...]", UsageText: "Todo [command] [arguments...]",
Version: "v0.1.0", Version: "v0.1.0",
HideVersion: true, HideVersion: true,
Authors: []any{ Authors: []any{
@ -23,11 +35,42 @@ func main() {
Address: "darius.klein@dariusklein.nl", Address: "darius.klein@dariusklein.nl",
}, },
}, },
DefaultCommand: "help", DefaultCommand: "todo",
Commands: []*cli.Command{}, Commands: commands(),
} }
if err := app.Run(context.Background(), os.Args); err != nil { if err := app.Run(context.Background(), os.Args); err != nil {
log.Fatal(err) 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
}

83
client/todo/sync.go Normal file
View File

@ -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
}

25
common/askUser.go Normal file
View File

@ -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)
}

View File

@ -1,7 +1,9 @@
package common package common
import ( import (
"encoding/json"
"fmt" "fmt"
"os"
"sync" "sync"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
@ -23,6 +25,7 @@ type BoltStore struct {
} }
func NewBoltStore(path string) (*BoltStore, error) { func NewBoltStore(path string) (*BoltStore, error) {
os.Mkdir("data", 0755)
db, err := bolt.Open(path, 0600, nil) db, err := bolt.Open(path, 0600, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not open db: %w", err) 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 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
}

View File

@ -4,3 +4,21 @@ const UserBucket = "users"
const TodoBucket = "todo" const TodoBucket = "todo"
const AuthHeader = "Authorization" 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"
)

View File

@ -3,6 +3,7 @@ module gitea.kleinsense.nl/DariusKlein/kleinTodo/common
go 1.24.4 go 1.24.4
require ( require (
github.com/BurntSushi/toml v1.5.0
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang-jwt/jwt/v5 v5.3.0
go.etcd.io/bbolt v1.4.3 go.etcd.io/bbolt v1.4.3
golang.org/x/crypto v0.41.0 golang.org/x/crypto v0.41.0

View File

@ -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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= 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 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 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 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 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -2,6 +2,8 @@ package jwt
import ( import (
_ "context" _ "context"
"errors"
"fmt"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@ -11,11 +13,22 @@ import (
) )
func GetVerifiedUser(r *http.Request) (string, error) { func GetVerifiedUser(r *http.Request) (string, error) {
verifyJWT, err := VerifyJWT(strings.TrimPrefix(r.Header.Get(common.AuthHeader), "Bearer ")) authHeader := r.Header.Get(common.AuthHeader)
if err != nil { if authHeader == "" {
return "", err 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 // VerifyJWT verify JWT token and returns user object

View File

@ -3,6 +3,7 @@ package common
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
) )
func (todo Todo) Store(store *BoltStore, user string) error { 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)) return store.SaveValueToBucket(user, todo.Name, string(todoJson))
} }
func (todos TodoList) FindByName(name string) (Todo, bool) { func (todoList TodoList) FindByName(name string) (Todo, bool) {
for _, todo := range todos.Todos { for _, todo := range todoList.Todos {
if todo.Name == name { if todo.Name == name {
return todo, true return todo, true
} }
} }
return Todo{}, false 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)
}

View File

@ -1,8 +1,8 @@
package common package common
type Credentials struct { type Credentials struct {
Username string `json:"username"` Username string `json:"username" toml:"username"`
Password string `json:"password"` Password string `json:"password" toml:"password"`
} }
type Todo struct { type Todo struct {

7
go.work Normal file
View File

@ -0,0 +1,7 @@
go 1.24.4
use (
./common
./server
./client/todo
)

36
go.work.sum Normal file
View File

@ -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=

View File

@ -26,15 +26,7 @@ func SyncHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
storedTodoJsons := store.GetAllFromBucket(user) serverTodos := store.GetTodos(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
}
}
var response = common.SyncResponse{ var response = common.SyncResponse{
SyncedTodos: []common.Todo{}, SyncedTodos: []common.Todo{},