diff --git a/pages/base.html b/pages/base.html index 3fa0cc4..10b3755 100644 --- a/pages/base.html +++ b/pages/base.html @@ -51,7 +51,7 @@ } // Check if auth info is indeed valid - let response = await get_user(username, password); + let response = await getUser(username, password); if (response.ok) { let barAuth = document.getElementById("bar-auth"); barAuth.innerHTML = "" + username + "" + " | "; diff --git a/pages/index.html b/pages/index.html index d8777f8..7afd54b 100644 --- a/pages/index.html +++ b/pages/index.html @@ -42,7 +42,7 @@ document.addEventListener('DOMContentLoaded', async function() { newTodoTextInput.value = ""; // Make a request - let response = await post_new_todo(username, password, {text: newTodoText}); + let response = await postNewTodo(username, password, {text: newTodoText}); if (response.ok) { location.reload(); } @@ -50,10 +50,12 @@ document.addEventListener('DOMContentLoaded', async function() { // Fetch and display TODOs - response = await get_todos(username, password); + response = await getTodos(username, password); let todosJson = await response.json(); let todos = []; let completeButtonIDs = []; + let deleteButtonIDs = []; + if (response.ok && todosJson != null) { let todosDiv = document.getElementById("todos"); todosJson.forEach((item) => { @@ -72,16 +74,30 @@ document.addEventListener('DOMContentLoaded', async function() { todosDiv.innerHTML += "" + item.text + "" + "" + " " + timeCreated.getDate() + "/" + timeCreated.getMonth() + "/" + timeCreated.getFullYear() + "" + ""; + ""; completeButtonIDs.push(todoCompleteBtnID); + deleteButtonIDs.push(todoDeleteBtnID); }); } + // Loop over all buttons (doesn't matter which ones because the amounts are equal) for (let i = 0; i < completeButtonIDs.length; i++) { + // Done button document.getElementById(completeButtonIDs[i]).addEventListener("click", async (event) => { - response = await delete_todo(username, password, todos[i].id); + // Mark as done + todos[i].isDone = true; + // Update + response = await updateTodo(username, password, todos[i].id, todos[i]); + if (response.ok) { + location.reload(); + } + }); + + // Delete button + document.getElementById(deleteButtonIDs[i]).addEventListener("click", async (event) => { + response = await deleteTodo(username, password, todos[i].id); if (response.ok) { location.reload(); } diff --git a/scripts/api.js b/scripts/api.js index d70eb5f..9b43b55 100644 --- a/scripts/api.js +++ b/scripts/api.js @@ -3,7 +3,7 @@ */ -async function post_new_todo(username, password, new_todo) { +async function postNewTodo(username, password, new_todo) { return fetch("/api/todo", { method: "POST", headers: { @@ -16,7 +16,7 @@ async function post_new_todo(username, password, new_todo) { } -async function get_todos(username, password) { +async function getTodos(username, password) { return fetch("/api/todo", { method: "GET", headers: { @@ -27,7 +27,7 @@ async function get_todos(username, password) { } -async function get_todo_groups(username, password) { +async function getTodoGroups(username, password) { return fetch("/api/group", { method: "GET", headers: { @@ -37,7 +37,7 @@ async function get_todo_groups(username, password) { }); } -async function delete_todo(username, password, id) { +async function deleteTodo(username, password, id) { return fetch("/api/todo/"+String(id), { method: "DELETE", headers: { @@ -47,7 +47,18 @@ async function delete_todo(username, password, id) { }); } -async function get_user(username, password) { +async function updateTodo(username, password, id, updatedTodo) { + return fetch("/api/todo/"+String(id), { + method: "POST", + headers: { + "EncryptedBase64": "false", + "Auth": username + "<-->" + password, + }, + body: JSON.stringify(updatedTodo), + }); +} + +async function getUser(username, password) { return fetch("/api/user", { method: "GET", headers: { diff --git a/src/server/api.go b/src/server/api.go index 725fc96..bc79b65 100644 --- a/src/server/api.go +++ b/src/server/api.go @@ -124,7 +124,7 @@ func (s *Server) UserEndpoint(w http.ResponseWriter, req *http.Request) { } } -func (s *Server) TodoEndpoint(w http.ResponseWriter, req *http.Request) { +func (s *Server) SpecificTodoEndpoint(w http.ResponseWriter, req *http.Request) { switch req.Method { case http.MethodDelete: // Delete an existing TODO @@ -150,35 +150,92 @@ func (s *Server) TodoEndpoint(w http.ResponseWriter, req *http.Request) { return } - // Mark TODO as done and assign a completion time - updatedTodo, err := s.db.GetTodo(todoID) + // // Mark TODO as done and assign a completion time + // updatedTodo, err := s.db.GetTodo(todoID) + // if err != nil { + // logger.Error("[Server] Failed to get todo with id %d for marking completion: %s", todoID, err) + // http.Error(w, "TODO retrieval error", http.StatusInternalServerError) + // return + // } + // updatedTodo.IsDone = true + // updatedTodo.CompletionTimeUnix = uint64(time.Now().Unix()) + + // err = s.db.UpdateTodo(todoID, *updatedTodo) + // if err != nil { + // logger.Error("[Server] Failed to update TODO with id %d: %s", todoID, err) + // http.Error(w, "Failed to update TODO information", http.StatusInternalServerError) + // return + // } + + // Now delete + err = s.db.DeleteTodo(todoID) if err != nil { - logger.Error("[Server] Failed to get todo with id %d for marking completion: %s", todoID, err) - http.Error(w, "TODO retrieval error", http.StatusInternalServerError) + logger.Error("[Server] Failed to delete %s's TODO: %s", GetUsernameFromAuth(req), err) + http.Error(w, "Failed to delete TODO", http.StatusInternalServerError) return } - updatedTodo.IsDone = true - updatedTodo.CompletionTimeUnix = uint64(time.Now().Unix()) - err = s.db.UpdateTodo(todoID, *updatedTodo) + // Success! + logger.Info("[Server] Deleted TODO with ID %d", todoID) + w.WriteHeader(http.StatusOK) + + case http.MethodPost: + // Change TODO information + + // Check authentication information + if !IsRequestAuthValid(req, s.db) { + http.Error(w, "Invalid user auth data", http.StatusForbidden) + return + } + + // Obtain TODO ID + todoIDStr := path.Base(req.URL.Path) + todoID, err := strconv.ParseUint(todoIDStr, 10, 64) if err != nil { - logger.Error("[Server] Failed to update TODO with id %d: %s", todoID, err) - http.Error(w, "Failed to update TODO information", http.StatusInternalServerError) + http.Error(w, "Invalid TODO ID", http.StatusBadRequest) return } - // Now delete - // err = s.db.DeleteTodo(todoID) - // if err != nil { - // logger.Error("[Server] Failed to delete %s's TODO: %s", GetUsernameFromAuth(req), err) - // http.Error(w, "Failed to delete TODO", http.StatusInternalServerError) - // return - // } + // Check if the user owns this TODO + if !DoesUserOwnTodo(GetUsernameFromAuth(req), todoID, s.db) { + http.Error(w, "You don't own this TODO", http.StatusForbidden) + return + } - // Success! - logger.Info("[Server] updated (marked as done) TODO with ID %d", todoID) + // Read body + body, err := io.ReadAll(req.Body) + if err != nil { + logger.Warning("[Server] Failed to read request body to possibly update a TODO: %s", err) + http.Error(w, "Failed to read body", http.StatusInternalServerError) + return + } + + // Unmarshal JSON + var updatedTodo db.Todo + err = json.Unmarshal(body, &updatedTodo) + if err != nil { + logger.Warning("[Server] Received invalid TODO JSON in order to update: %s", err) + http.Error(w, "Invalid TODO JSON", http.StatusBadRequest) + return + } + + // Update. (Creation date, owner username and an ID do not change) + err = s.db.UpdateTodo(todoID, updatedTodo) + if err != nil { + logger.Warning("[Server] Failed to update TODO: %s", err) + http.Error(w, "Failed to update", http.StatusBadRequest) + return + } w.WriteHeader(http.StatusOK) + logger.Info("[Server] Updated TODO with ID %d", todoID) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func (s *Server) TodoEndpoint(w http.ResponseWriter, req *http.Request) { + switch req.Method { case http.MethodPost: // Create a new TODO defer req.Body.Close() @@ -218,6 +275,7 @@ func (s *Server) TodoEndpoint(w http.ResponseWriter, req *http.Request) { // Success! w.WriteHeader(http.StatusOK) logger.Info("[Server] Created a new TODO for %s", newTodo.OwnerUsername) + case http.MethodGet: // Retrieve TODO information // Check authentication information @@ -226,7 +284,7 @@ func (s *Server) TodoEndpoint(w http.ResponseWriter, req *http.Request) { return } - // Get TODO + // Get all user TODOs todos, err := s.db.GetAllUserTodos(GetUsernameFromAuth(req)) if err != nil { http.Error(w, "Failed to get TODOs", http.StatusInternalServerError) @@ -243,42 +301,6 @@ func (s *Server) TodoEndpoint(w http.ResponseWriter, req *http.Request) { // Send out w.Header().Add("Content-Type", "application/json") w.Write(todosBytes) - - // case http.MethodPatch: - // // Change TODO due date and text - - // // Check authentication information - // if !IsRequestAuthValid(req, s.db) { - // http.Error(w, "Invalid user auth data", http.StatusForbidden) - // return - // } - - // // Read body - // body, err := io.ReadAll(req.Body) - // if err != nil { - // logger.Warning("[Server] Failed to read request body to possibly update a TODO: %s", err) - // http.Error(w, "Failed to read body", http.StatusInternalServerError) - // return - // } - - // // Unmarshal JSON - // var todo db.Todo - // err = json.Unmarshal(body, &todo) - // if err != nil { - // logger.Warning("[Server] Received invalid TODO JSON in order to update: %s", err) - // http.Error(w, "Invalid TODO JSON", http.StatusBadRequest) - // return - // } - - // // TODO - // err = s.db.UpdateTodo(todo.ID, todo) - // if err != nil { - // logger.Warning("[Server] Failed to update TODO: %s", err) - // http.Error(w, "Failed to update", http.StatusBadRequest) - // return - // } - - // w.WriteHeader(http.StatusOK) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } diff --git a/src/server/server.go b/src/server/server.go index b0759ac..bb53f27 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -133,7 +133,7 @@ func New(config conf.Conf) (*Server, error) { }) mux.HandleFunc("/api/user", server.UserEndpoint) mux.HandleFunc("/api/todo", server.TodoEndpoint) - mux.HandleFunc("/api/todo/", server.TodoEndpoint) + mux.HandleFunc("/api/todo/", server.SpecificTodoEndpoint) // mux.HandleFunc("/api/group", server.TodoGroupEndpoint)