Browse Source

feat: implemented complete TODO button

master
parent
commit
848cab106e
  1. 18
      pages/index.html
  2. 5
      scripts/api.js
  3. 4
      src/conf/conf.go
  4. 2
      src/main.go
  5. 50
      src/server/api.go
  6. 4
      src/server/api_test.go
  7. 2
      src/server/page.go
  8. 20
      src/server/server.go

18
pages/index.html

@ -209,24 +209,30 @@ document.addEventListener('DOMContentLoaded', async function() {
// Fetch and display TODOs
response = await get_todos(username, password);
let todosJson = await response.json();
let todos = [];
let completeButtonIDs = [];
if (response.ok && todosJson != null) {
let todosDiv = document.getElementById("todos");
todosJson.forEach((item) => {
console.log(item);
let todo_complete_btn_id = "btn-complete-" + String(item.id);
todos.push(item);
let todoCompleteBtnID = "btn-complete-" + String(item.id);
todosDiv.innerHTML += "<p>" + item.text +
"<small><button class='btn btn-success' id='" + todo_complete_btn_id + "'>" +
"<small><button class='btn btn-success' style='margin:10px;' id='" + todoCompleteBtnID + "'>" +
"Done</button></small></p>";
document.getElementById(todo_complete_btn_id).addEventListener("click", async (event) => {
response = await delete_todo(username, password, item);
completeButtonIDs.push(todoCompleteBtnID);
});
}
for (let i = 0; i < completeButtonIDs.length; i++) {
document.getElementById(completeButtonIDs[i]).addEventListener("click", async (event) => {
response = await delete_todo(username, password, todos[i].id);
if (response.ok) {
location.reload();
}
});
});
}
}, false)
</script>

5
scripts/api.js

