WIP client implementation
Some checks failed
build and deploy kleinTodo / build (push) Failing after 11s
Some checks failed
build and deploy kleinTodo / build (push) Failing after 11s
This commit is contained in:
parent
be6164d57f
commit
69785d74a8
9
.gitignore
vendored
9
.gitignore
vendored
@ -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
|
||||
|
||||
31
client/todo/clientCommon/config/commands.go
Normal file
31
client/todo/clientCommon/config/commands.go
Normal 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)
|
||||
}
|
||||
76
client/todo/clientCommon/config/config.go
Normal file
76
client/todo/clientCommon/config/config.go
Normal 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
|
||||
}
|
||||
61
client/todo/clientCommon/config/createConfig.go
Normal file
61
client/todo/clientCommon/config/createConfig.go
Normal 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
|
||||
}
|
||||
33
client/todo/clientCommon/config/getConfig.go
Normal file
33
client/todo/clientCommon/config/getConfig.go
Normal 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
5
client/todo/constants.go
Normal file
@ -0,0 +1,5 @@
|
||||
package main
|
||||
|
||||
var usernameFlagName = "username"
|
||||
var passwordFlagName = "password"
|
||||
var serverUrlFlagName = "server-url"
|
||||
38
client/todo/httpClient/httpClient.go
Normal file
38
client/todo/httpClient/httpClient.go
Normal 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
121
client/todo/login.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
83
client/todo/sync.go
Normal file
83
client/todo/sync.go
Normal 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
25
common/askUser.go
Normal 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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
36
go.work.sum
Normal file
36
go.work.sum
Normal 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=
|
||||
@ -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{},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user