diff --git a/main.go b/main.go index 2c14f33..f7adbca 100644 --- a/main.go +++ b/main.go @@ -8,12 +8,11 @@ import ( ) func main() { - go func() { - err := db.InitDB() - if err != nil { - log.Fatalf("Failed to initialize database: %v", err) - } - }() - + err := db.InitDB() + if err != nil { + log.Fatalf("Failed to initialize database: %v", err) + } + defer db.Close() + server.Run() } diff --git a/pkg/api/addtask.go b/pkg/api/addtask.go index 580a370..bc89460 100644 --- a/pkg/api/addtask.go +++ b/pkg/api/addtask.go @@ -2,15 +2,15 @@ package api import ( "fmt" + "log" + "net/http" + "strconv" "time" "github.com/ArtyomGaribyan/Task-Scheduler/pkg/db" ) -func checkTask (task *db.Task) error { -if task == nil { - return fmt.Errorf("task is nil") - } +func checkTask(task *db.Task) error { if task.Title == "" { return fmt.Errorf("title is required") } @@ -21,7 +21,7 @@ if task == nil { } else { _, err := time.Parse(db.DateLayout, task.Date) if err != nil { - return fmt.Errorf("invalid date format: %v", err) + return fmt.Errorf("invalid date format: %w", err) } } @@ -30,7 +30,7 @@ if task == nil { if task.Repeat != "" { task.Date, err = db.NextDate(now, task.Date, task.Repeat) if err != nil { - return err + return fmt.Errorf("next date calculation error: %w", err) } } else { task.Date = today @@ -39,16 +39,26 @@ if task == nil { return nil } -func addTaskHandler(task *db.Task) (int64, error) { - err := checkTask(task) +func AddTaskHandler(w http.ResponseWriter, task db.Task) { + log.Println("Adding task:", task) + err := checkTask(&task) if err != nil { - return 0, err + Error := db.Task{Error: "Validation error for adding task: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusBadRequest) + writeJson(w, Error) + return } id, err := db.AddTask(task) if err != nil { - return 0, err + Error := db.Task{Error: "Error for adding task: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, Error) + return } - - return id, nil + task.ID = strconv.Itoa(int(id)) + log.Println("Added task ID:", task.ID) + writeJson(w, task) } diff --git a/pkg/api/api.go b/pkg/api/api.go index 7ff5fa6..38f1948 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -5,23 +5,35 @@ import ( "fmt" "log" "net/http" - "strconv" "time" "github.com/ArtyomGaribyan/Task-Scheduler/pkg/db" ) func writeJson(w http.ResponseWriter, data any) { - fmt.Printf("\n") + fmt.Printf("\n") // for better readability in logs w.Header().Set("Content-Type", "application/json; charset=UTF-8") jsonData, err := json.Marshal(data) if err != nil { - http.Error(w, "Error encoding JSON", http.StatusInternalServerError) + log.Println("Error encoding JSON:", err) + http.Error(w, "Error encoding JSON: "+err.Error(), http.StatusInternalServerError) return } w.Write(jsonData) } +func getTaskBody (w http.ResponseWriter, r *http.Request, task *db.Task) { + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(task) + if err != nil { + Error := "Invalid request body: " + err.Error() + log.Println(Error) + w.WriteHeader(http.StatusBadRequest) + writeJson(w, Error) + } + log.Println("Received task:", task) +} + func HandleNextDate(w http.ResponseWriter, r *http.Request) { now := r.URL.Query().Get("now") date := r.URL.Query().Get("date") @@ -34,14 +46,20 @@ func HandleNextDate(w http.ResponseWriter, r *http.Request) { } else { parsedNow, err = time.Parse(db.DateLayout, now) if err != nil { - w.Write([]byte("")) + Error := "Next date parse error: " + err.Error() + log.Println(Error) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(Error)) return } } nextDate, err := db.NextDate(parsedNow, date, repeat) if err != nil { - w.Write([]byte("")) + Error := "Next date calculation error: " + err.Error() + log.Println(Error) + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(Error)) return } @@ -50,115 +68,27 @@ func HandleNextDate(w http.ResponseWriter, r *http.Request) { func HandleTask(w http.ResponseWriter, r *http.Request) { var task db.Task + task.ID = r.URL.Query().Get("id") method := r.Method log.Println("Method:", method) - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&task) - if err != nil { - task.ID = r.URL.Query().Get("id") - task.Title = r.URL.Query().Get("title") - task.Date = r.URL.Query().Get("date") - task.Comment = r.URL.Query().Get("comment") - task.Repeat = r.URL.Query().Get("repeat") - if task != (db.Task{}) { - goto If_close - } - - task.Error = "Invalid request body: " + err.Error() - log.Println(task.Error) - w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) - return - } -If_close: - - log.Println("Received task:", task) - switch method { case http.MethodGet: - if task.ID == "" { - task.Error = "Error getting task: missing ID" - log.Println(task.Error) - w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) - return - } - - taskResieved, err := db.GetTask(task.ID) - log.Println("Task received:", taskResieved.ID, taskResieved.Title, taskResieved.Date, taskResieved.Comment, taskResieved.Repeat) - if err != nil { - task.Error = "Error getting task: " + err.Error() - log.Println(task.Error) - w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) - return - } - writeJson(w, taskResieved) - return - + GetTaskHandler(w, task.ID) case http.MethodPost: - log.Println("Adding task:", task) - id, err := addTaskHandler(&task) - if err != nil { - task.Error = "Error adding task: " + err.Error() - log.Println(task.Error) - w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) - return - } - task.ID = strconv.Itoa(int(id)) - log.Println("Added task ID:", task.ID) - writeJson(w, task) - return - + getTaskBody(w, r, &task) + AddTaskHandler(w, task) case http.MethodPut: - if task.ID == "" { - task.Error = "Error updating task: missing ID" - log.Println(task.Error) - w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) - return - } - - log.Println("Updating task:", task.ID, "\n", task.Title, task.Date, task.Comment, task.Repeat) - err := UpdateTaskHandler(&task) - if err != nil { - task.Error = "Error updating task: " + err.Error() - log.Println(task.Error) - w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) - return - } - writeJson(w, db.Task{}) + getTaskBody(w, r, &task) + UpdateTaskHandler(w, task) case http.MethodDelete: - if task.ID == "" { - task.Error = "Error deleting task: missing ID" - log.Println(task.Error) - w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) - return - } - - err := db.DeleteTask(task.ID) - if err != nil { - task.Error = "Error deleting task: " + err.Error() - log.Println(task.Error) - w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) - return - } - - log.Printf("Successfully deleted task: %s\n", task.ID) - writeJson(w, db.Task{}) - + DeleteTaskHandler(w, task.ID) default: - task.Error = "Method not allowed" - log.Println(task.Error) - w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) - return + Error := db.Task{Error: "Method not allowed"} + log.Println(Error) + w.WriteHeader(http.StatusMethodNotAllowed) + writeJson(w, Error) } } @@ -168,32 +98,80 @@ func HandleTaskDone(w http.ResponseWriter, r *http.Request) { method := r.Method log.Println("Method:", method) if method != http.MethodPost { - log.Println("Method not allowed") - w.WriteHeader(http.StatusInternalServerError) - task.Error = "Method not allowed" - writeJson(w, task) + Error := db.Task{Error: "Method not allowed"} + log.Println(Error) + w.WriteHeader(http.StatusMethodNotAllowed) + writeJson(w, Error) return } task.ID = r.URL.Query().Get("id") if task.ID == "" { - task.Error = "Error marking task as done: missing ID" - log.Println(task.Error) - w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) + Error := db.Task{Error: "Validation error for marking task as done: missing ID"} + log.Println(Error) + w.WriteHeader(http.StatusBadRequest) + writeJson(w, Error) return } log.Println("Marking task as done", task.ID) - err := TaskDoneHandler(task.ID) + task, err := db.GetTask(task.ID) if err != nil { - task.Error = "Error marking task as done: " + err.Error() - log.Println(task.Error) + Error := db.Task{Error: "Error for marking task as done: " + err.Error()} + log.Println(Error) w.WriteHeader(http.StatusInternalServerError) - writeJson(w, task) + writeJson(w, Error) + return + } + + if task.Repeat == "" { + err = db.DeleteTask(task.ID) + if err != nil { + if err.Error() == "incorrect id for deleting task" { + Error := db.Task{Error: "Error for marking task as done: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusBadRequest) + writeJson(w, Error) + return + } + Error := db.Task{Error: "Error for marking task as done: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, Error) + return + } + log.Println("Task", task.ID, "was successfully removed") + writeJson(w, db.Task{}) return } + log.Println("Calculating next date for task:", task) + task.Date, err = db.NextDate(time.Now(), task.Date, task.Repeat) + if err != nil { + Error := db.Task{Error: "Error for calculating next date: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, Error) + return + } + log.Println("Next date for task:", task.Date) + + err = db.UpdateDate(&task) + if err != nil { + if err.Error() == "incorrect id for updating date" { + Error := db.Task{Error: "Error for marking task as done: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusBadRequest) + writeJson(w, Error) + return + } + Error := db.Task{Error: "Error for marking task as done: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusBadRequest) + writeJson(w, Error) + return + } + log.Println("Successfully marked as done: ", task.ID) writeJson(w, db.Task{}) } @@ -201,11 +179,7 @@ func HandleTaskDone(w http.ResponseWriter, r *http.Request) { func Init() { http.Handle("/", http.FileServer(http.Dir("web"))) http.HandleFunc("/api/nextdate", HandleNextDate) - http.HandleFunc("api/nextdate", HandleNextDate) http.HandleFunc("/api/task", HandleTask) - http.HandleFunc("api/task", HandleTask) http.HandleFunc("/api/tasks", HandleTasks) - http.HandleFunc("api/tasks", HandleTasks) http.HandleFunc("/api/task/done", HandleTaskDone) - http.HandleFunc("api/task/done", HandleTaskDone) } diff --git a/pkg/api/task.go b/pkg/api/task.go index 76264bd..189a31d 100644 --- a/pkg/api/task.go +++ b/pkg/api/task.go @@ -3,7 +3,6 @@ package api import ( "log" "net/http" - "time" "github.com/ArtyomGaribyan/Task-Scheduler/pkg/db" ) @@ -15,10 +14,10 @@ type TasksResp struct { func HandleTasks(w http.ResponseWriter, r *http.Request) { tasks, err := db.Tasks(50) if err != nil { + Error := db.Task{Error: "Error fetching tasks: " + err.Error()} + log.Println(Error) w.WriteHeader(http.StatusInternalServerError) - writeJson(w, TasksResp{ - Tasks: []*db.Task{}, - }) + writeJson(w, Error) return } log.Println("Tasks fetched in handler:", tasks) @@ -27,44 +26,80 @@ func HandleTasks(w http.ResponseWriter, r *http.Request) { }) } -func UpdateTaskHandler(task *db.Task) error { - err := checkTask(task) +func UpdateTaskHandler(w http.ResponseWriter, task db.Task) { + if task.ID == "" { + Error := db.Task{Error: "Validation error in updating task: missing ID"} + log.Println(Error) + w.WriteHeader(http.StatusBadRequest) + writeJson(w, Error) + return + } + + log.Println("Updating task:", task.ID, "\n", task.Title, task.Date, task.Comment, task.Repeat) + + err := checkTask(&task) if err != nil { - return err + Error := db.Task{Error: "Validation error in updating task: wrong data: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusBadRequest) + writeJson(w, Error) + return } err = db.UpdateTask(task) if err != nil { - return err + Error := db.Task{Error: "Error in updating task: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, Error) + return } - return nil + log.Println("Task updated successfully:", task.ID) + writeJson(w, db.Task{}) } -func TaskDoneHandler(id string) error { - task, err := db.GetTask(id) - if err != nil { - return err - } - if task.Repeat == "" { - err = db.DeleteTask(id) - if err != nil { - return err - } - return nil +func GetTaskHandler(w http.ResponseWriter, id string) { + task := db.Task{ID: id} + + if task.ID == "" { + Error := db.Task{Error: "Error getting task: missing ID"} + log.Println(Error) + w.WriteHeader(http.StatusBadRequest) + writeJson(w, Error) + return } - - log.Println("Calculating next date for task:", task) - task.Date, err = db.NextDate(time.Now(), task.Date, task.Repeat) + + task, err := db.GetTask(task.ID) if err != nil { - return err + Error := db.Task{Error: "Error getting task from DB: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, Error) + return + } + log.Println("Task received:", task.ID, task.Title, task.Date, task.Comment, task.Repeat) + writeJson(w, task) +} + +func DeleteTaskHandler(w http.ResponseWriter, id string) { + if id == "" { + Error := db.Task{Error: "Validation error in deleting task: missing ID"} + log.Println(Error) + w.WriteHeader(http.StatusBadRequest) + writeJson(w, Error) + return } - log.Println("Next date for task:", task.Date) - err = db.UpdateDate(&task) + err := db.DeleteTask(id) if err != nil { - return err + Error := db.Task{Error: "Error deleting task: " + err.Error()} + log.Println(Error) + w.WriteHeader(http.StatusInternalServerError) + writeJson(w, Error) + return } - return nil + log.Printf("Successfully deleted task: %s\n", id) + writeJson(w, db.Task{}) } diff --git a/pkg/db/db.go b/pkg/db/db.go index b63c270..2d0bfdb 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -9,21 +9,23 @@ import ( _ "modernc.org/sqlite" ) +var DB *sql.DB + func InitDB() error { log.Println("Opening database file:", tests.DBFile) - - db, err := sql.Open(tests.SQL, tests.DBFile) + + var err error + DB, err = sql.Open(tests.SQL, tests.DBFile) if err != nil { return err } - defer db.Close() - err = db.Ping() + err = DB.Ping() if err != nil { return err } - _, err = db.Exec(`CREATE TABLE IF NOT EXISTS scheduler ( + _, err = DB.Exec(`CREATE TABLE IF NOT EXISTS scheduler ( id INTEGER PRIMARY KEY AUTOINCREMENT, date CHAR(8) NOT NULL DEFAULT '', title VARCHAR(50) NOT NULL DEFAULT '', @@ -37,3 +39,8 @@ func InitDB() error { return nil } + +func Close() { + log.Println("Closing database") + DB.Close() +} diff --git a/pkg/db/nextdate.go b/pkg/db/nextdate.go index b5a26e2..378d71d 100644 --- a/pkg/db/nextdate.go +++ b/pkg/db/nextdate.go @@ -17,7 +17,7 @@ func caseD(now, date time.Time, repeatSplitted []string) (time.Time, error) { days, err := strconv.Atoi(repeatSplitted[1]) if err != nil { - return time.Time{}, fmt.Errorf("invalid date format: %v", err) + return time.Time{}, fmt.Errorf("invalid date format: %w", err) } if days <= 0 || days > 400 { @@ -44,7 +44,7 @@ func NextDate(now time.Time, dstart, repeat string) (string, error) { var nextDate time.Time dateStart, err := time.Parse(DateLayout, dstart) if err != nil { - return "", fmt.Errorf("invalid date format: %v", err) + return "", fmt.Errorf("invalid date format: %w", err) } repeatSplitted := strings.Fields(repeat) diff --git a/pkg/db/scheduler.db b/pkg/db/scheduler.db index 03ce1e6..c4dde79 100644 Binary files a/pkg/db/scheduler.db and b/pkg/db/scheduler.db differ diff --git a/pkg/db/task.go b/pkg/db/task.go index d294d5f..c167cc1 100644 --- a/pkg/db/task.go +++ b/pkg/db/task.go @@ -1,11 +1,8 @@ package db import ( - "database/sql" "fmt" "log" - - "github.com/ArtyomGaribyan/Task-Scheduler/tests" ) type Task struct { @@ -17,35 +14,24 @@ type Task struct { Error string `json:"error,omitempty"` } -func AddTask(task *Task) (int64, error) { +func AddTask(task Task) (int64, error) { var id int64 - db, err := sql.Open(tests.SQL, tests.DBFile) - if err != nil { - return 0, err - } - defer db.Close() - res, err := db.Exec(`INSERT INTO scheduler (date, title, comment, repeat) VALUES (?, ?, ?, ?)`, + res, err := DB.Exec(`INSERT INTO scheduler (date, title, comment, repeat) VALUES (?, ?, ?, ?)`, task.Date, task.Title, task.Comment, task.Repeat) if err == nil { id, err = res.LastInsertId() } if err != nil { - return 0, err + return 0, fmt.Errorf("failed to add task in DB: %w", err) } return id, nil } func Tasks(limit int) ([]*Task, error) { - db, err := sql.Open(tests.SQL, tests.DBFile) - if err != nil { - return []*Task{}, err - } - defer db.Close() - - rows, err := db.Query("SELECT id, date, title, comment, repeat FROM scheduler ORDER BY date LIMIT ?", limit) + rows, err := DB.Query("SELECT id, date, title, comment, repeat FROM scheduler ORDER BY date LIMIT ?", limit) if err != nil { - return []*Task{}, err + return []*Task{}, fmt.Errorf("failed to retrieve tasks: %w", err) } defer rows.Close() @@ -54,10 +40,14 @@ func Tasks(limit int) ([]*Task, error) { var task Task err := rows.Scan(&task.ID, &task.Date, &task.Title, &task.Comment, &task.Repeat) if err != nil { - return []*Task{}, err + return []*Task{}, fmt.Errorf("failed to scan task: %w", err) } tasks = append(tasks, &task) } + err = rows.Err() + if err != nil { + return []*Task{}, fmt.Errorf("failed to scan task: %w", err) + } if tasks == nil { tasks = []*Task{} @@ -68,38 +58,27 @@ func Tasks(limit int) ([]*Task, error) { } func GetTask(id string) (Task, error) { - db, err := sql.Open(tests.SQL, tests.DBFile) - if err != nil { - return Task{}, err - } - defer db.Close() - - var task Task - err = db.QueryRow("SELECT id, date, title, comment, repeat FROM scheduler WHERE id = ?", id).Scan(&task.ID, &task.Date, &task.Title, &task.Comment, &task.Repeat) + task := Task{ID: id} + err := DB.QueryRow("SELECT date, title, comment, repeat FROM scheduler WHERE id = ?", id).Scan(&task.Date, &task.Title, &task.Comment, &task.Repeat) if err != nil { - return Task{}, err + return Task{}, fmt.Errorf("failed to get task from DB: %w", err) } + log.Println("Recieved task", task) return task, nil } -func UpdateTask(task *Task) error { - db, err := sql.Open(tests.SQL, tests.DBFile) - if err != nil { - return err - } - defer db.Close() - +func UpdateTask(task Task) error { log.Println("Updating task in DB:", task.ID, task.Title, task.Date, task.Comment, task.Repeat) - res, err := db.Exec(`UPDATE scheduler SET date = ?, title = ?, comment = ?, repeat = ? WHERE id = ?`, + res, err := DB.Exec(`UPDATE scheduler SET date = ?, title = ?, comment = ?, repeat = ? WHERE id = ?`, task.Date, task.Title, task.Comment, task.Repeat, task.ID) if err != nil { - return err + return fmt.Errorf("failed to update task: %w", err) } count, err := res.RowsAffected() if err != nil { - return err + return fmt.Errorf("failed to get rows affected: %w", err) } if count == 0 { return fmt.Errorf("incorrect id for updating task") @@ -110,21 +89,15 @@ func UpdateTask(task *Task) error { } func UpdateDate(task *Task) error { - db, err := sql.Open(tests.SQL, tests.DBFile) - if err != nil { - return err - } - defer db.Close() - log.Println("Updating date in DB:", task) - res, err := db.Exec(`UPDATE scheduler SET date = ? WHERE id = ?`, task.Date, task.ID) + res, err := DB.Exec(`UPDATE scheduler SET date = ? WHERE id = ?`, task.Date, task.ID) if err != nil { - return err + return fmt.Errorf("failed to update date: %w", err) } count, err := res.RowsAffected() if err != nil { - return err + return fmt.Errorf("failed to get rows affected: %w", err) } if count == 0 { return fmt.Errorf("incorrect id for updating date") @@ -135,24 +108,18 @@ func UpdateDate(task *Task) error { } func DeleteTask(id string) error { - db, err := sql.Open(tests.SQL, tests.DBFile) - if err != nil { - return err - } - defer db.Close() - log.Println("Deleting task with ID:", id) - res, err := db.Exec(`DELETE FROM scheduler WHERE id = ?`, id) + res, err := DB.Exec(`DELETE FROM scheduler WHERE id = ?`, id) if err != nil { - return err + return fmt.Errorf("failed to delete task: %w", err) } count, err := res.RowsAffected() if err != nil { - return err + return fmt.Errorf("failed to delete task: failed to get rows affected: %w", err) } if count == 0 { - return fmt.Errorf(`incorrect id for updating task`) + return fmt.Errorf("incorrect id for deleting task") } log.Println("Rows affected:", count)