commit 4183f0740dd8220409570d0ab91f89ee54f1841a Author: Benaya Nathanael Yeroham Date: Wed Sep 24 16:53:21 2025 +0700 auto commit diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..305242e --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "5803024008/internal/db" + "5803024008/internal/handlers" + "log" + "net/http" + + "github.com/gorilla/mux" +) + +func home(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello broskie")) +} + +func main() { + // Initialize database connection + database, err := db.InitDB() + if err != nil { + log.Fatal("Failed to connect to database:", err) + } + defer database.Close() + + // Set database connection for handlers + handlers.SetDatabase(database) + + // Use mux.NewRouter() to initialize Gorilla Mux router + r := mux.NewRouter() + r.HandleFunc("/", home).Methods("GET") + + // Group management routes + r.HandleFunc("/groups", handlers.CreateGroupHandler).Methods("POST") + r.HandleFunc("/groups/{groupId}", handlers.GetGroupHandler).Methods("GET") + r.HandleFunc("/groups/{groupId}", handlers.RemoveGroupHandler).Methods("DELETE") + + // Task management routes + r.HandleFunc("/groups/{groupId}/tasks", handlers.CreateTaskHandler).Methods("POST") + r.HandleFunc("/groups/{groupId}/tasks", handlers.DisplayTasksByGroupHandler).Methods("GET") + r.HandleFunc("/tasks", handlers.DisplayTasksHandler).Methods("GET") + r.HandleFunc("/tasks/{taskId}", handlers.GetTaskHandler).Methods("GET") + r.HandleFunc("/tasks/{taskId}", handlers.UpdateTaskHandler).Methods("PUT") + r.HandleFunc("/tasks/{taskId}", handlers.RemoveTaskHandler).Methods("DELETE") + r.HandleFunc("/tasks/{taskId}/done", handlers.MarkTaskDoneHandler).Methods("PUT") + + // Use the http.ListenAndServe() function to start a new web server. + log.Print("Starting server on :4000") + err = http.ListenAndServe(":4000", r) + log.Fatal(err) +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5585aea --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module 5803024008 + +go 1.25.0 + +require github.com/gorilla/mux v1.8.1 + +require github.com/lib/pq v1.10.9 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2a964d7 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= diff --git a/internal/db/connection.go b/internal/db/connection.go new file mode 100644 index 0000000..c2a8b44 --- /dev/null +++ b/internal/db/connection.go @@ -0,0 +1,43 @@ +package db + +import ( + "database/sql" + "fmt" + + _ "github.com/lib/pq" +) + +const ( + host = "202.46.28.160" + port = 45432 + user = "5803024008" + password = "pw5803024008" + dbname = "tgs01_5803024008" +) + +// InitDB returns a database connection +func InitDB() (*sql.DB, error) { + // connection string + psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname) + + // open database + db, err := sql.Open("postgres", psqlconn) + if err != nil { + return nil, err + } + + // check db + err = db.Ping() + if err != nil { + return nil, err + } + + fmt.Println("Connected to database!") + return db, nil +} + +func CheckError(err error) { + if err != nil { + panic(err) + } +} \ No newline at end of file diff --git a/internal/db/group_queries.go b/internal/db/group_queries.go new file mode 100644 index 0000000..1d4e3b1 --- /dev/null +++ b/internal/db/group_queries.go @@ -0,0 +1,49 @@ +package db + +import ( + "database/sql" +) + +// CreateGroup inserts a new group into the database +func CreateGroup(db *sql.DB, groupName string) error { + query := `INSERT INTO groups (group_name) VALUES ($1)` + _, err := db.Exec(query, groupName) + return err +} + +// GetGroupByID retrieves a group by its ID +func GetGroupByID(db *sql.DB, groupID int) (*Groups, error) { + query := `SELECT group_id, group_name FROM groups WHERE group_id = $1` + row := db.QueryRow(query, groupID) + + var group Groups + err := row.Scan(&group.GroupId, &group.GroupName) + if err != nil { + return nil, err + } + + return &group, nil +} + +// RemoveGroup deletes a group and all its associated tasks +func RemoveGroup(db *sql.DB, groupID int) error { + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + // Delete all tasks in the group first + _, err = tx.Exec("DELETE FROM tasks WHERE group_id = $1", groupID) + if err != nil { + return err + } + + // Delete the group + _, err = tx.Exec("DELETE FROM groups WHERE group_id = $1", groupID) + if err != nil { + return err + } + + return tx.Commit() +} \ No newline at end of file diff --git a/internal/db/models.go b/internal/db/models.go new file mode 100644 index 0000000..239e511 --- /dev/null +++ b/internal/db/models.go @@ -0,0 +1,19 @@ +package db + +import ( + "time" +) + +type Groups struct{ + GroupId int `json:"group_id"` + GroupName string `json:"group_name"` +} + +type Tasks struct{ + TaskID int `json:"task_id"` + TaskName string `json:"task_name"` // Maps to "task" column in SQL + TaskDescription string `json:"task_desc"` // Maps to "task_desc" column in SQL (changed from *int to string) + GroupID int `json:"group_id"` + IsDone bool `json:"is_done"` // Maps to "isdone" column in SQL + CreatedAt time.Time `json:"created_at"` +} \ No newline at end of file diff --git a/internal/db/task_queries.go b/internal/db/task_queries.go new file mode 100644 index 0000000..40aa506 --- /dev/null +++ b/internal/db/task_queries.go @@ -0,0 +1,99 @@ +package db + +import ( + "database/sql" + "time" +) + +// CreateTask inserts a new task into the database +func CreateTask(db *sql.DB, taskName string, taskDescription string, groupID int) error { + query := `INSERT INTO tasks (task_name, task_desc, group_id, is_done, created_at) + VALUES ($1, $2, $3, $4, $5)` + _, err := db.Exec(query, taskName, taskDescription, groupID, false, time.Now()) + return err +} + +// GetAllTasks retrieves all tasks from the database +func GetAllTasks(db *sql.DB) ([]Tasks, error) { + query := `SELECT task_id, task_name, task_desc, group_id, is_done, created_at + FROM tasks ORDER BY created_at DESC` + rows, err := db.Query(query) + if err != nil { + return nil, err + } + defer rows.Close() + + var tasks []Tasks + for rows.Next() { + var task Tasks + err := rows.Scan(&task.TaskID, &task.TaskName, &task.TaskDescription, + &task.GroupID, &task.IsDone, &task.CreatedAt) + if err != nil { + return nil, err + } + tasks = append(tasks, task) + } + + return tasks, nil +} + +// GetTaskByID retrieves a specific task by its ID +func GetTaskByID(db *sql.DB, taskID int) (*Tasks, error) { + query := `SELECT task_id, task_name, task_desc, group_id, is_done, created_at + FROM tasks WHERE task_id = $1` + row := db.QueryRow(query, taskID) + + var task Tasks + err := row.Scan(&task.TaskID, &task.TaskName, &task.TaskDescription, + &task.GroupID, &task.IsDone, &task.CreatedAt) + if err != nil { + return nil, err + } + + return &task, nil +} + +// GetTasksByGroupID retrieves all tasks for a specific group +func GetTasksByGroupID(db *sql.DB, groupID int) ([]Tasks, error) { + query := `SELECT task_id, task_name, task_desc, group_id, is_done, created_at + FROM tasks WHERE group_id = $1` + rows, err := db.Query(query, groupID) + if err != nil { + return nil, err + } + defer rows.Close() + + var tasks []Tasks + for rows.Next() { + var task Tasks + err := rows.Scan(&task.TaskID, &task.TaskName, &task.TaskDescription, + &task.GroupID, &task.IsDone, &task.CreatedAt) + if err != nil { + return nil, err + } + tasks = append(tasks, task) + } + + return tasks, nil +} + +// MarkTaskAsDone updates a task's status to completed +func MarkTaskAsDone(db *sql.DB, taskID int) error { + query := `UPDATE tasks SET is_done = true WHERE task_id = $1` + _, err := db.Exec(query, taskID) + return err +} + +// RemoveTask deletes a task from the database +func RemoveTask(db *sql.DB, taskID int) error { + query := `DELETE FROM tasks WHERE task_id = $1` + _, err := db.Exec(query, taskID) + return err +} + +// UpdateTask updates task name and description +func UpdateTask(db *sql.DB, taskID int, taskName string, taskDescription string) error { + query := `UPDATE tasks SET task_name = $1, task_desc = $2 WHERE task_id = $3` + _, err := db.Exec(query, taskName, taskDescription, taskID) + return err +} \ No newline at end of file diff --git a/internal/handlers/group_handlers.go b/internal/handlers/group_handlers.go new file mode 100644 index 0000000..9e119c8 --- /dev/null +++ b/internal/handlers/group_handlers.go @@ -0,0 +1,139 @@ +package handlers + +import ( + "5803024008/internal/db" + "database/sql" + "encoding/json" + "net/http" + "strconv" + + "github.com/gorilla/mux" +) + +type GroupRequest struct { + GroupName string `json:"group_name"` +} + +type GroupResponse struct { + GroupID int `json:"group_id"` + GroupName string `json:"group_name"` +} + +type ErrorResponse struct { + Error string `json:"error"` +} + +// Database connection - you'll need to inject this or use a global variable +var database *sql.DB + +func SetDatabase(db *sql.DB) { + database = db +} + +func CreateGroupHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + var req GroupRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid JSON format"}) + return + } + + if req.GroupName == "" { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Group name is required"}) + return + } + + if err := db.CreateGroup(database, req.GroupName); err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Failed to create group"}) + return + } + + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]string{"message": "Group created successfully"}) +} + +func DisplayTasksByGroupHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + groupIDStr := vars["groupId"] + + groupID, err := strconv.Atoi(groupIDStr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid group ID"}) + return + } + + tasks, err := db.GetTasksByGroupID(database, groupID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Failed to retrieve tasks"}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(tasks) +} + +func RemoveGroupHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + groupIDStr := vars["groupId"] + + groupID, err := strconv.Atoi(groupIDStr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid group ID"}) + return + } + + if err := db.RemoveGroup(database, groupID); err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Failed to remove group"}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"message": "Group removed successfully"}) +} + +func GetGroupHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + groupIDStr := vars["groupId"] + + groupID, err := strconv.Atoi(groupIDStr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid group ID"}) + return + } + + group, err := db.GetGroupByID(database, groupID) + if err == sql.ErrNoRows { + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Group not found"}) + return + } + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Failed to retrieve group"}) + return + } + + response := GroupResponse{ + GroupID: group.GroupId, + GroupName: group.GroupName, + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) +} + diff --git a/internal/handlers/task_handlers.go b/internal/handlers/task_handlers.go new file mode 100644 index 0000000..62466a7 --- /dev/null +++ b/internal/handlers/task_handlers.go @@ -0,0 +1,194 @@ +package handlers + +import ( + "5803024008/internal/db" + "database/sql" + "encoding/json" + "net/http" + "strconv" + + "github.com/gorilla/mux" +) + +type TaskRequest struct { + TaskName string `json:"task_name"` + TaskDescription string `json:"task_desc"` // Changed from *string to string to match SQL NOT NULL +} + +type TaskUpdateRequest struct { + TaskName string `json:"task_name"` + TaskDescription string `json:"task_desc"` // Changed from *string to string +} + +func CreateTaskHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + groupIDStr := vars["groupId"] + + groupID, err := strconv.Atoi(groupIDStr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid group ID"}) + return + } + + var req TaskRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid JSON format"}) + return + } + + if req.TaskName == "" { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Task name is required"}) + return + } + + if req.TaskDescription == "" { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Task description is required"}) + return + } + + if err := db.CreateTask(database, req.TaskName, req.TaskDescription, groupID); err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Failed to create task"}) + return + } + + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]string{"message": "Task created successfully"}) +} + +func DisplayTasksHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + tasks, err := db.GetAllTasks(database) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Failed to retrieve tasks"}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(tasks) +} + +func GetTaskHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + taskIDStr := vars["taskId"] + + taskID, err := strconv.Atoi(taskIDStr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid task ID"}) + return + } + + task, err := db.GetTaskByID(database, taskID) + if err == sql.ErrNoRows { + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Task not found"}) + return + } + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Failed to retrieve task"}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(task) +} + +func MarkTaskDoneHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + taskIDStr := vars["taskId"] + + taskID, err := strconv.Atoi(taskIDStr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid task ID"}) + return + } + + if err := db.MarkTaskAsDone(database, taskID); err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Failed to mark task as done"}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"message": "Task marked as done successfully"}) +} + +func RemoveTaskHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + taskIDStr := vars["taskId"] + + taskID, err := strconv.Atoi(taskIDStr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid task ID"}) + return + } + + if err := db.RemoveTask(database, taskID); err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Failed to remove task"}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"message": "Task removed successfully"}) +} + +func UpdateTaskHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + vars := mux.Vars(r) + taskIDStr := vars["taskId"] + + taskID, err := strconv.Atoi(taskIDStr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid task ID"}) + return + } + + var req TaskUpdateRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Invalid JSON format"}) + return + } + + if req.TaskName == "" { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Task name is required"}) + return + } + + if req.TaskDescription == "" { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Task description is required"}) + return + } + + if err := db.UpdateTask(database, taskID, req.TaskName, req.TaskDescription); err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(ErrorResponse{Error: "Failed to update task"}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"message": "Task updated successfully"}) +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..9c15de1 --- /dev/null +++ b/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "log" + "net/http" + + "github.com/gorilla/mux" +) + +func createGroupHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusCreated) + w.Write([]byte("Group Created")) +} + +func createTaskHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + groupID := vars["groupId"] + + w.WriteHeader(http.StatusCreated) + w.Write([]byte("Task Created in Group ID: " + groupID)) +} + +func displayTasksHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("Displaying All Tasks")) +} + +func displayTasksByGroupHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + groupID := vars["groupId"] + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Displaying Tasks for Group ID: " + groupID)) +} + +func getTaskHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + taskID := vars["taskId"] + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Displaying Task ID: " + taskID)) +} + +func markTaskDoneHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + taskID := vars["taskId"] + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Task ID " + taskID + " Marked as Done")) +} + +func removeTaskHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + taskID := vars["taskId"] + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Task ID " + taskID + " Removed")) +} + +func removeGroupHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + groupID := vars["groupId"] + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Group ID " + groupID + " Removed")) +} + +func updateTaskHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + taskID := vars["taskId"] + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Task ID " + taskID + " Updated")) +} + +func getGroupHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + groupID := vars["groupId"] + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Displaying Group ID: " + groupID)) +} + +func home(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello from Task Management API")) +} + +func main() { + // Use mux.NewRouter() to initialize Gorilla Mux router + r := mux.NewRouter() + r.HandleFunc("/", home).Methods("GET") + + // Group management routes + r.HandleFunc("/groups", createGroupHandler).Methods("POST") + r.HandleFunc("/groups/{groupId}", getGroupHandler).Methods("GET") + r.HandleFunc("/groups/{groupId}", removeGroupHandler).Methods("DELETE") + + // Task management routes + r.HandleFunc("/groups/{groupId}/tasks", createTaskHandler).Methods("POST") + r.HandleFunc("/groups/{groupId}/tasks", displayTasksByGroupHandler).Methods("GET") + r.HandleFunc("/tasks", displayTasksHandler).Methods("GET") + r.HandleFunc("/tasks/{taskId}", getTaskHandler).Methods("GET") + r.HandleFunc("/tasks/{taskId}", updateTaskHandler).Methods("PUT") + r.HandleFunc("/tasks/{taskId}", removeTaskHandler).Methods("DELETE") + r.HandleFunc("/tasks/{taskId}/done", markTaskDoneHandler).Methods("PUT") + + // Use the http.ListenAndServe() function to start a new web server. + log.Print("Starting server on :4000") + err := http.ListenAndServe(":4000", r) + log.Fatal(err) +} \ No newline at end of file