kleinTodo/server/handler/syncHandler.go
Darius klein 7cf6eabbdd
All checks were successful
build and deploy kleinTodo / build (push) Successful in 51s
build and deploy kleinTodo / build (release) Successful in 1m24s
ci: add integration tests and pipeline step
Co-authored-by: Junie <junie@jetbrains.com>
2026-04-04 13:55:37 +02:00

111 lines
3.2 KiB
Go

package handler
import (
"encoding/json"
"fmt"
"net/http"
"time"
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common/jwt"
)
func SyncHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("SyncHandler: starting request processing...")
// 1. Setup: Auth, Decode, Database Connection
user, err := jwt.GetVerifiedUser(r)
if handleError(w, http.StatusUnauthorized, err) {
return
}
fmt.Printf("SyncHandler: authenticated user %s\n", user)
var todoList common.TodoList
if handleError(w, http.StatusBadRequest, json.NewDecoder(r.Body).Decode(&todoList)) {
return
}
fmt.Printf("SyncHandler: received %d todos from client\n", len(todoList.Todos))
store, err := common.GetServerDataStore()
if handleError(w, http.StatusInternalServerError, err) {
return
}
fmt.Println("SyncHandler: connected to datastore")
// 2. Load Server State
serverTodos := store.GetTodoMap(user)
fmt.Printf("SyncHandler: loaded %d server todos\n", len(serverTodos))
response := common.SyncResponse{
SyncedTodos: []common.Todo{},
MisMatchingTodos: []common.MisMatchingTodo{},
}
fmt.Println("SyncHandler: processing client todos...")
// 3. Process Client Updates
for _, clientTodo := range todoList.Todos {
// Default to "Now"
if clientTodo.LastModified.IsZero() {
clientTodo.LastModified = time.Now().UTC()
}
serverTodo, exists := serverTodos[clientTodo.Id]
// New Item (Does not exist on server)
if !exists {
if clientTodo.Deleted {
continue // Ignore deleted items that don't exist
}
if handleError(w, http.StatusInternalServerError, (&clientTodo).Store(store, user)) {
return
}
// Add to map so it gets returned in the "Synced" list
serverTodos[clientTodo.Id] = clientTodo
continue
}
// Item is deleted (Client wants to delete)
if clientTodo.Deleted {
// Only delete if client is newer than server
if clientTodo.LastModified.After(serverTodo.LastModified) {
if handleError(w, http.StatusInternalServerError, store.RemoveValueFromBucket(user, clientTodo.Id)) {
return
}
// Remove from map so it is NOT returned in the "Synced" list
delete(serverTodos, clientTodo.Id)
}
continue
}
// Item is updated (Item exists and is not deleted) ---
if serverTodo.IsEqual(clientTodo) {
continue
}
isMergeable := serverTodo.IsEqualIgnoringStatus(clientTodo) && clientTodo.LastModified.After(serverTodo.LastModified)
if isMergeable {
if handleError(w, http.StatusInternalServerError, (&clientTodo).Store(store, user)) {
return
}
serverTodos[clientTodo.Id] = clientTodo
} else {
response.MisMatchingTodos = append(response.MisMatchingTodos, common.MisMatchingTodo{
ServerTodo: serverTodo,
LocalTodo: clientTodo,
})
delete(serverTodos, clientTodo.Id)
}
}
fmt.Println("SyncHandler: building final response...")
// Build Final Response
for _, todo := range serverTodos {
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")
handleError(w, http.StatusInternalServerError, json.NewEncoder(w).Encode(response))
}