Browse Source

feat: Real update method; Delete TODO button functionality

master
parent
commit
a7a6ddb5d1
  1. 2
      pages/base.html
  2. 26
      pages/index.html
  3. 21
      scripts/api.js
  4. 134
      src/server/api.go
  5. 2
      src/server/server.go

2
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 = "<b>" + username + "</b>" + " | ";

26
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 += "<tr><td>" + item.text + "</td>" +
"<td>" + " " + timeCreated.getDate() + "/" + timeCreated.getMonth() + "/" + timeCreated.getFullYear() + "</td>" +
"<td><button class='btn btn-success' id='" + todoCompleteBtnID + "'>" +
"<img src='/static/images/check.svg'></button><button class='btn btn-danger id='"
+ todoDeleteBtnID + "><img src='/static/images/trash3-fill.svg'></button></td></tr>";
"<img src='/static/images/check.svg'></button><button class='btn btn-danger' id='" +
todoDeleteBtnID + "'><img src='/static/images/trash3-fill.svg'></button></td></tr>";
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();
}

21
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: {

134
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)
}

2
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)

Loading…
Cancel
Save