Compare commits
No commits in common. "main" and "V1.0.0" have entirely different histories.
3
.github/workflows/Deploy-docker.yml
vendored
3
.github/workflows/Deploy-docker.yml
vendored
@ -28,9 +28,6 @@ jobs:
|
|||||||
chmod +x build.sh
|
chmod +x build.sh
|
||||||
./build.sh
|
./build.sh
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: go test -v ./tests/...
|
|
||||||
|
|
||||||
- name: Build the Docker image
|
- name: Build the Docker image
|
||||||
run: docker build -t gitea.kleinsense.nl/dariusklein/klein_todo:latest -f server/Dockerfile .
|
run: docker build -t gitea.kleinsense.nl/dariusklein/klein_todo:latest -f server/Dockerfile .
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package httpClient
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
||||||
)
|
)
|
||||||
@ -24,10 +23,7 @@ func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||||||
|
|
||||||
// GetHttpClient Returns httpClient with jwt in headers
|
// GetHttpClient Returns httpClient with jwt in headers
|
||||||
func getHttpClient(token string) *http.Client {
|
func getHttpClient(token string) *http.Client {
|
||||||
return &http.Client{
|
return &http.Client{Transport: &AuthTransport{Token: token}}
|
||||||
Transport: &AuthTransport{Token: token},
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHttpClient Returns CustomClient with jwt in headers
|
// GetHttpClient Returns CustomClient with jwt in headers
|
||||||
|
|||||||
@ -56,10 +56,6 @@ func loginAction(context context.Context, c *cli.Command) error {
|
|||||||
var password = c.String(passwordFlagName)
|
var password = c.String(passwordFlagName)
|
||||||
var serverUrl = c.String(serverUrlFlagName)
|
var serverUrl = c.String(serverUrlFlagName)
|
||||||
|
|
||||||
return LoginAndSave(serverUrl, username, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoginAndSave(serverUrl, username, password string) error {
|
|
||||||
token, err := loginAndGetToken(serverUrl, username, password)
|
token, err := loginAndGetToken(serverUrl, username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -76,10 +72,6 @@ func LoginAndSave(serverUrl, username, password string) error {
|
|||||||
cfg.Server.Url = serverUrl
|
cfg.Server.Url = serverUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
return SaveConfig(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveConfig(c config.Config) error {
|
|
||||||
path, configPath, err := config.GetConfigPath()
|
path, configPath, err := config.GetConfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -87,11 +79,9 @@ func SaveConfig(c config.Config) error {
|
|||||||
if err = os.MkdirAll(path, 0770); err != nil {
|
if err = os.MkdirAll(path, 0770); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
configBytes, err := toml.Marshal(c)
|
configBytes, err := toml.Marshal(cfg)
|
||||||
if err != nil {
|
err = os.WriteFile(configPath, configBytes, 0644)
|
||||||
return err
|
return nil
|
||||||
}
|
|
||||||
return os.WriteFile(configPath, configBytes, 0644)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loginAndGetToken(url, username, password string) (string, error) {
|
func loginAndGetToken(url, username, password string) (string, error) {
|
||||||
|
|||||||
@ -6,10 +6,14 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"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/client/todo/httpClient"
|
||||||
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -65,7 +69,19 @@ func registerAction(context context.Context, c *cli.Command) error {
|
|||||||
cfg.Server.Url = serverUrl
|
cfg.Server.Url = serverUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
return SaveConfig(cfg)
|
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)
|
||||||
|
|
||||||
|
log.Println("Registered new user to kleinTodo server successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerAndGetToken(url, username, password string) error {
|
func registerAndGetToken(url, username, password string) error {
|
||||||
|
|||||||
@ -24,17 +24,6 @@ func Sync() *cli.Command {
|
|||||||
|
|
||||||
// syncAction logic for Template
|
// syncAction logic for Template
|
||||||
func syncAction(context context.Context, c *cli.Command) error {
|
func syncAction(context context.Context, c *cli.Command) error {
|
||||||
if cfg.Server.Token == "" {
|
|
||||||
if common.AskUserBool("No valid JWT found. Do you wish to login using your stored credentials?") {
|
|
||||||
err := LoginAndSave(cfg.Server.Url, cfg.Server.Credentials.Username, cfg.Server.Credentials.Password)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("login failed: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("login required to sync")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
store, err := common.GetTodoDataStore()
|
store, err := common.GetTodoDataStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -57,35 +46,12 @@ func syncAction(context context.Context, c *cli.Command) error {
|
|||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
fmt.Println("Sending sync request to server...")
|
resp, err := httpClient.GetHttpClient(cfg.Server.Token).Do(req)
|
||||||
token := cfg.Server.Token
|
|
||||||
resp, err := httpClient.GetHttpClient(token).Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error sending request: %w", err)
|
return fmt.Errorf("error sending request: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusUnauthorized {
|
|
||||||
if common.AskUserBool("Login expired or invalid. Do you wish to login using your stored credentials?") {
|
|
||||||
err := LoginAndSave(cfg.Server.Url, cfg.Server.Credentials.Username, cfg.Server.Credentials.Password)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("login failed: %w", err)
|
|
||||||
}
|
|
||||||
// Re-send request with new token
|
|
||||||
req, _ = http.NewRequest("POST", cfg.Server.Url+"/sync", bytes.NewBuffer(payload))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
newToken := cfg.Server.Token
|
|
||||||
resp, err = httpClient.GetHttpClient(newToken).Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error sending request after login: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("unauthorized: login required to sync")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Reading response body...")
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read response body: %w", err)
|
return fmt.Errorf("failed to read response body: %w", err)
|
||||||
@ -95,7 +61,6 @@ func syncAction(context context.Context, c *cli.Command) error {
|
|||||||
return fmt.Errorf("request failed with status %d: %s", resp.StatusCode, string(body))
|
return fmt.Errorf("request failed with status %d: %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Decoding sync response...")
|
|
||||||
var response common.SyncResponse
|
var response common.SyncResponse
|
||||||
|
|
||||||
if err := json.Unmarshal(body, &response); err != nil {
|
if err := json.Unmarshal(body, &response); err != nil {
|
||||||
@ -151,33 +116,12 @@ func pushTodoToServer(todo common.Todo) error {
|
|||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
token := cfg.Server.Token
|
resp, err := httpClient.GetHttpClient(cfg.Server.Token).Do(req)
|
||||||
resp, err := httpClient.GetHttpClient(token).Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error sending request: %w", err)
|
return fmt.Errorf("error sending request: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusUnauthorized {
|
|
||||||
if common.AskUserBool("Login expired or invalid. Do you wish to login using your stored credentials?") {
|
|
||||||
err := LoginAndSave(cfg.Server.Url, cfg.Server.Credentials.Username, cfg.Server.Credentials.Password)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("login failed: %w", err)
|
|
||||||
}
|
|
||||||
// Re-send request with new token
|
|
||||||
req, _ = http.NewRequest("POST", cfg.Server.Url+"/update", bytes.NewBuffer(payload))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
newToken := cfg.Server.Token
|
|
||||||
resp, err = httpClient.GetHttpClient(newToken).Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error sending request after login: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("unauthorized: login required to push todo to server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
return fmt.Errorf("request failed with status %d: %s", resp.StatusCode, string(body))
|
return fmt.Errorf("request failed with status %d: %s", resp.StatusCode, string(body))
|
||||||
|
|||||||
@ -10,9 +10,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func AskUserBool(question string) bool {
|
func AskUserBool(question string) bool {
|
||||||
switch strings.ToLower(AskUserString(fmt.Sprintf("%s (Y/N): ", question))) {
|
switch AskUserString(fmt.Sprintf("%s (Y/N): ", question)) {
|
||||||
case "y", "yes":
|
case "y", "Y", "yes":
|
||||||
return true
|
return true
|
||||||
|
case "n", "N", "no":
|
||||||
|
return false
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -22,9 +24,7 @@ func AskUserString(question string) string {
|
|||||||
promptStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("6")).Bold(true)
|
promptStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("6")).Bold(true)
|
||||||
fmt.Printf("%s %s", promptStyle.Render(">"), question)
|
fmt.Printf("%s %s", promptStyle.Render(">"), question)
|
||||||
|
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
if scanner.Scan() {
|
input, _, _ := reader.ReadLine()
|
||||||
return strings.TrimSpace(scanner.Text())
|
return strings.TrimSpace(string(input))
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,28 +63,11 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func GetTodoDataStore() (*BoltStore, error) {
|
func GetTodoDataStore() (*BoltStore, error) {
|
||||||
return GetCustomDataStore("todo.db")
|
|
||||||
}
|
|
||||||
|
|
||||||
var serverDB *BoltStore
|
|
||||||
var serverOnce sync.Once
|
|
||||||
var serverErr error
|
|
||||||
|
|
||||||
func GetServerDataStore() (*BoltStore, error) {
|
|
||||||
serverOnce.Do(func() {
|
|
||||||
var path string
|
|
||||||
path, serverErr = getStoragePath()
|
|
||||||
serverDB, serverErr = NewBoltStore(path + "/server.db")
|
|
||||||
})
|
|
||||||
return serverDB, serverErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCustomDataStore(dbName string) (*BoltStore, error) {
|
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
var path string
|
var path string
|
||||||
path, err = getStoragePath()
|
path, err = getStoragePath()
|
||||||
// We assign to the outer 'dataStore' and 'err' variables.
|
// We assign to the outer 'dataStore' and 'err' variables.
|
||||||
dataStore, err = NewBoltStore(path + "/" + dbName)
|
dataStore, err = NewBoltStore(path + "/todo.db")
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
38
go.mod
38
go.mod
@ -1,38 +0,0 @@
|
|||||||
module gitea.kleinsense.nl/DariusKlein/kleinTodo/tests
|
|
||||||
|
|
||||||
go 1.25.8
|
|
||||||
|
|
||||||
require (
|
|
||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404111829-b8b839778c39
|
|
||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/server v0.0.0-20260404111829-b8b839778c39
|
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
github.com/stretchr/testify v1.11.1
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.22 // indirect
|
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
||||||
go.etcd.io/bbolt v1.4.3 // indirect
|
|
||||||
golang.org/x/crypto v0.49.0 // indirect
|
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
replace gitea.kleinsense.nl/DariusKlein/kleinTodo/common => ../common
|
|
||||||
|
|
||||||
replace gitea.kleinsense.nl/DariusKlein/kleinTodo/server => ../server
|
|
||||||
57
go.sum
57
go.sum
@ -1,57 +0,0 @@
|
|||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404111829-b8b839778c39 h1:XxG5TiAP0dvLhRsbwtZDsvCZAiyYJxkOpspAEiLx9po=
|
|
||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404111829-b8b839778c39/go.mod h1:owENFzNmtoCmr7ZUjNbkO0i+ugwqKdXCVikfOOcOsWk=
|
|
||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/server v0.0.0-20260404111829-b8b839778c39 h1:xTwCC3AFY4L5w2Yu8lNYsdsy8yO1eaj2wngdQF4Y3mI=
|
|
||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/server v0.0.0-20260404111829-b8b839778c39/go.mod h1:wkQ2bz6csXjnKdiqzRgjPt/ZDlABGsFaKdApVsoXJis=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
|
||||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
|
||||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
|
||||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
|
||||||
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.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-runewidth v0.0.22 h1:76lXsPn6FyHtTY+jt2fTTvsMUCZq1k0qwRsAMuxzKAk=
|
|
||||||
github.com/mattn/go-runewidth v0.0.22/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
|
||||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
|
||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
|
||||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
|
||||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
|
||||||
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
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=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
3
go.work
3
go.work
@ -1,8 +1,7 @@
|
|||||||
go 1.25.8
|
go 1.25.0
|
||||||
|
|
||||||
use (
|
use (
|
||||||
./client/todo
|
./client/todo
|
||||||
./common
|
./common
|
||||||
./server
|
./server
|
||||||
./tests
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -13,7 +13,7 @@ func DeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := common.GetServerDataStore()
|
store, err := common.GetTodoDataStore()
|
||||||
if handleError(w, http.StatusInternalServerError, err) {
|
if handleError(w, http.StatusInternalServerError, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get data store
|
// Get data store
|
||||||
db, err := common.GetServerDataStore()
|
db, err := common.GetTodoDataStore()
|
||||||
if handleError(w, http.StatusInternalServerError, err) {
|
if handleError(w, http.StatusInternalServerError, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ func RegisterHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Get data store
|
// Get data store
|
||||||
db, err := common.GetServerDataStore()
|
db, err := common.GetTodoDataStore()
|
||||||
if handleError(w, http.StatusInternalServerError, err) {
|
if handleError(w, http.StatusInternalServerError, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ func StoreHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := common.GetServerDataStore()
|
store, err := common.GetTodoDataStore()
|
||||||
if handleError(w, http.StatusInternalServerError, err) {
|
if handleError(w, http.StatusInternalServerError, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -11,35 +10,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func SyncHandler(w http.ResponseWriter, r *http.Request) {
|
func SyncHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Println("SyncHandler: starting request processing...")
|
|
||||||
// 1. Setup: Auth, Decode, Database Connection
|
// 1. Setup: Auth, Decode, Database Connection
|
||||||
user, err := jwt.GetVerifiedUser(r)
|
user, err := jwt.GetVerifiedUser(r)
|
||||||
if handleError(w, http.StatusUnauthorized, err) {
|
if handleError(w, http.StatusUnauthorized, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("SyncHandler: authenticated user %s\n", user)
|
|
||||||
|
|
||||||
var todoList common.TodoList
|
var todoList common.TodoList
|
||||||
if handleError(w, http.StatusBadRequest, json.NewDecoder(r.Body).Decode(&todoList)) {
|
if handleError(w, http.StatusBadRequest, json.NewDecoder(r.Body).Decode(&todoList)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("SyncHandler: received %d todos from client\n", len(todoList.Todos))
|
|
||||||
|
|
||||||
store, err := common.GetServerDataStore()
|
store, err := common.GetTodoDataStore()
|
||||||
if handleError(w, http.StatusInternalServerError, err) {
|
if handleError(w, http.StatusInternalServerError, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("SyncHandler: connected to datastore")
|
|
||||||
|
|
||||||
// 2. Load Server State
|
// 2. Load Server State
|
||||||
serverTodos := store.GetTodoMap(user)
|
serverTodos := store.GetTodoMap(user)
|
||||||
fmt.Printf("SyncHandler: loaded %d server todos\n", len(serverTodos))
|
|
||||||
response := common.SyncResponse{
|
response := common.SyncResponse{
|
||||||
SyncedTodos: []common.Todo{},
|
SyncedTodos: []common.Todo{},
|
||||||
MisMatchingTodos: []common.MisMatchingTodo{},
|
MisMatchingTodos: []common.MisMatchingTodo{},
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("SyncHandler: processing client todos...")
|
|
||||||
// 3. Process Client Updates
|
// 3. Process Client Updates
|
||||||
for _, clientTodo := range todoList.Todos {
|
for _, clientTodo := range todoList.Todos {
|
||||||
|
|
||||||
@ -98,13 +91,11 @@ func SyncHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("SyncHandler: building final response...")
|
|
||||||
// Build Final Response
|
// Build Final Response
|
||||||
for _, todo := range serverTodos {
|
for _, todo := range serverTodos {
|
||||||
response.SyncedTodos = append(response.SyncedTodos, todo)
|
response.SyncedTodos = append(response.SyncedTodos, todo)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("SyncHandler: returning %d synced todos and %d mismatches\n", len(response.SyncedTodos), len(response.MisMatchingTodos))
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
handleError(w, http.StatusInternalServerError, json.NewEncoder(w).Encode(response))
|
handleError(w, http.StatusInternalServerError, json.NewEncoder(w).Encode(response))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ func UpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := common.GetServerDataStore()
|
store, err := common.GetTodoDataStore()
|
||||||
if handleError(w, http.StatusInternalServerError, err) {
|
if handleError(w, http.StatusInternalServerError, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
db, err := common.GetServerDataStore()
|
db, err := common.GetTodoDataStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
34
tests/go.mod
34
tests/go.mod
@ -1,34 +0,0 @@
|
|||||||
module gitea.kleinsense.nl/DariusKlein/kleinTodo/tests
|
|
||||||
|
|
||||||
go 1.25.8
|
|
||||||
|
|
||||||
require (
|
|
||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404111829-b8b839778c39
|
|
||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/server v0.0.0-20260404111829-b8b839778c39
|
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
github.com/stretchr/testify v1.11.1
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.22 // indirect
|
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
||||||
go.etcd.io/bbolt v1.4.3 // indirect
|
|
||||||
golang.org/x/crypto v0.49.0 // indirect
|
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
||||||
57
tests/go.sum
57
tests/go.sum
@ -1,57 +0,0 @@
|
|||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404111829-b8b839778c39 h1:XxG5TiAP0dvLhRsbwtZDsvCZAiyYJxkOpspAEiLx9po=
|
|
||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/common v0.0.0-20260404111829-b8b839778c39/go.mod h1:owENFzNmtoCmr7ZUjNbkO0i+ugwqKdXCVikfOOcOsWk=
|
|
||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/server v0.0.0-20260404111829-b8b839778c39 h1:xTwCC3AFY4L5w2Yu8lNYsdsy8yO1eaj2wngdQF4Y3mI=
|
|
||||||
gitea.kleinsense.nl/DariusKlein/kleinTodo/server v0.0.0-20260404111829-b8b839778c39/go.mod h1:wkQ2bz6csXjnKdiqzRgjPt/ZDlABGsFaKdApVsoXJis=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
|
||||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
|
||||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
|
||||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
|
||||||
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.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-runewidth v0.0.22 h1:76lXsPn6FyHtTY+jt2fTTvsMUCZq1k0qwRsAMuxzKAk=
|
|
||||||
github.com/mattn/go-runewidth v0.0.22/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
|
||||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
|
||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
|
||||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
|
||||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
|
||||||
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
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=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
@ -1,144 +0,0 @@
|
|||||||
package tests
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
|
||||||
"gitea.kleinsense.nl/DariusKlein/kleinTodo/server/handler"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIntegrationSync(t *testing.T) {
|
|
||||||
// 1. Setup Environment
|
|
||||||
tempDir, err := os.MkdirTemp("", "todo-test-*")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer os.RemoveAll(tempDir)
|
|
||||||
|
|
||||||
// Override storage path for BoltStore by setting HOME or XDG_CONFIG_HOME
|
|
||||||
// BoltStore uses os.UserConfigDir() which looks at XDG_CONFIG_HOME on Linux
|
|
||||||
os.Setenv("XDG_CONFIG_HOME", tempDir)
|
|
||||||
os.Setenv("JWT_SECRET", "test-secret-123")
|
|
||||||
|
|
||||||
// 2. Initialize Server
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.HandleFunc("POST /register", handler.RegisterHandler)
|
|
||||||
mux.HandleFunc("POST /login", handler.LoginHandler)
|
|
||||||
mux.HandleFunc("POST /sync", handler.SyncHandler)
|
|
||||||
ts := httptest.NewServer(mux)
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
client := ts.Client()
|
|
||||||
username := "testuser"
|
|
||||||
password := "testpass"
|
|
||||||
|
|
||||||
// 3. Register User
|
|
||||||
regPayload, _ := json.Marshal(common.Credentials{Username: username, Password: password})
|
|
||||||
resp, err := client.Post(ts.URL+"/register", "application/json", bytes.NewBuffer(regPayload))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
||||||
|
|
||||||
// 4. Login and Get Token
|
|
||||||
resp, err = client.Post(ts.URL+"/login", "application/json", bytes.NewBuffer(regPayload))
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
||||||
token := resp.Header.Get(common.AuthHeader)
|
|
||||||
require.NotEmpty(t, token)
|
|
||||||
|
|
||||||
// 5. Create Local Todos
|
|
||||||
store, err := common.GetTodoDataStore()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer store.Close()
|
|
||||||
|
|
||||||
todo1 := common.Todo{
|
|
||||||
Id: uuid.New().String(),
|
|
||||||
Name: "Task 1",
|
|
||||||
Description: "Desc 1",
|
|
||||||
Status: common.NotStarted,
|
|
||||||
Owner: username,
|
|
||||||
LastModified: time.Now().UTC(),
|
|
||||||
}
|
|
||||||
todo2 := common.Todo{
|
|
||||||
Id: uuid.New().String(),
|
|
||||||
Name: "Task 2",
|
|
||||||
Description: "Desc 2",
|
|
||||||
Status: common.WIP,
|
|
||||||
Owner: username,
|
|
||||||
LastModified: time.Now().UTC(),
|
|
||||||
}
|
|
||||||
|
|
||||||
err = todo1.Store(store, username)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = todo2.Store(store, username)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// 6. Sync to Server
|
|
||||||
localTodos := store.GetTodoList(username, true)
|
|
||||||
syncReq := common.TodoList{Todos: localTodos}
|
|
||||||
syncPayload, _ := json.Marshal(syncReq)
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("POST", ts.URL+"/sync", bytes.NewBuffer(syncPayload))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set(common.AuthHeader, "Bearer "+token)
|
|
||||||
|
|
||||||
resp, err = client.Do(req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
||||||
|
|
||||||
var syncResp common.SyncResponse
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&syncResp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
// Verify both todos are in sync response
|
|
||||||
assert.Len(t, syncResp.SyncedTodos, 2)
|
|
||||||
assert.Empty(t, syncResp.MisMatchingTodos)
|
|
||||||
|
|
||||||
// 7. Verify Server State (By syncing again with empty local)
|
|
||||||
emptySyncReq := common.TodoList{Todos: []common.Todo{}}
|
|
||||||
emptySyncPayload, _ := json.Marshal(emptySyncReq)
|
|
||||||
req, _ = http.NewRequest("POST", ts.URL+"/sync", bytes.NewBuffer(emptySyncPayload))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set(common.AuthHeader, "Bearer "+token)
|
|
||||||
|
|
||||||
resp, err = client.Do(req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
||||||
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&syncResp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
assert.Len(t, syncResp.SyncedTodos, 2, "Server should have kept the synced todos")
|
|
||||||
|
|
||||||
// 8. Test Pulling from Server (New Client)
|
|
||||||
// Create another temporary directory for a different client
|
|
||||||
client2TempDir, err := os.MkdirTemp("", "todo-test-client2-*")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer os.RemoveAll(client2TempDir)
|
|
||||||
|
|
||||||
// Since common.GetTodoDataStore() uses sync.Once, we can't easily get a new database in the same process
|
|
||||||
// BUT for this test, we can just use GetCustomDataStore("todo2.db") if it was public,
|
|
||||||
// however it seems we can just check if the server returns the data when requested.
|
|
||||||
|
|
||||||
req, _ = http.NewRequest("POST", ts.URL+"/sync", bytes.NewBuffer(emptySyncPayload))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set(common.AuthHeader, "Bearer "+token)
|
|
||||||
|
|
||||||
resp, err = client.Do(req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
||||||
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&syncResp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
resp.Body.Close()
|
|
||||||
|
|
||||||
assert.Len(t, syncResp.SyncedTodos, 2, "Server should return the 2 todos for a sync request")
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user