@ -37,13 +37,12 @@ async function get_todo_groups(username, password) {
});
}
async function delete_todo(username, password, todo) {
return fetch("/api/todo", {
async function delete_todo(username, password, id) {
return fetch("/api/todo/"+String(id), {
method: "DELETE",
headers: {
"EnctyptedBase64": "false",
"Auth": username + "<-->" + password,
body: JSON.stringify(todo),
},
});
}

4
src/conf/conf.go

@ -11,7 +11,7 @@ type Conf struct {
CertFilePath string `json:"cert_file_path"`
KeyFilePath string `json:"key_file_path"`
BaseContentDir string `json:"base_content_dir"`
ProdDBPath string `json:"production_db"`
ProdDBName string `json:"production_db_name"`
}
// Creates a default server configuration
@ -21,7 +21,7 @@ func Default() Conf {
CertFilePath: "",
KeyFilePath: "",
BaseContentDir: ".",
ProdDBPath: "dela.db",
ProdDBName: "dela.db",
}
}

2
src/main.go

@ -44,8 +44,6 @@ func init() {
Conf.BaseContentDir = WDir
}
// Check if database exists and create it otherwise
logger.Info("[Init] Successful initializaion!")
}

50
src/server/api.go

@ -6,6 +6,8 @@ import (
"encoding/json"
"io"
"net/http"
"path"
"strconv"
"time"
)
@ -15,23 +17,6 @@ func (s *Server) UserEndpoint(w http.ResponseWriter, req *http.Request) {
// Delete an existing user
defer req.Body.Close()
// Read body
body, err := io.ReadAll(req.Body)
if err != nil {
logger.Warning("[Server] Failed to read request body to delete a user: %s", err)
http.Error(w, "Failed to read body", http.StatusInternalServerError)
return
}
// Unmarshal JSON
var newUser db.User
err = json.Unmarshal(body, &newUser)
if err != nil {
logger.Warning("[Server] Received invalid user JSON for deletion: %s", err)
http.Error(w, "Invalid user JSON", http.StatusBadRequest)
return
}
username := GetUsernameFromAuth(req)
// Check if auth data is valid
@ -43,7 +28,7 @@ func (s *Server) UserEndpoint(w http.ResponseWriter, req *http.Request) {
// It is, indeed, a user
// Delete with all TODOs
err = s.db.DeleteUserClean(username)
err := s.db.DeleteUserClean(username)
if err != nil {
logger.Error("[Server] Failed to delete %s: %s", username, err)
http.Error(w, "Failed to delete user or TODO contents", http.StatusInternalServerError)
@ -127,36 +112,28 @@ func (s *Server) TodoEndpoint(w http.ResponseWriter, req *http.Request) {
// Delete an existing TODO
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: %s", err)
http.Error(w, "Failed to read body", http.StatusInternalServerError)
// Check if this user actually exists and the password is valid
if !IsRequestAuthValid(req, s.db) {
http.Error(w, "Invalid user auth data", http.StatusForbidden)
return
}
// Unmarshal JSON
var todo db.Todo
err = json.Unmarshal(body, &todo)
// Obtain TODO ID
todoIDStr := path.Base(req.URL.Path)
todoID, err := strconv.ParseUint(todoIDStr, 10, 64)
if err != nil {
logger.Warning("[Server] Received invalid TODO JSON for deletion: %s", err)
http.Error(w, "Invalid TODO JSON", http.StatusBadRequest)
return
}
// Check if given user actually owns this TODO
if !IsRequestAuthValid(req, s.db) {
http.Error(w, "Invalid user auth data", http.StatusForbidden)
http.Error(w, "Invalid TODO ID", http.StatusBadRequest)
return
}
if !DoesUserOwnTodo(GetUsernameFromAuth(req), todo.ID, s.db) {
// 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
}
// Now delete
err = s.db.DeleteTodo(todo.ID)
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)
@ -164,6 +141,7 @@ func (s *Server) TodoEndpoint(w http.ResponseWriter, req *http.Request) {
}
// Success!
logger.Info("[Server] deleted TODO with ID %d", todoID)
w.WriteHeader(http.StatusOK)
case http.MethodPost:

4
src/server/api_test.go

@ -18,12 +18,12 @@ func TestApi(t *testing.T) {
// Create a new server
config := conf.Default()
config.BaseContentDir = "../../"
config.ProdDBPath = filepath.Join(os.TempDir(), "dela_test_db.db")
config.ProdDBName = filepath.Join(os.TempDir(), "dela_test_db.db")
server, err := New(config)
if err != nil {
t.Fatalf("failed to create a new server: %s", err)
}
defer os.Remove(config.ProdDBPath)
defer os.Remove(config.ProdDBName)
go func() {
time.Sleep(time.Second * 5)

2
src/server/page.go

@ -1,7 +1,6 @@
package server
import (
"Unbewohnte/dela/logger"
"html/template"
"path/filepath"
)
@ -13,7 +12,6 @@ func getPage(pagesDir string, basePageName string, pageName string) (*template.T
filepath.Join(pagesDir, pageName),
)
if err != nil {
logger.Error("Failed to parse page files (pagename is \"%s\"): %s", pageName, err)
return nil, err
}

20
src/server/server.go

@ -49,10 +49,10 @@ func New(config conf.Conf) (*Server, error) {
}
// get database working
serverDB, err := db.FromFile(config.ProdDBPath)
serverDB, err := db.FromFile(filepath.Join(config.BaseContentDir, config.ProdDBName))
if err != nil {
// Create one then
serverDB, err = db.Create(config.ProdDBPath)
serverDB, err = db.Create(filepath.Join(config.BaseContentDir, config.ProdDBName))
if err != nil {
logger.Error("Failed to create a new database: %s", err)
return nil, err
@ -95,7 +95,7 @@ func New(config conf.Conf) (*Server, error) {
filepath.Join(server.config.BaseContentDir, PagesDirName), "base.html", "index.html",
)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
http.Error(w, "Page processing error", http.StatusInternalServerError)
}
requestedPage.ExecuteTemplate(w, "index.html", nil)
@ -109,22 +109,14 @@ func New(config conf.Conf) (*Server, error) {
if err == nil {
requestedPage.ExecuteTemplate(w, req.URL.Path[1:]+".html", nil)
} else {
// Redirect to the index
index, err := getPage(
filepath.Join(server.config.BaseContentDir, PagesDirName),
"base.html",
req.URL.Path[1:]+".html",
)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
index.ExecuteTemplate(w, "index.html", nil)
http.Error(w, "Page processing error", http.StatusInternalServerError)
}
}
})
mux.HandleFunc("/api/user", server.UserEndpoint)
mux.HandleFunc("/api/todo", server.TodoEndpoint)
mux.HandleFunc("/api/todo/", server.TodoEndpoint)
// mux.HandleFunc("/api/group", server.TodoGroupEndpoint)
server.http.Handler = mux

Loading…
Cancel
Save