Browse Source

feat: implemented complete TODO button

master
parent
commit
848cab106e
  1. 26
      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. 4
      src/server/page.go
  8. 20
      src/server/server.go

26
pages/index.html

@ -209,23 +209,29 @@ document.addEventListener('DOMContentLoaded', async function() {
// Fetch and display TODOs // Fetch and display TODOs
response = await get_todos(username, password); response = await get_todos(username, password);
let todosJson = await response.json(); let todosJson = await response.json();
let todos = [];
let completeButtonIDs = [];
if (response.ok && todosJson != null) { if (response.ok && todosJson != null) {
let todosDiv = document.getElementById("todos"); let todosDiv = document.getElementById("todos");
todosJson.forEach((item) => { todosJson.forEach((item) => {
console.log(item); console.log(item);
todos.push(item);
let todo_complete_btn_id = "btn-complete-" + String(item.id); let todoCompleteBtnID = "btn-complete-" + String(item.id);
todosDiv.innerHTML += "<p>" + item.text + 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>"; "Done</button></small></p>";
document.getElementById(todo_complete_btn_id).addEventListener("click", async (event) => { completeButtonIDs.push(todoCompleteBtnID);
response = await delete_todo(username, password, item); });
if (response.ok) { }
location.reload();
} 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) }, false)

5
scripts/api.js

@ -37,13 +37,12 @@ async function get_todo_groups(username, password) {
}); });
} }
async function delete_todo(username, password, todo) { async function delete_todo(username, password, id) {
return fetch("/api/todo", { return fetch("/api/todo/"+String(id), {
method: "DELETE", method: "DELETE",
headers: { headers: {
"EnctyptedBase64": "false", "EnctyptedBase64": "false",
"Auth": username + "<-->" + password, "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"` CertFilePath string `json:"cert_file_path"`
KeyFilePath string `json:"key_file_path"` KeyFilePath string `json:"key_file_path"`
BaseContentDir string `json:"base_content_dir"` BaseContentDir string `json:"base_content_dir"`
ProdDBPath string `json:"production_db"` ProdDBName string `json:"production_db_name"`
} }
// Creates a default server configuration // Creates a default server configuration
@ -21,7 +21,7 @@ func Default() Conf {
CertFilePath: "", CertFilePath: "",
KeyFilePath: "", KeyFilePath: "",
BaseContentDir: ".", BaseContentDir: ".",
ProdDBPath: "dela.db", ProdDBName: "dela.db",
} }
} }

2
src/main.go

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

50
src/server/api.go

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

4
src/server/api_test.go

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

4
src/server/page.go

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

20
src/server/server.go

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

Loading…
Cancel
Save