Added update command
All checks were successful
build and deploy kleinTodo / build (push) Successful in 34s
All checks were successful
build and deploy kleinTodo / build (push) Successful in 34s
Updated delete logic Added last modified and deleted to the todo item
This commit is contained in:
parent
bb685be5d5
commit
6e78c1948e
@ -2,10 +2,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
"gitea.kleinsense.nl/DariusKlein/kleinTodo/client/todo/clientCommon/config"
|
"gitea.kleinsense.nl/DariusKlein/kleinTodo/client/todo/clientCommon/config"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
@ -48,5 +51,44 @@ func commands() []*cli.Command {
|
|||||||
Sync(),
|
Sync(),
|
||||||
Add(),
|
Add(),
|
||||||
Todo(),
|
Todo(),
|
||||||
|
{
|
||||||
|
Name: "update",
|
||||||
|
Usage: "Update the vennexCLI to a specific version",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "version",
|
||||||
|
Usage: "The version label to install (e.g., v1.2.0, main, latest)",
|
||||||
|
Value: "latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: runUpdate,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runUpdate(ctx context.Context, command *cli.Command) error {
|
||||||
|
var pkgPath = "unknown"
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if ok {
|
||||||
|
pkgPath = info.Main.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg := fmt.Sprintf("%s@%s", pkgPath, command.String("version"))
|
||||||
|
|
||||||
|
slog.Info(fmt.Sprintf("Updating to %s...\n", pkg))
|
||||||
|
|
||||||
|
cmd := exec.Command("go", "install", pkg)
|
||||||
|
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Update failed:", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("Update successful!")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ func syncAction(context context.Context, c *cli.Command) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
serverTodos := store.GetTodoList(cfg.Server.Credentials.Username)
|
serverTodos := store.GetTodoList(cfg.Server.Credentials.Username, true)
|
||||||
|
|
||||||
var todos []common.Todo
|
var todos []common.Todo
|
||||||
|
|
||||||
|
|||||||
@ -4,10 +4,12 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
@ -32,7 +34,7 @@ func todo(context context.Context, c *cli.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
todos := store.GetTodoList(cfg.Server.Credentials.Username)
|
todos := store.GetTodoList(cfg.Server.Credentials.Username, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -89,7 +91,9 @@ func handleDelete(scanner *bufio.Scanner, todos []common.Todo, store *common.Bol
|
|||||||
|
|
||||||
removedItem := todos[index-1]
|
removedItem := todos[index-1]
|
||||||
|
|
||||||
err = store.RemoveValueFromBucket(cfg.Server.Credentials.Username, removedItem.Name)
|
removedItem.Deleted = true
|
||||||
|
|
||||||
|
err = removedItem.Store(store, cfg.Server.Credentials.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(err.Error())
|
slog.Error(err.Error())
|
||||||
return todos
|
return todos
|
||||||
@ -159,8 +163,16 @@ func handleAdd(scanner *bufio.Scanner, todos []common.Todo, store *common.BoltSt
|
|||||||
scanner.Scan()
|
scanner.Scan()
|
||||||
description := strings.TrimSpace(scanner.Text())
|
description := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
todoList := common.TodoList{Todos: todos}
|
||||||
|
|
||||||
|
_, exists := todoList.FindByName(name)
|
||||||
|
if exists {
|
||||||
|
log.Fatalf("Item '%s' already exists.", name)
|
||||||
|
return todos
|
||||||
|
}
|
||||||
|
|
||||||
newTodo := common.Todo{
|
newTodo := common.Todo{
|
||||||
Name: name, Description: description, Status: common.NotStarted, Owner: cfg.Server.Credentials.Username,
|
Name: name, Description: description, Status: common.NotStarted, Owner: cfg.Server.Credentials.Username, LastModified: time.Now(),
|
||||||
}
|
}
|
||||||
err := newTodo.Store(store, cfg.Server.Credentials.Username)
|
err := newTodo.Store(store, cfg.Server.Credentials.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
@ -24,8 +25,26 @@ type BoltStore struct {
|
|||||||
DB *bolt.DB
|
DB *bolt.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const configDirName = "kleinTodo"
|
||||||
|
|
||||||
|
func getStoragePath() (path string, err error) {
|
||||||
|
configDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not get user config directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
appConfigDir := filepath.Join(configDir, configDirName)
|
||||||
|
|
||||||
|
// Ensure the directory exists before we try to use it.
|
||||||
|
if err := os.MkdirAll(appConfigDir, 0755); err != nil {
|
||||||
|
return "", fmt.Errorf("could not create app config directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return appConfigDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewBoltStore(path string) (*BoltStore, error) {
|
func NewBoltStore(path string) (*BoltStore, error) {
|
||||||
os.Mkdir("data", 0755)
|
os.MkdirAll(filepath.Dir(path), 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)
|
||||||
@ -45,8 +64,10 @@ var (
|
|||||||
|
|
||||||
func GetTodoDataStore() (*BoltStore, error) {
|
func GetTodoDataStore() (*BoltStore, error) {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
|
var path string
|
||||||
|
path, err = getStoragePath()
|
||||||
// We assign to the outer 'dataStore' and 'err' variables.
|
// We assign to the outer 'dataStore' and 'err' variables.
|
||||||
dataStore, err = NewBoltStore("data/todo.db")
|
dataStore, err = NewBoltStore(path + "/todo.db")
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -193,15 +214,23 @@ func (s *BoltStore) GetTodoMap(user string) map[string]Todo {
|
|||||||
return serverTodos
|
return serverTodos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BoltStore) GetTodoList(user string) []Todo {
|
func (s *BoltStore) GetTodoList(user string, includeDeleted bool) []Todo {
|
||||||
storedTodoJsons := s.GetAllFromBucket(user)
|
storedTodoJsons := s.GetAllFromBucket(user)
|
||||||
|
|
||||||
var serverTodos []Todo
|
var storedTodos []Todo
|
||||||
for _, val := range storedTodoJsons {
|
for _, val := range storedTodoJsons {
|
||||||
var todo Todo
|
var todo Todo
|
||||||
if json.Unmarshal([]byte(val), &todo) == nil {
|
if json.Unmarshal([]byte(val), &todo) == nil {
|
||||||
serverTodos = append(serverTodos, todo)
|
var include = false
|
||||||
|
if includeDeleted {
|
||||||
|
include = true
|
||||||
|
} else {
|
||||||
|
include = todo.Deleted
|
||||||
|
}
|
||||||
|
if include {
|
||||||
|
storedTodos = append(storedTodos, todo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return serverTodos
|
}
|
||||||
|
return storedTodos
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,12 +4,14 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (todo Todo) Store(store *BoltStore, user string) error {
|
func (todo Todo) Store(store *BoltStore, user string) error {
|
||||||
if todo.Owner != user {
|
if todo.Owner != user {
|
||||||
return fmt.Errorf("unauthorized user")
|
return fmt.Errorf("unauthorized user")
|
||||||
}
|
}
|
||||||
|
todo.LastModified = time.Now()
|
||||||
todoJson, err := json.Marshal(todo)
|
todoJson, err := json.Marshal(todo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -23,6 +25,7 @@ func (todoRequest StoreTodoRequest) Store(store *BoltStore, user string) error {
|
|||||||
Description: todoRequest.Description,
|
Description: todoRequest.Description,
|
||||||
Status: todoRequest.Status,
|
Status: todoRequest.Status,
|
||||||
Owner: user,
|
Owner: user,
|
||||||
|
LastModified: time.Now(),
|
||||||
}
|
}
|
||||||
todoJson, err := json.Marshal(todo)
|
todoJson, err := json.Marshal(todo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -41,6 +44,10 @@ func (todoList TodoList) FindByName(name string) (Todo, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (todo Todo) PrintIndexed(index int) {
|
func (todo Todo) PrintIndexed(index int) {
|
||||||
|
|
||||||
|
if todo.Deleted {
|
||||||
|
return
|
||||||
|
}
|
||||||
var statusColor string
|
var statusColor string
|
||||||
|
|
||||||
// Select color based on the status (case-insensitive)
|
// Select color based on the status (case-insensitive)
|
||||||
@ -57,7 +64,19 @@ func (todo Todo) PrintIndexed(index int) {
|
|||||||
statusColor = ColorReset // No color for unknown statuses
|
statusColor = ColorReset // No color for unknown statuses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastMod := todo.LastModified.Format("2006-01-02 15:04")
|
||||||
|
|
||||||
fmt.Printf("%d) %s - %s%s%s\n", index, todo.Name, statusColor, strings.ToUpper(todo.Status), ColorReset)
|
fmt.Printf("%d) %s - %s%s%s\n", index, todo.Name, statusColor, strings.ToUpper(todo.Status), ColorReset)
|
||||||
|
|
||||||
fmt.Printf("\t%s\n", todo.Description)
|
fmt.Printf("\t%s\n", todo.Description)
|
||||||
|
|
||||||
|
fmt.Printf("\t%sLast Modified: %s%s\n", ColorReset, lastMod, ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Todo) IsEqual(other Todo) bool {
|
||||||
|
return t.Name == other.Name &&
|
||||||
|
t.Description == other.Description &&
|
||||||
|
t.Status == other.Status &&
|
||||||
|
t.Owner == other.Owner &&
|
||||||
|
t.Deleted == other.Deleted
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
type Credentials struct {
|
type Credentials struct {
|
||||||
Username string `json:"username" toml:"username"`
|
Username string `json:"username" toml:"username"`
|
||||||
Password string `json:"password" toml:"password"`
|
Password string `json:"password" toml:"password"`
|
||||||
@ -10,6 +12,8 @@ type Todo struct {
|
|||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
|
LastModified time.Time `json:"last_modified"`
|
||||||
|
Deleted bool `json:"deleted"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StoreTodoRequest struct {
|
type StoreTodoRequest struct {
|
||||||
|
|||||||
@ -18,5 +18,7 @@ FROM gcr.io/distroless/base-debian12
|
|||||||
|
|
||||||
COPY --from=build /app/serverBinary .
|
COPY --from=build /app/serverBinary .
|
||||||
|
|
||||||
|
ENV XDG_CONFIG_HOME=/data
|
||||||
|
|
||||||
# Define the command to run the app when the container starts
|
# Define the command to run the app when the container starts
|
||||||
CMD ["./serverBinary"]
|
CMD ["./serverBinary"]
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
||||||
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common/jwt"
|
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common/jwt"
|
||||||
@ -36,14 +35,19 @@ func SyncHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
for _, clientTodo := range todoList.Todos {
|
for _, clientTodo := range todoList.Todos {
|
||||||
serverTodo, exists := serverTodos[clientTodo.Name]
|
serverTodo, exists := serverTodos[clientTodo.Name]
|
||||||
|
|
||||||
if !exists {
|
if clientTodo.Deleted && clientTodo.LastModified.After(serverTodo.LastModified) {
|
||||||
|
err = store.RemoveValueFromBucket(user, clientTodo.Name)
|
||||||
|
if handleError(w, http.StatusInternalServerError, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if !exists {
|
||||||
err = clientTodo.Store(store, user)
|
err = clientTodo.Store(store, user)
|
||||||
if handleError(w, http.StatusInternalServerError, err) {
|
if handleError(w, http.StatusInternalServerError, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
serverTodos[clientTodo.Name] = clientTodo
|
serverTodos[clientTodo.Name] = clientTodo
|
||||||
} else {
|
} else {
|
||||||
if !reflect.DeepEqual(serverTodo, clientTodo) {
|
if !serverTodo.IsEqual(clientTodo) {
|
||||||
response.MisMatchingTodos = append(response.MisMatchingTodos, common.MisMatchingTodo{
|
response.MisMatchingTodos = append(response.MisMatchingTodos, common.MisMatchingTodo{
|
||||||
ServerTodo: serverTodo,
|
ServerTodo: serverTodo,
|
||||||
LocalTodo: clientTodo,
|
LocalTodo: clientTodo,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user