From 7aa3e52ddbc1e0ff58035364c8b98d5ebf76547c Mon Sep 17 00:00:00 2001 From: Unbewohnte Date: Wed, 23 Oct 2024 20:12:51 +0300 Subject: [PATCH] Feature: Category deletion; UI changes --- pages/base.html | 29 +++++----- pages/category.html | 44 ++++++++------ pages/index.html | 74 ++++++++++++++++-------- pages/login.html | 2 +- scripts/api.js | 8 +++ scripts/auth.js | 14 ++++- src/server/endpoints.go | 90 +++++++++++++++++++++++------ src/server/server.go | 1 + static/images/arrows-fullscreen.svg | 3 + 9 files changed, 189 insertions(+), 76 deletions(-) create mode 100644 static/images/arrows-fullscreen.svg diff --git a/pages/base.html b/pages/base.html index 7424324..9d59350 100644 --- a/pages/base.html +++ b/pages/base.html @@ -45,24 +45,23 @@ diff --git a/pages/category.html b/pages/category.html index dae6eeb..28135eb 100644 --- a/pages/category.html +++ b/pages/category.html @@ -9,23 +9,27 @@ +
@@ -35,7 +39,8 @@
- + +
@@ -136,7 +141,7 @@ document.addEventListener('DOMContentLoaded', async function() { // "Add" button document.getElementById("new-todo-submit").addEventListener("click", async (event) => { - let newTodoTextInput = document.getElementById("new-todo-text"); + let newTodoTextInput = document.getElementById("new-todo-text"); let newTodoText = newTodoTextInput.value; if (newTodoText.length < 1) { newTodoTextInput.setCustomValidity("At least one character is needed!"); @@ -146,10 +151,15 @@ document.addEventListener('DOMContentLoaded', async function() { } newTodoTextInput.value = ""; + let newTodoDueInput = document.getElementById("new-todo-due"); + let dueTimeStamp = Date.parse(newTodoDueInput.value) / 1000; + let groupId = document.getElementById("categoryId").innerText; // Make a request - let response = await postNewTodo({text: newTodoText, groupId: Number(groupId)}); + let response = await postNewTodo( + {text: newTodoText, groupId: Number(groupId), dueUnix: Number(dueTimeStamp)} + ); if (response.ok) { location.reload(); } diff --git a/pages/index.html b/pages/index.html index 48f447c..d78f408 100644 --- a/pages/index.html +++ b/pages/index.html @@ -3,49 +3,70 @@ {{ define "content" }} -
+
- -
{{ end }} \ No newline at end of file diff --git a/pages/login.html b/pages/login.html index d500c0a..e96fba6 100644 --- a/pages/login.html +++ b/pages/login.html @@ -44,7 +44,7 @@ async function logIn() { password = sha256(password); // Check if auth info is indeed valid - let response = await getUser(); + let response = await doLogin({login: login, password: password}); if (response.ok) { window.location.replace("/"); } else { diff --git a/scripts/api.js b/scripts/api.js index 65a805c..be99b61 100644 --- a/scripts/api.js +++ b/scripts/api.js @@ -26,6 +26,10 @@ async function postNewUser(newUser) { return post("/api/user/create", newUser) } +async function doLogin(userInformation) { + return post("/api/user/login", userInformation) +} + async function get(url) { return fetch(url, { method: "GET", @@ -66,6 +70,10 @@ async function deleteTodo(id) { return del("/api/todo/delete/"+id); } +async function deleteCategory(id) { + return del("/api/group/delete/"+id); +} + async function update(url, json) { return post(url, json); } diff --git a/scripts/auth.js b/scripts/auth.js index d35fadf..d0db963 100644 --- a/scripts/auth.js +++ b/scripts/auth.js @@ -1,7 +1,19 @@ /* - 2024 Kasyanov Nikolay Alexeyevich (Unbewohnte) + 2024 Kasyanov Nikolay Alexeevich (Unbewohnte) */ +function getCookie(name){ + return document.cookie.split(';').some(c => { + return c.trim().startsWith(name + '='); + }); +} + +function forgetAuthInfo() { + if(getCookie("auth")) { + document.cookie = "auth" + "=" + ";expires=Thu, 01 Jan 1970 00:00:01 GMT"; + } +} + /** * [js-sha256]{@link https://github.com/emn178/js-sha256} * diff --git a/src/server/endpoints.go b/src/server/endpoints.go index fe8651f..76ca9a8 100644 --- a/src/server/endpoints.go +++ b/src/server/endpoints.go @@ -97,6 +97,55 @@ func (s *Server) EndpointUserCreate(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) } +func (s *Server) EndpointUserLogin(w http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Retrieve user data + defer req.Body.Close() + + contents, err := io.ReadAll(req.Body) + if err != nil { + logger.Error("[Server][EndpointUserLogin] Failed to read request body: %s", err) + http.Error(w, "Failed to read request body", http.StatusInternalServerError) + return + } + + var user db.User + err = json.Unmarshal(contents, &user) + if err != nil { + logger.Error("[Server][EndpointUserLogin] Failed to unmarshal user data: %s", err) + http.Error(w, "User JSON unmarshal error", http.StatusInternalServerError) + return + } + + // Check auth data + userDB, err := s.db.GetUser(user.Login) + if err != nil { + logger.Error("[Server][EndpointUserLogin] Failed to fetch user information from DB: %s", err) + http.Error(w, "Failed to fetch user information", http.StatusInternalServerError) + return + } + + if user.Password != userDB.Password { + http.Error(w, "Failed auth", http.StatusForbidden) + return + } + + // Send cookie + http.SetCookie(w, &http.Cookie{ + Name: "auth", + Value: fmt.Sprintf("%s:%s", user.Login, user.Password), + SameSite: http.SameSiteStrictMode, + HttpOnly: false, + Path: "/", + Secure: true, + }) + w.WriteHeader(http.StatusOK) +} + func (s *Server) EndpointUserUpdate(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) @@ -436,43 +485,47 @@ func (s *Server) EndpointTodoGroupDelete(w http.ResponseWriter, req *http.Reques // Delete an existing group defer req.Body.Close() - // Read body - body, err := io.ReadAll(req.Body) - if err != nil { - logger.Warning("[Server] Failed to read request body to possibly delete a TODO group: %s", err) - http.Error(w, "Failed to read body", http.StatusInternalServerError) + // Check if given user actually owns this group + if !IsUserAuthorizedReq(req, s.db) { + http.Error(w, "Invalid user auth data", http.StatusForbidden) return } - // Unmarshal JSON - var group db.TodoGroup - err = json.Unmarshal(body, &group) + // Get group ID + groupId, err := strconv.ParseUint(path.Base(req.URL.Path), 10, 64) if err != nil { - logger.Warning("[Server] Received invalid TODO group JSON for deletion: %s", err) - http.Error(w, "Invalid TODO group JSON", http.StatusBadRequest) + http.Error(w, "Bad Category ID", http.StatusBadRequest) return } - // Check if given user actually owns this group - if !IsUserAuthorizedReq(req, s.db) { - http.Error(w, "Invalid user auth data", http.StatusForbidden) + if !s.db.DoesUserOwnGroup(groupId, GetLoginFromReq(req)) { + http.Error(w, "You don't own this group", http.StatusForbidden) return } - if !s.db.DoesUserOwnGroup(group.ID, GetLoginFromReq(req)) { - http.Error(w, "You don't own this group", http.StatusForbidden) + groupDB, err := s.db.GetTodoGroup(groupId) + if err != nil { + logger.Error("[Server][EndpointGroupDelete] Failed to fetch TODO group with Id %d: %s", groupId, err) + http.Error(w, "Failed to retrieve TODO group", http.StatusInternalServerError) return } - // Now delete - err = s.db.DeleteTodoGroup(group.ID) + if !groupDB.Removable { + // Not removable + http.Error(w, "Not removable", http.StatusBadRequest) + return + } + + // Delete + err = s.db.DeleteTodoGroup(groupId) if err != nil { - logger.Error("[Server] Failed to delete %s's TODO group: %s", GetLoginFromReq(req), err) + logger.Error("[Server][EndpointGroupDelete] Failed to delete %s's TODO group: %s", GetLoginFromReq(req), err) http.Error(w, "Failed to delete TODO group", http.StatusInternalServerError) return } // Success! + logger.Info("[Server][EndpointGroupDelete] Deleted group ID: %d for %s", groupId, GetLoginFromReq(req)) w.WriteHeader(http.StatusOK) } @@ -505,6 +558,7 @@ func (s *Server) EndpointTodoGroupCreate(w http.ResponseWriter, req *http.Reques // Add group to the database newGroup.OwnerLogin = GetLoginFromReq(req) newGroup.TimeCreatedUnix = uint64(time.Now().Unix()) + newGroup.Removable = true err = s.db.CreateTodoGroup(newGroup) if err != nil { http.Error(w, "Failed to create TODO group", http.StatusInternalServerError) diff --git a/src/server/server.go b/src/server/server.go index 570e538..91bc299 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -192,6 +192,7 @@ func New(config conf.Conf) (*Server, error) { mux.HandleFunc("/api/user/delete", server.EndpointUserDelete) // Non specific mux.HandleFunc("/api/user/update", server.EndpointUserUpdate) // Non specific mux.HandleFunc("/api/user/create", server.EndpointUserCreate) // Non specific + mux.HandleFunc("/api/user/login", server.EndpointUserLogin) // Non specific mux.HandleFunc("/api/todo/create", server.EndpointTodoCreate) // Non specific mux.HandleFunc("/api/todo/get", server.EndpointUserTodosGet) // Non specific mux.HandleFunc("/api/todo/delete/", server.EndpointTodoDelete) // Specific diff --git a/static/images/arrows-fullscreen.svg b/static/images/arrows-fullscreen.svg new file mode 100644 index 0000000..7633e3f --- /dev/null +++ b/static/images/arrows-fullscreen.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file