2025-07-26 23:31:00 +02:00
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"net/http"
|
2026-01-18 14:08:34 +01:00
|
|
|
"time"
|
2025-08-23 13:28:48 +02:00
|
|
|
|
|
|
|
|
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common"
|
|
|
|
|
"gitea.kleinsense.nl/DariusKlein/kleinTodo/common/jwt"
|
2025-07-26 23:31:00 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func SyncHandler(w http.ResponseWriter, r *http.Request) {
|
2026-01-18 14:27:16 +01:00
|
|
|
// 1. Setup: Auth, Decode, Database Connection
|
2025-07-26 23:31:00 +02:00
|
|
|
user, err := jwt.GetVerifiedUser(r)
|
|
|
|
|
if handleError(w, http.StatusUnauthorized, err) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var todoList common.TodoList
|
2026-01-18 14:27:16 +01:00
|
|
|
if handleError(w, http.StatusBadRequest, json.NewDecoder(r.Body).Decode(&todoList)) {
|
2025-07-26 23:31:00 +02:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
store, err := common.GetTodoDataStore()
|
|
|
|
|
if handleError(w, http.StatusInternalServerError, err) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 14:27:16 +01:00
|
|
|
// 2. Load Server State
|
2025-08-23 21:51:33 +02:00
|
|
|
serverTodos := store.GetTodoMap(user)
|
2026-01-18 14:27:16 +01:00
|
|
|
response := common.SyncResponse{
|
2025-07-26 23:31:00 +02:00
|
|
|
SyncedTodos: []common.Todo{},
|
|
|
|
|
MisMatchingTodos: []common.MisMatchingTodo{},
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 14:27:16 +01:00
|
|
|
// 3. Process Client Updates
|
2025-07-26 23:31:00 +02:00
|
|
|
for _, clientTodo := range todoList.Todos {
|
2026-01-18 14:27:16 +01:00
|
|
|
|
|
|
|
|
// Default to "Now"
|
2026-01-18 14:08:34 +01:00
|
|
|
if clientTodo.LastModified.IsZero() {
|
|
|
|
|
clientTodo.LastModified = time.Now().UTC()
|
|
|
|
|
}
|
2026-01-18 14:27:16 +01:00
|
|
|
|
2026-01-12 21:20:59 +01:00
|
|
|
serverTodo, exists := serverTodos[clientTodo.Id]
|
2025-07-26 23:31:00 +02:00
|
|
|
|
2026-01-18 14:27:16 +01:00
|
|
|
// New Item (Does not exist on server)
|
2026-01-11 19:04:18 +01:00
|
|
|
if !exists {
|
|
|
|
|
if clientTodo.Deleted {
|
2026-01-18 14:27:16 +01:00
|
|
|
continue // Ignore deleted items that don't exist
|
2026-01-11 18:40:38 +01:00
|
|
|
}
|
2026-01-18 14:27:16 +01:00
|
|
|
|
|
|
|
|
if handleError(w, http.StatusInternalServerError, clientTodo.Store(store, user)) {
|
2025-07-26 23:31:00 +02:00
|
|
|
return
|
|
|
|
|
}
|
2026-01-18 14:27:16 +01:00
|
|
|
// Add to map so it gets returned in the "Synced" list
|
2026-01-12 21:20:59 +01:00
|
|
|
serverTodos[clientTodo.Id] = clientTodo
|
2026-01-11 19:04:18 +01:00
|
|
|
continue
|
|
|
|
|
}
|
2026-01-18 14:27:16 +01:00
|
|
|
|
|
|
|
|
// Item is deleted (Client wants to delete)
|
2026-01-11 19:04:18 +01:00
|
|
|
if clientTodo.Deleted {
|
2026-01-18 14:27:16 +01:00
|
|
|
// Only delete if client is newer than server
|
2026-01-11 19:04:18 +01:00
|
|
|
if clientTodo.LastModified.After(serverTodo.LastModified) {
|
2026-01-18 14:27:16 +01:00
|
|
|
if handleError(w, http.StatusInternalServerError, store.RemoveValueFromBucket(user, clientTodo.Id)) {
|
2026-01-11 19:04:18 +01:00
|
|
|
return
|
|
|
|
|
}
|
2026-01-18 14:27:16 +01:00
|
|
|
// Remove from map so it is NOT returned in the "Synced" list
|
2026-01-12 21:20:59 +01:00
|
|
|
delete(serverTodos, clientTodo.Id)
|
2026-01-11 19:04:18 +01:00
|
|
|
}
|
2026-01-18 14:27:16 +01:00
|
|
|
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
|
2025-07-26 23:31:00 +02:00
|
|
|
}
|
2026-01-18 14:27:16 +01:00
|
|
|
serverTodos[clientTodo.Id] = clientTodo
|
|
|
|
|
} else {
|
|
|
|
|
response.MisMatchingTodos = append(response.MisMatchingTodos, common.MisMatchingTodo{
|
|
|
|
|
ServerTodo: serverTodo,
|
|
|
|
|
LocalTodo: clientTodo,
|
|
|
|
|
})
|
|
|
|
|
delete(serverTodos, clientTodo.Id)
|
2025-07-26 23:31:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 14:27:16 +01:00
|
|
|
// Build Final Response
|
2025-07-26 23:31:00 +02:00
|
|
|
for _, todo := range serverTodos {
|
|
|
|
|
response.SyncedTodos = append(response.SyncedTodos, todo)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2026-01-18 14:27:16 +01:00
|
|
|
handleError(w, http.StatusInternalServerError, json.NewEncoder(w).Encode(response))
|
2025-07-26 23:31:00 +02:00
|
|
|
}
|