From abea1a86bb9355021a46b8872262a231b33ca9b2 Mon Sep 17 00:00:00 2001 From: bedrockdude10 Date: Wed, 31 Jan 2024 19:08:22 -0500 Subject: [PATCH 01/29] Added mock data --- backend/db/migrations/init.sql | 68 +++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql index e5c0512..7dc3a13 100644 --- a/backend/db/migrations/init.sql +++ b/backend/db/migrations/init.sql @@ -8,7 +8,6 @@ DROP TABLE IF EXISTS label; DROP TABLE IF EXISTS task_labels; DROP TABLE IF EXISTS files; - CREATE TYPE role AS ENUM ('PATIENT', 'PRIMARY', 'SECONDARY'); CREATE TYPE task_assignment_status AS ENUM ('ACCEPTED', 'DECLINED', 'NOTIFIED'); CREATE TYPE task_status AS ENUM ('INCOMPLETE', 'COMPLETE', 'PARTIAL'); @@ -113,6 +112,7 @@ CREATE TABLE IF NOT EXISTS files ( FOREIGN KEY (task_id) REFERENCES task (task_id) ); +----------------- SAMPLE DATA :) ----------------------- -- Insert sample data into "medication" table INSERT INTO medication (medication_id, medication_name) @@ -122,3 +122,69 @@ VALUES (3, 'Medication C'), (4, 'Medication D'), (5, 'Medication E') + +INSERT INTO care_group (group_name, date_created) +VALUES + ('Smith Family', NOW()), + ('Johnson Support Network', NOW()), + ('Williams Care Team', NOW()), + ('Brown Medical Group', NOW()) +; + +INSERT INTO users (user_id, first_name, last_name, email, phone, address) +VALUES + ('user1', 'John', 'Smith', 'john.smith@example.com', '123-456-7890', '123 Main St'), + ('user2', 'Jane', 'Doe', 'jane.doe@example.com', '987-654-3210', '456 Elm St'), + ('user3', 'Bob', 'Johnson', 'bob.johnson@example.com', NULL, NULL), + ('user4', 'Emily', 'Garcia', 'emily.garcia@example.com', '555-1212', '789 Oak Ave') +; + +INSERT INTO group_roles (group_id, user_id, role) +VALUES + (1, 'user1', 'PATIENT'), + (1, 'user2', 'PRIMARY'), + (2, 'user3', 'PRIMARY'), + (2, 'user4', 'SECONDARY'), + (3, 'user4', 'PATIENT'), + (4, 'user1', 'SECONDARY'), + (4, 'user3', 'SECONDARY') +; + +INSERT INTO task (group_id, created_by, created_date, start_date, end_date, notes, task_status, task_type) +VALUES + (1, 'user2', NOW(), '2024-02-05 10:00:00', '2024-02-05 11:00:00', 'Pick up medication from pharmacy', 'INCOMPLETE', 'med_mgmt'), + (2, 'user3', NOW(), '2024-02-10 14:30:00', NULL, 'Schedule doctor appointment', 'INCOMPLETE', 'dr_appt'), + (3, 'user4', NOW(), NULL, '2024-02-20 23:59:59', 'Submit insurance claim', 'PARTIAL', 'financial'), + (4, 'user1', NOW(), NULL, NULL, 'Refill water pitcher', 'COMPLETE', 'other') +; + +INSERT INTO task_assignees (task_id, user_id, assignment_status, assigned_by, assigned_date) +VALUES + (1, 'user1', 'ACCEPTED', 'user2', NOW()), + (2, 'user3', 'NOTIFIED', 'user3', NOW()), + (3, 'user4', 'DECLINED', 'user4', NOW()), + (4, 'user2', 'COMPLETE', 'user1', NOW()) +; + +INSERT INTO label (group_id, label_name, label_color) +VALUES + (1, 'Medication', 'blue'), + (2, 'Appointments', 'green'), + (3, 'Financial', 'orange'), + (4, 'Household', 'purple') +; + +INSERT INTO task_labels (task_id, group_id, label_name) +VALUES + (1, 1, 'Medication'), + (2, 2, 'Appointments'), + (3, 3, 'Financial'), + (4, 4, 'Household') +; + +INSERT INTO files (file_id, file_name, group_id, upload_by, upload_date, file_size, task_id) +VALUES + (1, 'Medication list.pdf', 1, 'user2', NOW(), 123456, 1), + (2, 'Insurance form.docx', 3, 'user4', NOW(), 456789, 3), + (3, 'Water pitcher instructions.txt', 4, 'user1', NOW(), 1234, 4) +; From ba204fa7439da2080e7142cd0f2c80d34c5be533 Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Thu, 1 Feb 2024 16:12:50 -0500 Subject: [PATCH 02/29] feat: get tasks by gid/uid/status/type/date --- backend/db/migrations/init.sql | 11 +- backend/docs/docs.go | 165 +++++++++++++++++++++ backend/docs/swagger.json | 165 +++++++++++++++++++++ backend/docs/swagger.yaml | 108 ++++++++++++++ backend/models/task.go | 21 +++ backend/schema/tasks/routes.go | 129 +++++++++++++++++ backend/schema/tasks/task_test.go | 207 +++++++++++++++++++++++++++ backend/schema/tasks/transactions.go | 189 ++++++++++++++++++++++++ 8 files changed, 990 insertions(+), 5 deletions(-) create mode 100644 backend/models/task.go create mode 100644 backend/schema/tasks/routes.go create mode 100644 backend/schema/tasks/task_test.go create mode 100644 backend/schema/tasks/transactions.go diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql index 7dc3a13..c380755 100644 --- a/backend/db/migrations/init.sql +++ b/backend/db/migrations/init.sql @@ -122,6 +122,7 @@ VALUES (3, 'Medication C'), (4, 'Medication D'), (5, 'Medication E') +; INSERT INTO care_group (group_name, date_created) VALUES @@ -152,10 +153,10 @@ VALUES INSERT INTO task (group_id, created_by, created_date, start_date, end_date, notes, task_status, task_type) VALUES - (1, 'user2', NOW(), '2024-02-05 10:00:00', '2024-02-05 11:00:00', 'Pick up medication from pharmacy', 'INCOMPLETE', 'med_mgmt'), - (2, 'user3', NOW(), '2024-02-10 14:30:00', NULL, 'Schedule doctor appointment', 'INCOMPLETE', 'dr_appt'), - (3, 'user4', NOW(), NULL, '2024-02-20 23:59:59', 'Submit insurance claim', 'PARTIAL', 'financial'), - (4, 'user1', NOW(), NULL, NULL, 'Refill water pitcher', 'COMPLETE', 'other') + (1, 'user2', '2024-02-03 10:45:00', '2024-02-05 10:00:00', '2024-02-05 11:00:00', 'Pick up medication from pharmacy', 'INCOMPLETE', 'med_mgmt'), + (2, 'user3', '2024-02-20 23:59:59', '2024-02-10 14:30:00', NULL, 'Schedule doctor appointment', 'INCOMPLETE', 'dr_appt'), + (3, 'user4', '2020-02-05 11:00:00', NULL, '2024-02-20 23:59:59', 'Submit insurance claim', 'PARTIAL', 'financial'), + (4, 'user1', '2006-01-02 15:04:05', NULL, NULL, 'Refill water pitcher', 'COMPLETE', 'other') ; INSERT INTO task_assignees (task_id, user_id, assignment_status, assigned_by, assigned_date) @@ -163,7 +164,7 @@ VALUES (1, 'user1', 'ACCEPTED', 'user2', NOW()), (2, 'user3', 'NOTIFIED', 'user3', NOW()), (3, 'user4', 'DECLINED', 'user4', NOW()), - (4, 'user2', 'COMPLETE', 'user1', NOW()) + (4, 'user2', 'DECLINED', 'user1', NOW()) ; INSERT INTO label (group_id, label_name, label_color) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 1c29687..63057d2 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -48,6 +48,126 @@ const docTemplate = `{ } } } + }, + "/tasks/{endDate}": { + "get": { + "description": "get all tasks by end date", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By End Date", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } + }, + "/tasks/{gid}": { + "get": { + "description": "get all tasks by group id", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By Group ID", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } + }, + "/tasks/{startDate}": { + "get": { + "description": "get all tasks by start date", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By Start Date", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } + }, + "/tasks/{status}": { + "get": { + "description": "get all tasks by status", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By Status", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } + }, + "/tasks/{type}": { + "get": { + "description": "get all tasks by type", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By Type", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } + }, + "/tasks/{uid}": { + "get": { + "description": "get all tasks by user id", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By User ID", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } } }, "definitions": { @@ -61,6 +181,51 @@ const docTemplate = `{ "type": "string" } } + }, + "models.Task": { + "type": "object", + "properties": { + "created_by": { + "description": "User ID", + "type": "string" + }, + "created_date": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "group_id": { + "type": "integer" + }, + "notes": { + "type": "string" + }, + "repeating": { + "type": "boolean" + }, + "repeating_end_date": { + "type": "string" + }, + "repeating_interval": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "task_id": { + "type": "integer" + }, + "task_info": { + "type": "string" + }, + "task_status": { + "type": "string" + }, + "task_type": { + "type": "string" + } + } } } }` diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 1ac678d..212af82 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -41,6 +41,126 @@ } } } + }, + "/tasks/{endDate}": { + "get": { + "description": "get all tasks by end date", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By End Date", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } + }, + "/tasks/{gid}": { + "get": { + "description": "get all tasks by group id", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By Group ID", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } + }, + "/tasks/{startDate}": { + "get": { + "description": "get all tasks by start date", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By Start Date", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } + }, + "/tasks/{status}": { + "get": { + "description": "get all tasks by status", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By Status", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } + }, + "/tasks/{type}": { + "get": { + "description": "get all tasks by type", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By Type", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } + }, + "/tasks/{uid}": { + "get": { + "description": "get all tasks by user id", + "tags": [ + "tasks" + ], + "summary": "Get All Tasks By User ID", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + } + } + } } }, "definitions": { @@ -54,6 +174,51 @@ "type": "string" } } + }, + "models.Task": { + "type": "object", + "properties": { + "created_by": { + "description": "User ID", + "type": "string" + }, + "created_date": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "group_id": { + "type": "integer" + }, + "notes": { + "type": "string" + }, + "repeating": { + "type": "boolean" + }, + "repeating_end_date": { + "type": "string" + }, + "repeating_interval": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "task_id": { + "type": "integer" + }, + "task_info": { + "type": "string" + }, + "task_status": { + "type": "string" + }, + "task_type": { + "type": "string" + } + } } } } \ No newline at end of file diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 0f4976b..053172c 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -7,6 +7,36 @@ definitions: medication_name: type: string type: object + models.Task: + properties: + created_by: + description: User ID + type: string + created_date: + type: string + end_date: + type: string + group_id: + type: integer + notes: + type: string + repeating: + type: boolean + repeating_end_date: + type: string + repeating_interval: + type: string + start_date: + type: string + task_id: + type: integer + task_info: + type: string + task_status: + type: string + task_type: + type: string + type: object info: contact: {} description: This is an API for the Care-Wallet App. @@ -35,4 +65,82 @@ paths: summary: Get All Meds tags: - medications + /tasks/{endDate}: + get: + description: get all tasks by end date + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Task' + type: array + summary: Get All Tasks By End Date + tags: + - tasks + /tasks/{gid}: + get: + description: get all tasks by group id + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Task' + type: array + summary: Get All Tasks By Group ID + tags: + - tasks + /tasks/{startDate}: + get: + description: get all tasks by start date + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Task' + type: array + summary: Get All Tasks By Start Date + tags: + - tasks + /tasks/{status}: + get: + description: get all tasks by status + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Task' + type: array + summary: Get All Tasks By Status + tags: + - tasks + /tasks/{type}: + get: + description: get all tasks by type + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Task' + type: array + summary: Get All Tasks By Type + tags: + - tasks + /tasks/{uid}: + get: + description: get all tasks by user id + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Task' + type: array + summary: Get All Tasks By User ID + tags: + - tasks swagger: "2.0" diff --git a/backend/models/task.go b/backend/models/task.go new file mode 100644 index 0000000..7dd5193 --- /dev/null +++ b/backend/models/task.go @@ -0,0 +1,21 @@ +package models + +import ( + "time" +) + +type Task struct { + TaskID int `json:"task_id"` + GroupID int `json:"group_id"` + CreatedBy string `json:"created_by"` // User ID + CreatedDate time.Time `json:"created_date"` + StartDate *time.Time `json:"start_date"` + EndDate *time.Time `json:"end_date"` + Notes *string `json:"notes"` + Repeating bool `json:"repeating"` + RepeatingInterval *string `json:"repeating_interval"` + RepeatingEndDate *time.Time `json:"repeating_end_date"` + TaskStatus string `json:"task_status"` + TaskType string `json:"task_type"` + TaskInfo *string `json:"task_info"` +} \ No newline at end of file diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go new file mode 100644 index 0000000..00b6615 --- /dev/null +++ b/backend/schema/tasks/routes.go @@ -0,0 +1,129 @@ +package tasks + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/jackc/pgx" +) + +type PgModel struct { + Conn *pgx.Conn +} + +func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { + + tasks := v1.Group("tasks") + { + tasks.GET("/group/:gid", c.GetTasksByGroupId) + tasks.GET("/created_by/:uid", c.GetTasksByCreatedBy) + tasks.GET("/status/:status", c.GetTasksByStatus) + tasks.GET("/type/:type", c.GetTasksByType) + tasks.GET("/start/:startDate", c.GetTasksByStartDate) + tasks.GET("/end/:endDate", c.GetTasksByEndDate) + } + + return tasks +} + +// GetTasksByGroupId godoc +// +// @summary Get All Tasks By Group ID +// @description get all tasks by group id +// @tags tasks +// @success 200 {array} models.Task +// @router /tasks/{gid} [get] +func (pg *PgModel) GetTasksByGroupId(c *gin.Context) { + tasks, err := GetTasksByGroupIdFromDB(pg.Conn, c.Param("gid")) + + if err != nil { + panic(err) + } + + c.JSON(http.StatusOK, tasks) +} + +// GetTasksByCreatedBy godoc +// +// @summary Get All Tasks By Created By +// @description get all tasks by created by +// @tags tasks +// @success 200 {array} models.Task +// @router /tasks/{uid} [get] +func (pg *PgModel) GetTasksByCreatedBy(c *gin.Context) { + tasks, err := GetTasksByCreatedByFromDB(pg.Conn, c.Param("uid")) + + if err != nil { + panic(err) + } + + c.JSON(http.StatusOK, tasks) +} + +// GetTasksByStatus godoc +// +// @summary Get All Tasks By Status +// @description get all tasks by status +// @tags tasks +// @success 200 {array} models.Task +// @router /tasks/{status} [get] +func (pg *PgModel) GetTasksByStatus(c *gin.Context) { + tasks, err := GetTasksByStatusFromDB(pg.Conn, c.Param("status")) + + if err != nil { + panic(err) + } + + c.JSON(http.StatusOK, tasks) +} + +// GetTasksByType godoc +// +// @summary Get All Tasks By Type +// @description get all tasks by type +// @tags tasks +// @success 200 {array} models.Task +// @router /tasks/{type} [get] +func (pg *PgModel) GetTasksByType(c *gin.Context) { + tasks, err := GetTasksByTypeFromDB(pg.Conn, c.Param("type")) + + if err != nil { + panic(err) + } + + c.JSON(http.StatusOK, tasks) +} + +// GetTasksByStartDate godoc +// +// @summary Get All Tasks By Start Date +// @description get all tasks by start date +// @tags tasks +// @success 200 {array} models.Task +// @router /tasks/{startDate} [get] +func (pg *PgModel) GetTasksByStartDate(c *gin.Context) { + tasks, err := GetTasksByStartDateFromDB(pg.Conn, c.Param("startDate")) + + if err != nil { + panic(err) + } + + c.JSON(http.StatusOK, tasks) +} + +// GetTasksByEndDate godoc +// +// @summary Get All Tasks By End Date +// @description get all tasks by end date +// @tags tasks +// @success 200 {array} models.Task +// @router /tasks/{endDate} [get] +func (pg *PgModel) GetTasksByEndDate(c *gin.Context) { + tasks, err := GetTasksByEndDateFromDB(pg.Conn, c.Param("endDate")) + + if err != nil { + panic(err) + } + + c.JSON(http.StatusOK, tasks) +} \ No newline at end of file diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go new file mode 100644 index 0000000..03a1bf9 --- /dev/null +++ b/backend/schema/tasks/task_test.go @@ -0,0 +1,207 @@ +package tasks + +import ( + "carewallet/configuration" + "carewallet/db" + "carewallet/models" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "reflect" + "testing" + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +func TestGetTasks(t *testing.T) { + config, err := configuration.GetConfiguration() + + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to retreive configuration file: %v\n", err) + os.Exit(1) + } + + conn := db.ConnectPosgresDatabase(config) + defer conn.Close() + + controller := PgModel{Conn: conn} + router := gin.Default() + router.Use(cors.Default()) + + v1 := router.Group("/") + { + TaskGroup(v1, &controller) + } + + t.Run("TestGetTasksByGroupId", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/tasks/group/4", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to retrieve tasks by group id.") + } + + var responseTasks []models.Task + err := json.Unmarshal(w.Body.Bytes(), &responseTasks) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + createdDate, _ := time.Parse("2006-01-02 15:04:05", "2006-01-02 15:04:05") + expectedTasks := []models.Task{ + { + TaskID: 4, + GroupID: 4, + CreatedBy: "user1", + CreatedDate: createdDate, + TaskStatus: "COMPLETE", + TaskType: "other", + }, + } + + if !reflect.DeepEqual(expectedTasks, responseTasks) { + t.Error("Result was not correct") + } + }) + + t.Run("TestGetTasksByCreatedBy", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/tasks/created_by/user2", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to retrieve tasks by user id.") + } + + var responseTasks []models.Task + err := json.Unmarshal(w.Body.Bytes(), &responseTasks) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-01 20:14:26.126136") + expectedTasks := []models.Task{ + { + TaskID: 1, + GroupID: 1, + CreatedBy: "user2", + CreatedDate: createdDate, + TaskStatus: "INCOMPLETE", + TaskType: "med_mgmt", + }, + } + + if !reflect.DeepEqual(expectedTasks, responseTasks) { + t.Error("Result was not correct") + } + }) + + t.Run("TestGetTasksByStatus", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/tasks/status/COMPLETE", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to retrieve tasks by status.") + } + + var responseTasks []models.Task + err := json.Unmarshal(w.Body.Bytes(), &responseTasks) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + createdDate, _ := time.Parse("2006-01-02 15:04:05", "2006-01-02 15:04:05") + expectedTasks := []models.Task{ + { + TaskID: 4, + GroupID: 4, + CreatedBy: "user1", + CreatedDate: createdDate, + TaskStatus: "COMPLETE", + TaskType: "other", + }, + } + + if !reflect.DeepEqual(expectedTasks, responseTasks) { + t.Error("Result was not correct") + } + }) + + t.Run("TestGetTasksByStartDate", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/tasks/start/2024-02-05 10:00:00", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to retrieve tasks by start date.") + } + + var responseTasks []models.Task + err := json.Unmarshal(w.Body.Bytes(), &responseTasks) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-01 20:14:26.126136") + expectedTasks := []models.Task{ + { + TaskID: 1, + GroupID: 1, + CreatedBy: "user2", + CreatedDate: createdDate, + TaskStatus: "INCOMPLETE", + TaskType: "med_mgmt", + }, + } + + fmt.Println("Expected:", expectedTasks) + fmt.Println("Response: ", responseTasks) + + if !reflect.DeepEqual(expectedTasks, responseTasks) { + t.Error("Result was not correct") + } + }) + + t.Run("TestGetTasksByEndDate", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/tasks/end/2024-02-05 11:00:00", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to retrieve tasks by end date.") + } + + var responseTasks []models.Task + err := json.Unmarshal(w.Body.Bytes(), &responseTasks) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-01 20:14:26.126136") + expectedTasks := []models.Task{ + { + TaskID: 1, + GroupID: 1, + CreatedBy: "user2", + CreatedDate: createdDate, + TaskStatus: "INCOMPLETE", + TaskType: "med_mgmt", + }, + } + + if !reflect.DeepEqual(expectedTasks, responseTasks) { + t.Error("Result was not correct") + } + }) +} \ No newline at end of file diff --git a/backend/schema/tasks/transactions.go b/backend/schema/tasks/transactions.go new file mode 100644 index 0000000..ee0ded4 --- /dev/null +++ b/backend/schema/tasks/transactions.go @@ -0,0 +1,189 @@ +package tasks + +import ( + "carewallet/models" + "strconv" + "strings" + + "github.com/jackc/pgx" +) + +func GetTasksByGroupIdFromDB(pool *pgx.Conn, groupId string) ([]models.Task, error) { + groupID, err := strconv.Atoi(groupId) + if err != nil { + return nil, err + } + + rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE group_id = $1;", groupID) + + if err != nil { + print(err, "error selecting tasks by group id") + + return nil, err + } + + defer rows.Close() + + var results []models.Task + + for rows.Next() { + task := models.Task{} + err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) + + if err != nil { + print(err, "error scanning tasks by group id") + + return nil, err + } + results = append(results, task) + } + + return results, nil +} + +func GetTasksByCreatedByFromDB(pool *pgx.Conn, createdBy string) ([]models.Task, error) { + rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE created_by = $1;", createdBy) + + if err != nil { + print(err, "error selecting tasks by user id") + + return nil, err + } + + defer rows.Close() + + var results []models.Task + + for rows.Next() { + task := models.Task{} + err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) + + if err != nil { + print(err, "error scanning tasks by user id") + + return nil, err + } + + results = append(results, task) + } + + return results, nil +} + +func GetTasksByStatusFromDB(pool *pgx.Conn, status string) ([]models.Task, error) { + task_status := strings.ToUpper(status) + rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type from task WHERE task_status = $1;", task_status) + + if err != nil { + print(err, "error selecting tasks by status") + + return nil, err + } + + defer rows.Close() + + var results []models.Task + + for rows.Next() { + task := models.Task{} + err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) + + if err != nil { + print(err, "error scanning tasks by status") + + return nil, err + } + + results = append(results, task) + } + + return results, nil +} + +func GetTasksByTypeFromDB(pool *pgx.Conn, taskType string) ([]models.Task, error) { + task_type := strings.ToLower(taskType) + rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE task_type = $1;", task_type) + + if err != nil { + print(err, "error selecting tasks by type") + + return nil, err + } + + defer rows.Close() + + var results []models.Task + + for rows.Next() { + task := models.Task{} + err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) + + if err != nil { + print(err, "error scanning tasks by type") + + return nil, err + } + + results = append(results, task) + } + + return results, nil +} + +func GetTasksByStartDateFromDB(pool *pgx.Conn, startDate string) ([]models.Task, error) { + rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE start_date = $1;", startDate) + + if err != nil { + print(err, "error selecting tasks by start date") + + return nil, err + } + + defer rows.Close() + + var results []models.Task + + for rows.Next() { + task := models.Task{} + err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) + + if err != nil { + print(err, "error scanning tasks by start date") + + return nil, err + } + + results = append(results, task) + } + + return results, nil +} + +func GetTasksByEndDateFromDB(pool *pgx.Conn, endDate string) ([]models.Task, error) { + rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE end_date = $1;", endDate) + + if err != nil { + print(err, "error selecting tasks by end date") + + return nil, err + } + + defer rows.Close() + + var results []models.Task + + for rows.Next() { + task := models.Task{} + err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) + + if err != nil { + print(err, "error scanning tasks by end date") + + return nil, err + } + + results = append(results, task) + } + + return results, nil +} \ No newline at end of file From dc4e66b1d9525e92c3a9fc6344172a0b5c9243f2 Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Thu, 1 Feb 2024 16:16:51 -0500 Subject: [PATCH 03/29] style: formatting --- backend/models/task.go | 2 +- backend/schema/tasks/routes.go | 2 +- backend/schema/tasks/task_test.go | 2 +- backend/schema/tasks/transactions.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/models/task.go b/backend/models/task.go index 7dd5193..5bd4e1c 100644 --- a/backend/models/task.go +++ b/backend/models/task.go @@ -18,4 +18,4 @@ type Task struct { TaskStatus string `json:"task_status"` TaskType string `json:"task_type"` TaskInfo *string `json:"task_info"` -} \ No newline at end of file +} diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index 00b6615..a890a0b 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -126,4 +126,4 @@ func (pg *PgModel) GetTasksByEndDate(c *gin.Context) { } c.JSON(http.StatusOK, tasks) -} \ No newline at end of file +} diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 03a1bf9..16487d8 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -204,4 +204,4 @@ func TestGetTasks(t *testing.T) { t.Error("Result was not correct") } }) -} \ No newline at end of file +} diff --git a/backend/schema/tasks/transactions.go b/backend/schema/tasks/transactions.go index ee0ded4..1bd5e42 100644 --- a/backend/schema/tasks/transactions.go +++ b/backend/schema/tasks/transactions.go @@ -186,4 +186,4 @@ func GetTasksByEndDateFromDB(pool *pgx.Conn, endDate string) ([]models.Task, err } return results, nil -} \ No newline at end of file +} From 51bd4b544a02138654086b70c176ea0a7647dc8a Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Thu, 1 Feb 2024 16:24:46 -0500 Subject: [PATCH 04/29] fix: Remove commitizen branch --- .pre-commit-config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ded1a6f..2323431 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,5 +20,3 @@ repos: rev: v3.13.0 hooks: - id: commitizen - - id: commitizen-branch - stages: [push] From 8e40ed242a4303b4b37104843613abcfc918c3ff Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Thu, 1 Feb 2024 16:49:45 -0500 Subject: [PATCH 05/29] test: fix task tests --- backend/db/migrations/init.sql | 2 +- backend/docs/docs.go | 4 +-- backend/docs/swagger.json | 4 +-- backend/docs/swagger.yaml | 4 +-- backend/schema/tasks/task_test.go | 60 +++++++++++++++++++++++++++---- 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql index c380755..132f1b8 100644 --- a/backend/db/migrations/init.sql +++ b/backend/db/migrations/init.sql @@ -154,7 +154,7 @@ VALUES INSERT INTO task (group_id, created_by, created_date, start_date, end_date, notes, task_status, task_type) VALUES (1, 'user2', '2024-02-03 10:45:00', '2024-02-05 10:00:00', '2024-02-05 11:00:00', 'Pick up medication from pharmacy', 'INCOMPLETE', 'med_mgmt'), - (2, 'user3', '2024-02-20 23:59:59', '2024-02-10 14:30:00', NULL, 'Schedule doctor appointment', 'INCOMPLETE', 'dr_appt'), + (2, 'user3', '2024-02-20 23:59:59', '2024-02-10 14:30:00', NULL, 'Schedule doctor appointment', 'INCOMPLETE', 'other'), (3, 'user4', '2020-02-05 11:00:00', NULL, '2024-02-20 23:59:59', 'Submit insurance claim', 'PARTIAL', 'financial'), (4, 'user1', '2006-01-02 15:04:05', NULL, NULL, 'Refill water pitcher', 'COMPLETE', 'other') ; diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 63057d2..b271426 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -151,11 +151,11 @@ const docTemplate = `{ }, "/tasks/{uid}": { "get": { - "description": "get all tasks by user id", + "description": "get all tasks by created by", "tags": [ "tasks" ], - "summary": "Get All Tasks By User ID", + "summary": "Get All Tasks By Created By", "responses": { "200": { "description": "OK", diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 212af82..4f772a9 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -144,11 +144,11 @@ }, "/tasks/{uid}": { "get": { - "description": "get all tasks by user id", + "description": "get all tasks by created by", "tags": [ "tasks" ], - "summary": "Get All Tasks By User ID", + "summary": "Get All Tasks By Created By", "responses": { "200": { "description": "OK", diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 053172c..54db19c 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -132,7 +132,7 @@ paths: - tasks /tasks/{uid}: get: - description: get all tasks by user id + description: get all tasks by created by responses: "200": description: OK @@ -140,7 +140,7 @@ paths: items: $ref: '#/definitions/models.Task' type: array - summary: Get All Tasks By User ID + summary: Get All Tasks By Created By tags: - tasks swagger: "2.0" diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 16487d8..8143691 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -86,7 +86,7 @@ func TestGetTasks(t *testing.T) { t.Error("Failed to unmarshal json") } - createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-01 20:14:26.126136") + createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-03 10:45:00") expectedTasks := []models.Task{ { TaskID: 1, @@ -136,6 +136,57 @@ func TestGetTasks(t *testing.T) { } }) + t.Run("TestGetTasksByType", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/tasks/type/other", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to retrieve tasks by start date.") + } + + var responseTasks []models.Task + err := json.Unmarshal(w.Body.Bytes(), &responseTasks) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + createdDate1, _ := time.Parse("2006-01-02 15:04:05", "2024-02-20 23:59:59") + createdDate2, _ := time.Parse("2006-01-02 15:04:05", "2006-01-02 15:04:05") + expectedTasks := []models.Task{ + { + TaskID: 2, + GroupID: 2, + CreatedBy: "user3", + CreatedDate: createdDate1, + TaskStatus: "INCOMPLETE", + TaskType: "other", + }, + { + TaskID: 4, + GroupID: 4, + CreatedBy: "user1", + CreatedDate: createdDate2, + TaskStatus: "COMPLETE", + TaskType: "other", + }, + } + + fmt.Println( + "responseTasks: ", responseTasks, + ) + fmt.Println( + "expectedTasks: ", expectedTasks, + ) + + if !reflect.DeepEqual(expectedTasks, responseTasks) { + t.Error("Result was not correct") + } + }) + + + t.Run("TestGetTasksByStartDate", func(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/tasks/start/2024-02-05 10:00:00", nil) @@ -152,7 +203,7 @@ func TestGetTasks(t *testing.T) { t.Error("Failed to unmarshal json") } - createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-01 20:14:26.126136") + createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-03 10:45:00") expectedTasks := []models.Task{ { TaskID: 1, @@ -164,9 +215,6 @@ func TestGetTasks(t *testing.T) { }, } - fmt.Println("Expected:", expectedTasks) - fmt.Println("Response: ", responseTasks) - if !reflect.DeepEqual(expectedTasks, responseTasks) { t.Error("Result was not correct") } @@ -188,7 +236,7 @@ func TestGetTasks(t *testing.T) { t.Error("Failed to unmarshal json") } - createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-01 20:14:26.126136") + createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-03 10:45:00") expectedTasks := []models.Task{ { TaskID: 1, From ccd9ce1312b44fe1eefba1c4236f918bf3258040 Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Thu, 1 Feb 2024 16:53:12 -0500 Subject: [PATCH 06/29] style: quick reformat --- backend/models/task.go | 26 +++++++++++++------------- backend/schema/tasks/task_test.go | 2 -- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/backend/models/task.go b/backend/models/task.go index 5bd4e1c..4f700c2 100644 --- a/backend/models/task.go +++ b/backend/models/task.go @@ -5,17 +5,17 @@ import ( ) type Task struct { - TaskID int `json:"task_id"` - GroupID int `json:"group_id"` - CreatedBy string `json:"created_by"` // User ID - CreatedDate time.Time `json:"created_date"` - StartDate *time.Time `json:"start_date"` - EndDate *time.Time `json:"end_date"` - Notes *string `json:"notes"` - Repeating bool `json:"repeating"` - RepeatingInterval *string `json:"repeating_interval"` - RepeatingEndDate *time.Time `json:"repeating_end_date"` - TaskStatus string `json:"task_status"` - TaskType string `json:"task_type"` - TaskInfo *string `json:"task_info"` + TaskID int `json:"task_id"` + GroupID int `json:"group_id"` + CreatedBy string `json:"created_by"` // User ID + CreatedDate time.Time `json:"created_date"` + StartDate *time.Time `json:"start_date"` + EndDate *time.Time `json:"end_date"` + Notes *string `json:"notes"` + Repeating bool `json:"repeating"` + RepeatingInterval *string `json:"repeating_interval"` + RepeatingEndDate *time.Time `json:"repeating_end_date"` + TaskStatus string `json:"task_status"` + TaskType string `json:"task_type"` + TaskInfo *string `json:"task_info"` } diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 8143691..bff88d5 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -185,8 +185,6 @@ func TestGetTasks(t *testing.T) { } }) - - t.Run("TestGetTasksByStartDate", func(t *testing.T) { w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/tasks/start/2024-02-05 10:00:00", nil) From ac31075287e62af34d183e362599a20e3b925c88 Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Fri, 2 Feb 2024 10:37:33 -0500 Subject: [PATCH 07/29] feat: assign user(s) to a task CRUD and test --- backend/docs/docs.go | 31 +++++++++++++++ backend/docs/swagger.json | 31 +++++++++++++++ backend/docs/swagger.yaml | 20 ++++++++++ backend/models/task_user.go | 6 +++ backend/schema/tasks/routes.go | 30 +++++++++++++++ backend/schema/tasks/task_test.go | 56 +++++++++++++++++++++++++++- backend/schema/tasks/transactions.go | 28 ++++++++++++++ 7 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 backend/models/task_user.go diff --git a/backend/docs/docs.go b/backend/docs/docs.go index b271426..896014d 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -129,6 +129,26 @@ const docTemplate = `{ } } }, + "/tasks/{tid}/assignees": { + "post": { + "description": "assign users to task", + "tags": [ + "tasks" + ], + "summary": "Assign Users To Task", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.TaskUser" + } + } + } + } + } + }, "/tasks/{type}": { "get": { "description": "get all tasks by type", @@ -226,6 +246,17 @@ const docTemplate = `{ "type": "string" } } + }, + "models.TaskUser": { + "type": "object", + "properties": { + "taskID": { + "type": "integer" + }, + "userID": { + "type": "string" + } + } } } }` diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 4f772a9..b5feab8 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -122,6 +122,26 @@ } } }, + "/tasks/{tid}/assignees": { + "post": { + "description": "assign users to task", + "tags": [ + "tasks" + ], + "summary": "Assign Users To Task", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.TaskUser" + } + } + } + } + } + }, "/tasks/{type}": { "get": { "description": "get all tasks by type", @@ -219,6 +239,17 @@ "type": "string" } } + }, + "models.TaskUser": { + "type": "object", + "properties": { + "taskID": { + "type": "integer" + }, + "userID": { + "type": "string" + } + } } } } \ No newline at end of file diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 54db19c..85ff88e 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -37,6 +37,13 @@ definitions: task_type: type: string type: object + models.TaskUser: + properties: + taskID: + type: integer + userID: + type: string + type: object info: contact: {} description: This is an API for the Care-Wallet App. @@ -117,6 +124,19 @@ paths: summary: Get All Tasks By Status tags: - tasks + /tasks/{tid}/assignees: + post: + description: assign users to task + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.TaskUser' + type: array + summary: Assign Users To Task + tags: + - tasks /tasks/{type}: get: description: get all tasks by type diff --git a/backend/models/task_user.go b/backend/models/task_user.go new file mode 100644 index 0000000..105a996 --- /dev/null +++ b/backend/models/task_user.go @@ -0,0 +1,6 @@ +package models + +type TaskUser struct { + TaskID int + UserID string +} diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index a890a0b..ea5050b 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -1,6 +1,7 @@ package tasks import ( + "fmt" "net/http" "github.com/gin-gonic/gin" @@ -21,6 +22,7 @@ func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { tasks.GET("/type/:type", c.GetTasksByType) tasks.GET("/start/:startDate", c.GetTasksByStartDate) tasks.GET("/end/:endDate", c.GetTasksByEndDate) + tasks.POST("/:tid/assignees", c.AssignUsersToTask) } return tasks @@ -127,3 +129,31 @@ func (pg *PgModel) GetTasksByEndDate(c *gin.Context) { c.JSON(http.StatusOK, tasks) } + +// AssignUsersToTask godoc +// +// @summary Assign Users To Task +// @description assign users to task +// @tags tasks +// @success 200 {array} models.TaskUser +// @router /tasks/{tid}/assignees [post] +func (pg *PgModel) AssignUsersToTask(c *gin.Context) { + var requestBody struct { + UserIDs []string `json:"userIDs"` + Assigner string `json:"assigner"` + } + + if err := c.BindJSON(&requestBody); err != nil { + fmt.Println("error binding to request body: ", err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + taskUser, err := AssignUsersToTaskInDB(pg.Conn, requestBody.UserIDs, c.Param("tid"), requestBody.Assigner) + + if err != nil { + panic(err) + } + + c.JSON(http.StatusOK, taskUser) +} diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index bff88d5..370b1fe 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -1,6 +1,7 @@ package tasks import ( + "bytes" "carewallet/configuration" "carewallet/db" "carewallet/models" @@ -17,7 +18,7 @@ import ( "github.com/gin-gonic/gin" ) -func TestGetTasks(t *testing.T) { +func TestTaskGroup(t *testing.T) { config, err := configuration.GetConfiguration() if err != nil { @@ -250,4 +251,57 @@ func TestGetTasks(t *testing.T) { t.Error("Result was not correct") } }) + + t.Run("TestAssignUsersToTask", func(t *testing.T) { + type AssignRequest struct { + UserIDs []string `json:"userIDs"` + Assigner string `json:"assigner"` + } + + assignRequest := AssignRequest{ + UserIDs: []string{"user3", "user4"}, + Assigner: "user3", + } + + userIdsJSON, err := json.Marshal(assignRequest) + if err != nil { + t.Error("Failed to marshal userIds to JSON") + } + + fmt.Println("userIdsJSON: ", string(userIdsJSON)) + + // Create a request with the userIds JSON + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/tasks/1/assignees", bytes.NewBuffer(userIdsJSON)) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to assign users to task.") + } + + var responseTaskUsers []models.TaskUser + err = json.Unmarshal(w.Body.Bytes(), &responseTaskUsers) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + expectedTaskUsers := []models.TaskUser{ + { + TaskID: 1, + UserID: "user3", + }, + { + TaskID: 1, + UserID: "user4", + }, + } + + fmt.Println("responseTaskUsers: ", responseTaskUsers) + fmt.Println("expectedTaskUsers: ", expectedTaskUsers) + + if !reflect.DeepEqual(expectedTaskUsers, responseTaskUsers) { + t.Error("Result was not correct") + } + }) } diff --git a/backend/schema/tasks/transactions.go b/backend/schema/tasks/transactions.go index 1bd5e42..5a1de66 100644 --- a/backend/schema/tasks/transactions.go +++ b/backend/schema/tasks/transactions.go @@ -2,8 +2,10 @@ package tasks import ( "carewallet/models" + "fmt" "strconv" "strings" + "time" "github.com/jackc/pgx" ) @@ -187,3 +189,29 @@ func GetTasksByEndDateFromDB(pool *pgx.Conn, endDate string) ([]models.Task, err return results, nil } + +func AssignUsersToTaskInDB(pool *pgx.Conn, users []string, taskID string, assigner string) ([]models.TaskUser, error) { + task_id, err := strconv.Atoi(taskID) + if err != nil { + print(err, "error converting task ID to int") + return nil, err + } + + var taskUsers []models.TaskUser + + for _, user := range users { + print(task_id, " ", user) + _, err := pool.Exec("INSERT INTO task_assignees (task_id, user_id, assignment_status, assigned_by, assigned_date) VALUES ($1, $2, $3, $4, $5);", task_id, user, "NOTIFIED", assigner, time.Now()) + + if err != nil { + print(err, "error inserting users into task_user") + + return nil, err + } + + taskUsers = append(taskUsers, models.TaskUser{TaskID: task_id, UserID: user}) + fmt.Println(taskUsers) + } + + return taskUsers, nil +} From cc20bd35715950ea10db5f4bf04c672f0308efdf Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Fri, 2 Feb 2024 11:20:31 -0500 Subject: [PATCH 08/29] style: quick style --- backend/docs/docs.go | 20 +++++++++ backend/docs/swagger.json | 20 +++++++++ backend/docs/swagger.yaml | 13 ++++++ backend/schema/tasks/routes.go | 34 ++++++++++++++-- backend/schema/tasks/task_test.go | 61 ++++++++++++++++++++-------- backend/schema/tasks/transactions.go | 44 +++++++++++++++++--- 6 files changed, 168 insertions(+), 24 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 896014d..46ce572 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -149,6 +149,26 @@ const docTemplate = `{ } } }, + "/tasks/{tid}/remove": { + "delete": { + "description": "remove users from task", + "tags": [ + "tasks" + ], + "summary": "Remove Users From Task", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.TaskUser" + } + } + } + } + } + }, "/tasks/{type}": { "get": { "description": "get all tasks by type", diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index b5feab8..f4f9312 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -142,6 +142,26 @@ } } }, + "/tasks/{tid}/remove": { + "delete": { + "description": "remove users from task", + "tags": [ + "tasks" + ], + "summary": "Remove Users From Task", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.TaskUser" + } + } + } + } + } + }, "/tasks/{type}": { "get": { "description": "get all tasks by type", diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 85ff88e..23dc752 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -137,6 +137,19 @@ paths: summary: Assign Users To Task tags: - tasks + /tasks/{tid}/remove: + delete: + description: remove users from task + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.TaskUser' + type: array + summary: Remove Users From Task + tags: + - tasks /tasks/{type}: get: description: get all tasks by type diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index ea5050b..c6c2af6 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -22,7 +22,8 @@ func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { tasks.GET("/type/:type", c.GetTasksByType) tasks.GET("/start/:startDate", c.GetTasksByStartDate) tasks.GET("/end/:endDate", c.GetTasksByEndDate) - tasks.POST("/:tid/assignees", c.AssignUsersToTask) + tasks.POST("/:tid/assign", c.AssignUsersToTask) + tasks.DELETE("/:tid/remove", c.RemoveUsersFromTask) } return tasks @@ -149,11 +150,38 @@ func (pg *PgModel) AssignUsersToTask(c *gin.Context) { return } - taskUser, err := AssignUsersToTaskInDB(pg.Conn, requestBody.UserIDs, c.Param("tid"), requestBody.Assigner) + assignedUsers, err := AssignUsersToTaskInDB(pg.Conn, requestBody.UserIDs, c.Param("tid"), requestBody.Assigner) if err != nil { panic(err) } - c.JSON(http.StatusOK, taskUser) + c.JSON(http.StatusOK, assignedUsers) +} + +// RemoveUsersFromTask godoc +// +// @summary Remove Users From Task +// @description remove users from task +// @tags tasks +// @success 200 {array} models.TaskUser +// @router /tasks/{tid}/remove [delete] +func (pg *PgModel) RemoveUsersFromTask(c *gin.Context) { + var requestBody struct { + UserIDs []string `json:"userIDs"` + } + + if err := c.BindJSON(&requestBody); err != nil { + fmt.Println("error binding to request body: ", err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + removedUsers, err := RemoveUsersFromTaskInDB(pg.Conn, requestBody.UserIDs, c.Param("tid")) + + if err != nil { + panic(err) + } + + c.JSON(http.StatusOK, removedUsers) } diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 370b1fe..0af417b 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -174,13 +174,6 @@ func TestTaskGroup(t *testing.T) { }, } - fmt.Println( - "responseTasks: ", responseTasks, - ) - fmt.Println( - "expectedTasks: ", expectedTasks, - ) - if !reflect.DeepEqual(expectedTasks, responseTasks) { t.Error("Result was not correct") } @@ -259,7 +252,7 @@ func TestTaskGroup(t *testing.T) { } assignRequest := AssignRequest{ - UserIDs: []string{"user3", "user4"}, + UserIDs: []string{"user3"}, Assigner: "user3", } @@ -268,11 +261,9 @@ func TestTaskGroup(t *testing.T) { t.Error("Failed to marshal userIds to JSON") } - fmt.Println("userIdsJSON: ", string(userIdsJSON)) - // Create a request with the userIds JSON w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/tasks/1/assignees", bytes.NewBuffer(userIdsJSON)) + req, _ := http.NewRequest("POST", "/tasks/4/assign", bytes.NewBuffer(userIdsJSON)) router.ServeHTTP(w, req) if http.StatusOK != w.Code { @@ -288,13 +279,9 @@ func TestTaskGroup(t *testing.T) { expectedTaskUsers := []models.TaskUser{ { - TaskID: 1, + TaskID: 4, UserID: "user3", }, - { - TaskID: 1, - UserID: "user4", - }, } fmt.Println("responseTaskUsers: ", responseTaskUsers) @@ -304,4 +291,46 @@ func TestTaskGroup(t *testing.T) { t.Error("Result was not correct") } }) + + t.Run("TestRemoveUsersFromTask", func(t *testing.T) { + type RemoveRequest struct { + UserIDs []string `json:"userIDs"` + } + + removeRequest := RemoveRequest{ + UserIDs: []string{"user2"}, + } + + userIdsJSON, err := json.Marshal(removeRequest) + if err != nil { + t.Error("Failed to marshal userIds to JSON") + } + + // Create a request with the userIds JSON + w := httptest.NewRecorder() + req, _ := http.NewRequest("DELETE", "/tasks/4/remove", bytes.NewBuffer(userIdsJSON)) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to remove users from task.") + } + + var responseTaskUsers []models.TaskUser + err = json.Unmarshal(w.Body.Bytes(), &responseTaskUsers) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + expectedTaskUsers := []models.TaskUser{ + { + TaskID: 4, + UserID: "user2", + }, + } + + if !reflect.DeepEqual(expectedTaskUsers, responseTaskUsers) { + t.Error("Result was not correct") + } + }) } diff --git a/backend/schema/tasks/transactions.go b/backend/schema/tasks/transactions.go index 5a1de66..498bdc3 100644 --- a/backend/schema/tasks/transactions.go +++ b/backend/schema/tasks/transactions.go @@ -197,21 +197,55 @@ func AssignUsersToTaskInDB(pool *pgx.Conn, users []string, taskID string, assign return nil, err } - var taskUsers []models.TaskUser + var assignedUsers []models.TaskUser for _, user := range users { print(task_id, " ", user) _, err := pool.Exec("INSERT INTO task_assignees (task_id, user_id, assignment_status, assigned_by, assigned_date) VALUES ($1, $2, $3, $4, $5);", task_id, user, "NOTIFIED", assigner, time.Now()) if err != nil { - print(err, "error inserting users into task_user") + print(err, "error inserting users into task_assignees") return nil, err } - taskUsers = append(taskUsers, models.TaskUser{TaskID: task_id, UserID: user}) - fmt.Println(taskUsers) + assignedUsers = append(assignedUsers, models.TaskUser{TaskID: task_id, UserID: user}) + fmt.Println(assignedUsers) } - return taskUsers, nil + return assignedUsers, nil +} + +func RemoveUsersFromTaskInDB(pool *pgx.Conn, users []string, taskID string) ([]models.TaskUser, error) { + task_id, err := strconv.Atoi(taskID) + if err != nil { + print(err, "error converting task ID to int") + return nil, err + } + + var removedUsers []models.TaskUser + + for _, user := range users { + // Check if the user ID and task ID exist in the table + var exists int + err := pool.QueryRow("SELECT 1 FROM task_assignees WHERE task_id = $1 AND user_id = $2 LIMIT 1;", task_id, user).Scan(&exists) + if err != nil { + if err == pgx.ErrNoRows { + // User ID or task ID does not exist, return an error + return nil, fmt.Errorf("user not assigned to task") + } + print(err, "error checking if user and task exist in task_assignees") + return nil, err + } + + _, err = pool.Exec("DELETE FROM task_assignees WHERE task_id = $1 AND user_id = $2;", task_id, user) + if err != nil { + print(err, "error deleting users from task_assignees") + return nil, err + } + + removedUsers = append(removedUsers, models.TaskUser{TaskID: task_id, UserID: user}) + } + + return removedUsers, nil } From 36d9727312fba0bb552e8d8fa4d4ef644f3f26fd Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Fri, 2 Feb 2024 14:52:46 -0500 Subject: [PATCH 09/29] refactor: abstract queries, enable multiple --- backend/docs/docs.go | 165 ---------------------- backend/docs/swagger.json | 165 ---------------------- backend/docs/swagger.yaml | 128 ++--------------- backend/schema/tasks/routes.go | 114 +++------------ backend/schema/tasks/task_test.go | 202 +++------------------------ backend/schema/tasks/transactions.go | 177 ++++++----------------- 6 files changed, 94 insertions(+), 857 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 46ce572..8186cd9 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -49,86 +49,6 @@ const docTemplate = `{ } } }, - "/tasks/{endDate}": { - "get": { - "description": "get all tasks by end date", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By End Date", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } - }, - "/tasks/{gid}": { - "get": { - "description": "get all tasks by group id", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By Group ID", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } - }, - "/tasks/{startDate}": { - "get": { - "description": "get all tasks by start date", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By Start Date", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } - }, - "/tasks/{status}": { - "get": { - "description": "get all tasks by status", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By Status", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } - }, "/tasks/{tid}/assignees": { "post": { "description": "assign users to task", @@ -168,46 +88,6 @@ const docTemplate = `{ } } } - }, - "/tasks/{type}": { - "get": { - "description": "get all tasks by type", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By Type", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } - }, - "/tasks/{uid}": { - "get": { - "description": "get all tasks by created by", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By Created By", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } } }, "definitions": { @@ -222,51 +102,6 @@ const docTemplate = `{ } } }, - "models.Task": { - "type": "object", - "properties": { - "created_by": { - "description": "User ID", - "type": "string" - }, - "created_date": { - "type": "string" - }, - "end_date": { - "type": "string" - }, - "group_id": { - "type": "integer" - }, - "notes": { - "type": "string" - }, - "repeating": { - "type": "boolean" - }, - "repeating_end_date": { - "type": "string" - }, - "repeating_interval": { - "type": "string" - }, - "start_date": { - "type": "string" - }, - "task_id": { - "type": "integer" - }, - "task_info": { - "type": "string" - }, - "task_status": { - "type": "string" - }, - "task_type": { - "type": "string" - } - } - }, "models.TaskUser": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index f4f9312..abb0696 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -42,86 +42,6 @@ } } }, - "/tasks/{endDate}": { - "get": { - "description": "get all tasks by end date", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By End Date", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } - }, - "/tasks/{gid}": { - "get": { - "description": "get all tasks by group id", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By Group ID", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } - }, - "/tasks/{startDate}": { - "get": { - "description": "get all tasks by start date", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By Start Date", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } - }, - "/tasks/{status}": { - "get": { - "description": "get all tasks by status", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By Status", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } - }, "/tasks/{tid}/assignees": { "post": { "description": "assign users to task", @@ -161,46 +81,6 @@ } } } - }, - "/tasks/{type}": { - "get": { - "description": "get all tasks by type", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By Type", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } - }, - "/tasks/{uid}": { - "get": { - "description": "get all tasks by created by", - "tags": [ - "tasks" - ], - "summary": "Get All Tasks By Created By", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - } - } - } - } } }, "definitions": { @@ -215,51 +95,6 @@ } } }, - "models.Task": { - "type": "object", - "properties": { - "created_by": { - "description": "User ID", - "type": "string" - }, - "created_date": { - "type": "string" - }, - "end_date": { - "type": "string" - }, - "group_id": { - "type": "integer" - }, - "notes": { - "type": "string" - }, - "repeating": { - "type": "boolean" - }, - "repeating_end_date": { - "type": "string" - }, - "repeating_interval": { - "type": "string" - }, - "start_date": { - "type": "string" - }, - "task_id": { - "type": "integer" - }, - "task_info": { - "type": "string" - }, - "task_status": { - "type": "string" - }, - "task_type": { - "type": "string" - } - } - }, "models.TaskUser": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 23dc752..2c37bdc 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -7,36 +7,6 @@ definitions: medication_name: type: string type: object - models.Task: - properties: - created_by: - description: User ID - type: string - created_date: - type: string - end_date: - type: string - group_id: - type: integer - notes: - type: string - repeating: - type: boolean - repeating_end_date: - type: string - repeating_interval: - type: string - start_date: - type: string - task_id: - type: integer - task_info: - type: string - task_status: - type: string - task_type: - type: string - type: object models.TaskUser: properties: taskID: @@ -48,22 +18,22 @@ info: contact: {} description: This is an API for the Care-Wallet App. title: Care-Wallet API - version: "1.0" + version: '1.0' paths: /files/upload: post: description: Upload a file to database and S3 bucket responses: - "200": + '200': description: OK summary: Upload a file tags: - - file + - file /medications: get: description: get all user medications responses: - "200": + '200': description: OK schema: items: @@ -71,64 +41,12 @@ paths: type: array summary: Get All Meds tags: - - medications - /tasks/{endDate}: - get: - description: get all tasks by end date - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Task' - type: array - summary: Get All Tasks By End Date - tags: - - tasks - /tasks/{gid}: - get: - description: get all tasks by group id - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Task' - type: array - summary: Get All Tasks By Group ID - tags: - - tasks - /tasks/{startDate}: - get: - description: get all tasks by start date - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Task' - type: array - summary: Get All Tasks By Start Date - tags: - - tasks - /tasks/{status}: - get: - description: get all tasks by status - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Task' - type: array - summary: Get All Tasks By Status - tags: - - tasks + - medications /tasks/{tid}/assignees: post: description: assign users to task responses: - "200": + '200': description: OK schema: items: @@ -136,12 +54,12 @@ paths: type: array summary: Assign Users To Task tags: - - tasks + - tasks /tasks/{tid}/remove: delete: description: remove users from task responses: - "200": + '200': description: OK schema: items: @@ -149,31 +67,5 @@ paths: type: array summary: Remove Users From Task tags: - - tasks - /tasks/{type}: - get: - description: get all tasks by type - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Task' - type: array - summary: Get All Tasks By Type - tags: - - tasks - /tasks/{uid}: - get: - description: get all tasks by created by - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/models.Task' - type: array - summary: Get All Tasks By Created By - tags: - - tasks -swagger: "2.0" + - tasks +swagger: '2.0' diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index c6c2af6..4f57eea 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -16,12 +16,7 @@ func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { tasks := v1.Group("tasks") { - tasks.GET("/group/:gid", c.GetTasksByGroupId) - tasks.GET("/created_by/:uid", c.GetTasksByCreatedBy) - tasks.GET("/status/:status", c.GetTasksByStatus) - tasks.GET("/type/:type", c.GetTasksByType) - tasks.GET("/start/:startDate", c.GetTasksByStartDate) - tasks.GET("/end/:endDate", c.GetTasksByEndDate) + tasks.GET("/filtered", c.GetFilteredTasks) tasks.POST("/:tid/assign", c.AssignUsersToTask) tasks.DELETE("/:tid/remove", c.RemoveUsersFromTask) } @@ -29,100 +24,33 @@ func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { return tasks } -// GetTasksByGroupId godoc -// -// @summary Get All Tasks By Group ID -// @description get all tasks by group id -// @tags tasks -// @success 200 {array} models.Task -// @router /tasks/{gid} [get] -func (pg *PgModel) GetTasksByGroupId(c *gin.Context) { - tasks, err := GetTasksByGroupIdFromDB(pg.Conn, c.Param("gid")) - - if err != nil { - panic(err) - } - - c.JSON(http.StatusOK, tasks) -} - -// GetTasksByCreatedBy godoc -// -// @summary Get All Tasks By Created By -// @description get all tasks by created by -// @tags tasks -// @success 200 {array} models.Task -// @router /tasks/{uid} [get] -func (pg *PgModel) GetTasksByCreatedBy(c *gin.Context) { - tasks, err := GetTasksByCreatedByFromDB(pg.Conn, c.Param("uid")) - - if err != nil { - panic(err) - } - - c.JSON(http.StatusOK, tasks) +type TaskQuery struct { + GroupID string `query:"groupID"` + CreatedBy string `query:"createdBy"` + TaskStatus string `query:"taskStatus"` + TaskType string `query:"taskType"` + StartDate string `query:"startDate"` + EndDate string `query:"endDate"` } -// GetTasksByStatus godoc +// GetFilteredTasks godoc // -// @summary Get All Tasks By Status -// @description get all tasks by status +// @summary Get Filtered Tasks +// @description get filtered tasks // @tags tasks // @success 200 {array} models.Task -// @router /tasks/{status} [get] -func (pg *PgModel) GetTasksByStatus(c *gin.Context) { - tasks, err := GetTasksByStatusFromDB(pg.Conn, c.Param("status")) - - if err != nil { - panic(err) +// @router /tasks/filtered [get] +func (pg *PgModel) GetFilteredTasks(c *gin.Context) { + filterQuery := TaskQuery{ + GroupID: c.Query("GroupID"), + CreatedBy: c.Query("CreatedBy"), + TaskStatus: c.Query("TaskStatus"), + TaskType: c.Query("TaskType"), + StartDate: c.Query("StartDate"), + EndDate: c.Query("EndDate"), } - c.JSON(http.StatusOK, tasks) -} - -// GetTasksByType godoc -// -// @summary Get All Tasks By Type -// @description get all tasks by type -// @tags tasks -// @success 200 {array} models.Task -// @router /tasks/{type} [get] -func (pg *PgModel) GetTasksByType(c *gin.Context) { - tasks, err := GetTasksByTypeFromDB(pg.Conn, c.Param("type")) - - if err != nil { - panic(err) - } - - c.JSON(http.StatusOK, tasks) -} - -// GetTasksByStartDate godoc -// -// @summary Get All Tasks By Start Date -// @description get all tasks by start date -// @tags tasks -// @success 200 {array} models.Task -// @router /tasks/{startDate} [get] -func (pg *PgModel) GetTasksByStartDate(c *gin.Context) { - tasks, err := GetTasksByStartDateFromDB(pg.Conn, c.Param("startDate")) - - if err != nil { - panic(err) - } - - c.JSON(http.StatusOK, tasks) -} - -// GetTasksByEndDate godoc -// -// @summary Get All Tasks By End Date -// @description get all tasks by end date -// @tags tasks -// @success 200 {array} models.Task -// @router /tasks/{endDate} [get] -func (pg *PgModel) GetTasksByEndDate(c *gin.Context) { - tasks, err := GetTasksByEndDateFromDB(pg.Conn, c.Param("endDate")) + tasks, err := GetTasksByQueryFromDB(pg.Conn, filterQuery) if err != nil { panic(err) diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 0af417b..8c60e15 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "net/url" "os" "reflect" "testing" @@ -38,208 +39,51 @@ func TestTaskGroup(t *testing.T) { TaskGroup(v1, &controller) } - t.Run("TestGetTasksByGroupId", func(t *testing.T) { - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/tasks/group/4", nil) - router.ServeHTTP(w, req) - - if http.StatusOK != w.Code { - t.Error("Failed to retrieve tasks by group id.") - } - - var responseTasks []models.Task - err := json.Unmarshal(w.Body.Bytes(), &responseTasks) - - if err != nil { - t.Error("Failed to unmarshal json") - } - - createdDate, _ := time.Parse("2006-01-02 15:04:05", "2006-01-02 15:04:05") - expectedTasks := []models.Task{ - { - TaskID: 4, - GroupID: 4, - CreatedBy: "user1", - CreatedDate: createdDate, - TaskStatus: "COMPLETE", - TaskType: "other", - }, - } - - if !reflect.DeepEqual(expectedTasks, responseTasks) { - t.Error("Result was not correct") - } - }) - - t.Run("TestGetTasksByCreatedBy", func(t *testing.T) { - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/tasks/created_by/user2", nil) - router.ServeHTTP(w, req) - - if http.StatusOK != w.Code { - t.Error("Failed to retrieve tasks by user id.") - } - - var responseTasks []models.Task - err := json.Unmarshal(w.Body.Bytes(), &responseTasks) - - if err != nil { - t.Error("Failed to unmarshal json") - } - - createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-03 10:45:00") - expectedTasks := []models.Task{ - { - TaskID: 1, - GroupID: 1, - CreatedBy: "user2", - CreatedDate: createdDate, - TaskStatus: "INCOMPLETE", - TaskType: "med_mgmt", - }, - } - - if !reflect.DeepEqual(expectedTasks, responseTasks) { - t.Error("Result was not correct") - } - }) - - t.Run("TestGetTasksByStatus", func(t *testing.T) { - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/tasks/status/COMPLETE", nil) - router.ServeHTTP(w, req) - - if http.StatusOK != w.Code { - t.Error("Failed to retrieve tasks by status.") - } - - var responseTasks []models.Task - err := json.Unmarshal(w.Body.Bytes(), &responseTasks) - - if err != nil { - t.Error("Failed to unmarshal json") - } - - createdDate, _ := time.Parse("2006-01-02 15:04:05", "2006-01-02 15:04:05") - expectedTasks := []models.Task{ - { - TaskID: 4, - GroupID: 4, - CreatedBy: "user1", - CreatedDate: createdDate, - TaskStatus: "COMPLETE", - TaskType: "other", - }, - } - - if !reflect.DeepEqual(expectedTasks, responseTasks) { - t.Error("Result was not correct") + t.Run("TestGetFilteredTasks", func(t *testing.T) { + getRequest := TaskQuery{ + GroupID: "", + CreatedBy: "user2", + TaskStatus: "INCOMPLETE", + TaskType: "", + StartDate: "", + EndDate: "", } - }) - t.Run("TestGetTasksByType", func(t *testing.T) { w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/tasks/type/other", nil) + query := url.Values{} + query.Set("GroupID", getRequest.GroupID) + query.Set("CreatedBy", getRequest.CreatedBy) + query.Set("TaskStatus", getRequest.TaskStatus) + query.Set("TaskType", getRequest.TaskType) + query.Set("StartDate", getRequest.StartDate) + query.Set("EndDate", getRequest.EndDate) + + req, _ := http.NewRequest("GET", "/tasks/filtered?"+query.Encode(), nil) router.ServeHTTP(w, req) if http.StatusOK != w.Code { - t.Error("Failed to retrieve tasks by start date.") + t.Error("Failed to retrieve tasks by filter query.") } var responseTasks []models.Task - err := json.Unmarshal(w.Body.Bytes(), &responseTasks) + err = json.Unmarshal(w.Body.Bytes(), &responseTasks) if err != nil { t.Error("Failed to unmarshal json") } - createdDate1, _ := time.Parse("2006-01-02 15:04:05", "2024-02-20 23:59:59") - createdDate2, _ := time.Parse("2006-01-02 15:04:05", "2006-01-02 15:04:05") - expectedTasks := []models.Task{ - { - TaskID: 2, - GroupID: 2, - CreatedBy: "user3", - CreatedDate: createdDate1, - TaskStatus: "INCOMPLETE", - TaskType: "other", - }, - { - TaskID: 4, - GroupID: 4, - CreatedBy: "user1", - CreatedDate: createdDate2, - TaskStatus: "COMPLETE", - TaskType: "other", - }, - } - - if !reflect.DeepEqual(expectedTasks, responseTasks) { - t.Error("Result was not correct") - } - }) - - t.Run("TestGetTasksByStartDate", func(t *testing.T) { - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/tasks/start/2024-02-05 10:00:00", nil) - router.ServeHTTP(w, req) - - if http.StatusOK != w.Code { - t.Error("Failed to retrieve tasks by start date.") - } - - var responseTasks []models.Task - err := json.Unmarshal(w.Body.Bytes(), &responseTasks) - - if err != nil { - t.Error("Failed to unmarshal json") - } - - createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-03 10:45:00") - expectedTasks := []models.Task{ - { - TaskID: 1, - GroupID: 1, - CreatedBy: "user2", - CreatedDate: createdDate, - TaskStatus: "INCOMPLETE", - TaskType: "med_mgmt", - }, - } - - if !reflect.DeepEqual(expectedTasks, responseTasks) { - t.Error("Result was not correct") - } - }) - - t.Run("TestGetTasksByEndDate", func(t *testing.T) { - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/tasks/end/2024-02-05 11:00:00", nil) - router.ServeHTTP(w, req) - - if http.StatusOK != w.Code { - t.Error("Failed to retrieve tasks by end date.") - } - - var responseTasks []models.Task - err := json.Unmarshal(w.Body.Bytes(), &responseTasks) - - if err != nil { - t.Error("Failed to unmarshal json") - } - - createdDate, _ := time.Parse("2006-01-02 15:04:05", "2024-02-03 10:45:00") expectedTasks := []models.Task{ { TaskID: 1, GroupID: 1, CreatedBy: "user2", - CreatedDate: createdDate, + CreatedDate: time.Date(2024, 2, 3, 10, 45, 0, 0, time.UTC), TaskStatus: "INCOMPLETE", TaskType: "med_mgmt", }, } - + fmt.Println(expectedTasks) + fmt.Println(responseTasks) if !reflect.DeepEqual(expectedTasks, responseTasks) { t.Error("Result was not correct") } diff --git a/backend/schema/tasks/transactions.go b/backend/schema/tasks/transactions.go index 498bdc3..c081955 100644 --- a/backend/schema/tasks/transactions.go +++ b/backend/schema/tasks/transactions.go @@ -4,168 +4,71 @@ import ( "carewallet/models" "fmt" "strconv" - "strings" "time" "github.com/jackc/pgx" ) -func GetTasksByGroupIdFromDB(pool *pgx.Conn, groupId string) ([]models.Task, error) { - groupID, err := strconv.Atoi(groupId) - if err != nil { - return nil, err - } +func GetTasksByQueryFromDB(pool *pgx.Conn, filterQuery TaskQuery) ([]models.Task, error) { + groupID := filterQuery.GroupID + createdBy := filterQuery.CreatedBy + taskStatus := filterQuery.TaskStatus + taskType := filterQuery.TaskType + startDate := filterQuery.StartDate + endDate := filterQuery.EndDate - rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE group_id = $1;", groupID) + var query string + var args []interface{} - if err != nil { - print(err, "error selecting tasks by group id") - - return nil, err + if groupID != "" { + query += "group_id = $1" + args = append(args, groupID) } - defer rows.Close() - - var results []models.Task - - for rows.Next() { - task := models.Task{} - err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) - - if err != nil { - print(err, "error scanning tasks by group id") - - return nil, err + if createdBy != "" { + if query != "" { + query += " AND " } - results = append(results, task) - } - - return results, nil -} - -func GetTasksByCreatedByFromDB(pool *pgx.Conn, createdBy string) ([]models.Task, error) { - rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE created_by = $1;", createdBy) - - if err != nil { - print(err, "error selecting tasks by user id") - - return nil, err + query += fmt.Sprintf("created_by = $%d", len(args)+1) + args = append(args, createdBy) } - defer rows.Close() - - var results []models.Task - - for rows.Next() { - task := models.Task{} - err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) - - if err != nil { - print(err, "error scanning tasks by user id") - - return nil, err + if taskStatus != "" { + if query != "" { + query += " AND " } - - results = append(results, task) + query += fmt.Sprintf("task_status = $%d", len(args)+1) + args = append(args, taskStatus) } - return results, nil -} - -func GetTasksByStatusFromDB(pool *pgx.Conn, status string) ([]models.Task, error) { - task_status := strings.ToUpper(status) - rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type from task WHERE task_status = $1;", task_status) - - if err != nil { - print(err, "error selecting tasks by status") - - return nil, err - } - - defer rows.Close() - - var results []models.Task - - for rows.Next() { - task := models.Task{} - err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) - - if err != nil { - print(err, "error scanning tasks by status") - - return nil, err + if taskType != "" { + if query != "" { + query += " AND " } - - results = append(results, task) + query += fmt.Sprintf("task_type = $%d", len(args)+1) + args = append(args, taskType) } - return results, nil -} - -func GetTasksByTypeFromDB(pool *pgx.Conn, taskType string) ([]models.Task, error) { - task_type := strings.ToLower(taskType) - rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE task_type = $1;", task_type) - - if err != nil { - print(err, "error selecting tasks by type") - - return nil, err - } - - defer rows.Close() - - var results []models.Task - - for rows.Next() { - task := models.Task{} - err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) - - if err != nil { - print(err, "error scanning tasks by type") - - return nil, err + if startDate != "" { + if query != "" { + query += " AND " } - - results = append(results, task) - } - - return results, nil -} - -func GetTasksByStartDateFromDB(pool *pgx.Conn, startDate string) ([]models.Task, error) { - rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE start_date = $1;", startDate) - - if err != nil { - print(err, "error selecting tasks by start date") - - return nil, err + query += fmt.Sprintf("start_date = $%d", len(args)+1) + args = append(args, startDate) } - defer rows.Close() - - var results []models.Task - - for rows.Next() { - task := models.Task{} - err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) - - if err != nil { - print(err, "error scanning tasks by start date") - - return nil, err + if endDate != "" { + if query != "" { + query += " AND " } - - results = append(results, task) + query += fmt.Sprintf("end_date = $%d", len(args)+1) + args = append(args, endDate) } - return results, nil -} - -func GetTasksByEndDateFromDB(pool *pgx.Conn, endDate string) ([]models.Task, error) { - rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE end_date = $1;", endDate) + rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE "+query+";", args...) if err != nil { - print(err, "error selecting tasks by end date") + print(err, "error selecting tasks by query") return nil, err } @@ -179,7 +82,7 @@ func GetTasksByEndDateFromDB(pool *pgx.Conn, endDate string) ([]models.Task, err err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) if err != nil { - print(err, "error scanning tasks by end date") + print(err, "error scanning tasks by query") return nil, err } From 399c20f5bfc99e5c1d2df1e0d793a5bc152e410d Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Fri, 2 Feb 2024 15:57:02 -0500 Subject: [PATCH 10/29] refactor: abstract filter query/arg building logic --- backend/schema/tasks/task_test.go | 25 +++++++---- backend/schema/tasks/transactions.go | 64 +++++++--------------------- 2 files changed, 32 insertions(+), 57 deletions(-) diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 8c60e15..6c8f247 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -42,9 +42,9 @@ func TestTaskGroup(t *testing.T) { t.Run("TestGetFilteredTasks", func(t *testing.T) { getRequest := TaskQuery{ GroupID: "", - CreatedBy: "user2", - TaskStatus: "INCOMPLETE", - TaskType: "", + CreatedBy: "", + TaskStatus: "", + TaskType: "other", StartDate: "", EndDate: "", } @@ -74,14 +74,23 @@ func TestTaskGroup(t *testing.T) { expectedTasks := []models.Task{ { - TaskID: 1, - GroupID: 1, - CreatedBy: "user2", - CreatedDate: time.Date(2024, 2, 3, 10, 45, 0, 0, time.UTC), + TaskID: 2, + GroupID: 2, + CreatedBy: "user3", + CreatedDate: time.Date(2024, 2, 20, 23, 59, 59, 0, time.UTC), TaskStatus: "INCOMPLETE", - TaskType: "med_mgmt", + TaskType: "other", + }, + { + TaskID: 4, + GroupID: 4, + CreatedBy: "user1", + CreatedDate: time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC), + TaskStatus: "COMPLETE", + TaskType: "other", }, } + fmt.Println(expectedTasks) fmt.Println(responseTasks) if !reflect.DeepEqual(expectedTasks, responseTasks) { diff --git a/backend/schema/tasks/transactions.go b/backend/schema/tasks/transactions.go index c081955..5386f38 100644 --- a/backend/schema/tasks/transactions.go +++ b/backend/schema/tasks/transactions.go @@ -10,59 +10,25 @@ import ( ) func GetTasksByQueryFromDB(pool *pgx.Conn, filterQuery TaskQuery) ([]models.Task, error) { - groupID := filterQuery.GroupID - createdBy := filterQuery.CreatedBy - taskStatus := filterQuery.TaskStatus - taskType := filterQuery.TaskType - startDate := filterQuery.StartDate - endDate := filterQuery.EndDate - + query_fields := []string{ + filterQuery.GroupID, + filterQuery.CreatedBy, + filterQuery.TaskStatus, + filterQuery.TaskType, + filterQuery.StartDate, + filterQuery.EndDate} + field_names := []string{"group_id", "created_by", "task_status", "task_type", "start_date", "end_date"} var query string var args []interface{} - if groupID != "" { - query += "group_id = $1" - args = append(args, groupID) - } - - if createdBy != "" { - if query != "" { - query += " AND " - } - query += fmt.Sprintf("created_by = $%d", len(args)+1) - args = append(args, createdBy) - } - - if taskStatus != "" { - if query != "" { - query += " AND " - } - query += fmt.Sprintf("task_status = $%d", len(args)+1) - args = append(args, taskStatus) - } - - if taskType != "" { - if query != "" { - query += " AND " - } - query += fmt.Sprintf("task_type = $%d", len(args)+1) - args = append(args, taskType) - } - - if startDate != "" { - if query != "" { - query += " AND " - } - query += fmt.Sprintf("start_date = $%d", len(args)+1) - args = append(args, startDate) - } - - if endDate != "" { - if query != "" { - query += " AND " + for i, field := range query_fields { + if field != "" { + if query != "" { + query += " AND " + } + query += fmt.Sprintf("%s = $%d", field_names[i], len(args)+1) + args = append(args, field) } - query += fmt.Sprintf("end_date = $%d", len(args)+1) - args = append(args, endDate) } rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE "+query+";", args...) From de26aaac1719095b4cf69e550a40a83764bf0b31 Mon Sep 17 00:00:00 2001 From: haleymartin-6 <119809061+haleymartin-6@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:26:41 -0500 Subject: [PATCH 11/29] crud routes for create new task, delete task, and update task info --- backend/schema/tasks/task_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 6c8f247..45aafce 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -186,4 +186,5 @@ func TestTaskGroup(t *testing.T) { t.Error("Result was not correct") } }) + } From 6bc18baee387e842b4ef029f4382ad919122c4de Mon Sep 17 00:00:00 2001 From: CaitlinFlynn <122330940+CaitlinFlynn@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:37:55 -0500 Subject: [PATCH 12/29] Feat: generic card and popup modal (#20) * Intial card/button * Basic card and modal Basic card and modal skeleton * fixed card and popup to have the requirments fixed card and popup to have the requirments * wip for passing medication data into Card component * trying to get stuff workin * trying to get comps workin * stuff * Final Working Card + Popup --------- Co-authored-by: AdharshKan42 --- client/components/Card.tsx | 65 ++++++++++++++++++++++++++++++++ client/components/PopupModal.tsx | 56 +++++++++++++++++++++++++++ client/package.json | 1 + 3 files changed, 122 insertions(+) create mode 100644 client/components/Card.tsx create mode 100644 client/components/PopupModal.tsx diff --git a/client/components/Card.tsx b/client/components/Card.tsx new file mode 100644 index 0000000..167ba6c --- /dev/null +++ b/client/components/Card.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { StyleSheet, View, GestureResponderEvent, Text } from 'react-native'; +import { Card } from 'react-native-paper'; +import { useNavigation } from '@react-navigation/native'; + +interface ClickableCardProps { + med: Medication[]; + onPress: () => void; + children: JSX.Element[] | JSX.Element; + cardStyle?: object; + navigateTo?: string; +} + +const ClickableCard: React.FC = ({ + med, + onPress, + children, + cardStyle, + navigateTo +}) => { + // const navigation = useNavigation(); + + const handlePress = () => { + if (navigateTo) { + console.log('trying to navigate!'); + // navigation.navigate(navigateTo as never); + } else { + onPress(); + } + }; + + const styles = StyleSheet.create({ + card: { + margin: 10, + width: 200, + borderRadius: 8, + borderWidth: 1, + borderColor: 'gray', + backgroundColor: 'lightblue', + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 3, + elevation: 5 + }, + title: { + fontSize: 18, + marginBottom: 8, + fontWeight: 'bold' + }, + content: { + fontSize: 16, + color: 'gray' + } + }); + + return ( + + + {children} + + ); +}; + +export default ClickableCard; diff --git a/client/components/PopupModal.tsx b/client/components/PopupModal.tsx new file mode 100644 index 0000000..aab5978 --- /dev/null +++ b/client/components/PopupModal.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { + Modal, + Portal, + Text, + Button, + Provider as PaperProvider +} from 'react-native-paper'; + +interface PopupModalProps { + med: Medication[]; + onPress: () => void; + buttonStyle?: object; + modalStyle?: object; + modalContent?: React.ReactNode; +} + +const PopupModal: React.FC = ({ + med, + onPress, + buttonStyle, + modalStyle, + modalContent +}) => { + const [visible, setVisible] = React.useState(false); + + const showModal = () => setVisible(true); + const hideModal = () => setVisible(false); + const containerStyle = { + backgroundColor: 'white', + padding: 20, + ...modalStyle + }; + + return ( + + + + {modalContent || Default Modal Content} + + + + + ); +}; + +export default PopupModal; diff --git a/client/package.json b/client/package.json index c1dd572..33dd1c9 100644 --- a/client/package.json +++ b/client/package.json @@ -31,6 +31,7 @@ "nativewind": "^2.0.11", "react": "18.2.0", "react-native": "0.72.6", + "react-native-paper": "^5.12.3", "react-native-safe-area-context": "4.6.3", "react-native-svg-transformer": "^1.3.0", "react-native-screens": "~3.22.0" From 97ffda1f25a118b4df86334971e87cef926d9eb1 Mon Sep 17 00:00:00 2001 From: Matt McCoy <59743922+MattCMcCoy@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:47:59 -0500 Subject: [PATCH 13/29] fix: go formatting stuff werent running through entire directory (#23) --- .pre-commit-config.yaml | 5 +++-- Taskfile.yaml | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2323431..de4c6af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,15 +6,16 @@ repos: - id: check-added-large-files - id: check-merge-conflict - id: end-of-file-fixer - exclude: "backend/docs/" + exclude: 'backend/docs/' - id: trailing-whitespace - id: no-commit-to-branch - args: ["--branch", "main"] + args: ['--branch', 'main'] - repo: local hooks: - id: format language: python name: Run Format + require_serial: true entry: bash -c 'task format' - repo: https://github.com/commitizen-tools/commitizen rev: v3.13.0 diff --git a/Taskfile.yaml b/Taskfile.yaml index 9464db8..e56ba02 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -10,11 +10,13 @@ tasks: format: - | + echo -e "Frontend Formatting..." cd client npm run format + echo -e "\nBackend Formatting...\n" cd ../backend - go fmt - go vet + go fmt ./... + go vet ./... swag f start-backend: @@ -47,5 +49,6 @@ tasks: pre-commit: - | + pre-commit clean pre-commit install --hook-type commit-msg --hook-type pre-push --hook-type pre-commit pre-commit run --all-files From 571f0a8bfc8267658689d300dd9191e1bb52d536 Mon Sep 17 00:00:00 2001 From: Matt McCoy <59743922+MattCMcCoy@users.noreply.github.com> Date: Mon, 5 Feb 2024 16:54:11 -0500 Subject: [PATCH 14/29] feat: add a user context provider to the app (#24) * feat: userContext stuff idk man * refactor: general all around fixes (removing unused imports, fixing navigation errors) * refactor: all over the place with this commit, but added swagger docs for file upload * refactor: make context more general * refactor: utilize card and popup in the medlist FE --- backend/docs/docs.go | 43 ++++++++++- backend/docs/swagger.json | 43 ++++++++++- backend/docs/swagger.yaml | 27 +++++++ backend/schema/files/routes.go | 7 +- client/App.tsx | 62 +++------------- client/babel.config.js | 2 +- client/components/Card.tsx | 63 ++++------------ client/components/PopupModal.tsx | 72 +++++++------------ client/contexts/CareWalletContext.tsx | 53 ++++++++++++++ client/contexts/types.ts | 9 +++ client/navigation/AppNavigation.tsx | 28 ++++++++ .../navigation/AppStackBottomTabNavigator.tsx | 23 ++++++ client/navigation/Router.tsx | 11 +++ client/screens/Login.tsx | 16 ++--- client/screens/Medication.tsx | 43 +++++++++-- client/tailwind.config.js | 7 +- 16 files changed, 339 insertions(+), 170 deletions(-) create mode 100644 client/contexts/CareWalletContext.tsx create mode 100644 client/contexts/types.ts create mode 100644 client/navigation/AppNavigation.tsx create mode 100644 client/navigation/AppStackBottomTabNavigator.tsx create mode 100644 client/navigation/Router.tsx diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 8186cd9..72485a2 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -22,9 +22,24 @@ const docTemplate = `{ "file" ], "summary": "Upload a file", + "parameters": [ + { + "type": "file", + "description": "Body with file zip", + "name": "file_data", + "in": "formData", + "required": true + } + ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/models.File" + } + }, + "400": { + "description": "Bad Request" } } } @@ -91,6 +106,32 @@ const docTemplate = `{ } }, "definitions": { + "models.File": { + "type": "object", + "properties": { + "file_id": { + "type": "integer" + }, + "file_name": { + "type": "string" + }, + "file_size": { + "type": "integer" + }, + "group_id": { + "type": "integer" + }, + "task_id": { + "type": "integer" + }, + "upload_by": { + "type": "integer" + }, + "upload_date": { + "type": "string" + } + } + }, "models.Medication": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index abb0696..425d952 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -15,9 +15,24 @@ "file" ], "summary": "Upload a file", + "parameters": [ + { + "type": "file", + "description": "Body with file zip", + "name": "file_data", + "in": "formData", + "required": true + } + ], "responses": { "200": { - "description": "OK" + "description": "OK", + "schema": { + "$ref": "#/definitions/models.File" + } + }, + "400": { + "description": "Bad Request" } } } @@ -84,6 +99,32 @@ } }, "definitions": { + "models.File": { + "type": "object", + "properties": { + "file_id": { + "type": "integer" + }, + "file_name": { + "type": "string" + }, + "file_size": { + "type": "integer" + }, + "group_id": { + "type": "integer" + }, + "task_id": { + "type": "integer" + }, + "upload_by": { + "type": "integer" + }, + "upload_date": { + "type": "string" + } + } + }, "models.Medication": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 2c37bdc..8b1d7e2 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1,5 +1,22 @@ basePath: / definitions: + models.File: + properties: + file_id: + type: integer + file_name: + type: string + file_size: + type: integer + group_id: + type: integer + task_id: + type: integer + upload_by: + type: integer + upload_date: + type: string + type: object models.Medication: properties: medication_id: @@ -23,9 +40,19 @@ paths: /files/upload: post: description: Upload a file to database and S3 bucket + parameters: + - description: Body with file zip + in: formData + name: file_data + required: true + type: file responses: '200': description: OK + schema: + $ref: '#/definitions/models.File' + "400": + description: Bad Request summary: Upload a file tags: - file diff --git a/backend/schema/files/routes.go b/backend/schema/files/routes.go index bfae7aa..f32a874 100644 --- a/backend/schema/files/routes.go +++ b/backend/schema/files/routes.go @@ -28,10 +28,13 @@ func GetFileGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { // @summary Upload a file // @description Upload a file to database and S3 bucket // @tags file -// @success 200 +// @param file_data formData file true "Body with file zip" +// +// @success 200 {object} models.File +// @failure 400 // @router /files/upload [post] func (pg *PgModel) UploadFileRoute(c *gin.Context) { - // TODO: Ensure Swagger Knows about there bad request returns!!! + // TODO: Ensure Swagger Knows about the bad request returns!!! var file models.File if err := c.Bind(&file); err != nil { diff --git a/client/App.tsx b/client/App.tsx index 947b824..23205c7 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -1,57 +1,17 @@ import * as React from 'react'; -import { Text } from 'react-native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import { NavigationContainer, NavigationProp } from '@react-navigation/native'; -import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; -import LoginPage from './screens/Login'; -import MedList from './screens/Medication'; -import Home from './assets/home.svg'; -import DocPickerButton from './components/DocPickerButton'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import Router from './navigation/Router'; +import CareWalletProvider from './contexts/CareWalletContext'; +import { PaperProvider } from 'react-native-paper'; -export type ScreenNames = ['BottomNav', 'Landing', 'TEMP-FileUpload', 'Login']; -export type RootStackParamList = Record; -export type StackNavigation = NavigationProp; - -const Stack = createNativeStackNavigator(); -const Tab = createBottomTabNavigator(); - -// TODO: figure out a way to do this better, I didnt enjoy this way of doing it in SaluTemp there HAS to be a better way export default function App() { return ( - - - - - - - - ); -} - -function Tabs() { - return ( - - , - tabBarLabel: () => Landing - }} - component={MedList} - /> - + + + + + + + ); } diff --git a/client/babel.config.js b/client/babel.config.js index e3a823c..6fa972e 100644 --- a/client/babel.config.js +++ b/client/babel.config.js @@ -2,6 +2,6 @@ module.exports = function (api) { api.cache(true); return { presets: ['babel-preset-expo'], - plugins: ['nativewind/babel'] + plugins: ['react-native-paper/babel', 'nativewind/babel'] }; }; diff --git a/client/components/Card.tsx b/client/components/Card.tsx index 167ba6c..527f00d 100644 --- a/client/components/Card.tsx +++ b/client/components/Card.tsx @@ -1,62 +1,27 @@ +import { styled } from 'nativewind'; import React from 'react'; -import { StyleSheet, View, GestureResponderEvent, Text } from 'react-native'; import { Card } from 'react-native-paper'; -import { useNavigation } from '@react-navigation/native'; interface ClickableCardProps { - med: Medication[]; + title: string; onPress: () => void; - children: JSX.Element[] | JSX.Element; - cardStyle?: object; - navigateTo?: string; + children?: JSX.Element[] | JSX.Element; } -const ClickableCard: React.FC = ({ - med, +const StyledModal = styled(Card.Title, { + props: { + titleStyle: true + } +}); + +export const ClickableCard: React.FC = ({ + title, onPress, - children, - cardStyle, - navigateTo + children }) => { - // const navigation = useNavigation(); - - const handlePress = () => { - if (navigateTo) { - console.log('trying to navigate!'); - // navigation.navigate(navigateTo as never); - } else { - onPress(); - } - }; - - const styles = StyleSheet.create({ - card: { - margin: 10, - width: 200, - borderRadius: 8, - borderWidth: 1, - borderColor: 'gray', - backgroundColor: 'lightblue', - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.3, - shadowRadius: 3, - elevation: 5 - }, - title: { - fontSize: 18, - marginBottom: 8, - fontWeight: 'bold' - }, - content: { - fontSize: 16, - color: 'gray' - } - }); - return ( - - + + {children} ); diff --git a/client/components/PopupModal.tsx b/client/components/PopupModal.tsx index aab5978..97f96df 100644 --- a/client/components/PopupModal.tsx +++ b/client/components/PopupModal.tsx @@ -1,56 +1,34 @@ +import { styled } from 'nativewind'; import React from 'react'; -import { - Modal, - Portal, - Text, - Button, - Provider as PaperProvider -} from 'react-native-paper'; +import { Modal, Portal } from 'react-native-paper'; interface PopupModalProps { - med: Medication[]; - onPress: () => void; - buttonStyle?: object; - modalStyle?: object; - modalContent?: React.ReactNode; + isVisible: boolean; + setVisible: (val: boolean) => void; + children?: JSX.Element[] | JSX.Element; } -const PopupModal: React.FC = ({ - med, - onPress, - buttonStyle, - modalStyle, - modalContent -}) => { - const [visible, setVisible] = React.useState(false); - - const showModal = () => setVisible(true); - const hideModal = () => setVisible(false); - const containerStyle = { - backgroundColor: 'white', - padding: 20, - ...modalStyle - }; +// rnp requires contentcontainerstyle to style the component, this will integrate native-wind into that +const StyledModal = styled(Modal, { + props: { + contentContainerStyle: true + } +}); +export default function PopupModal({ + children, + isVisible, + setVisible +}: PopupModalProps) { return ( - - - - {modalContent || Default Modal Content} - - - - + {children} + + ); -}; - -export default PopupModal; +} diff --git a/client/contexts/CareWalletContext.tsx b/client/contexts/CareWalletContext.tsx new file mode 100644 index 0000000..55347ca --- /dev/null +++ b/client/contexts/CareWalletContext.tsx @@ -0,0 +1,53 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { getAuth, onAuthStateChanged } from 'firebase/auth'; +import { Group, User } from './types'; + +type CareWalletContextData = { + user: User; + group: Group; +}; + +const CareWalletContext = createContext({} as CareWalletContextData); + +export default function CareWalletProvider({ children }: { children: any }) { + const [user, setUser] = useState({} as User); + const [group, setGroup] = useState({} as Group); + const auth = getAuth(); + + useEffect(() => { + onAuthStateChanged(auth, (user) => { + const signedInUser: User = { + userID: user?.uid ?? '', + userEmail: user?.email ?? '' + }; + setUser(signedInUser); + }); + setGroup({ + groupID: 'TEMP - REPLACE WITH ACTUAL', + role: 'TEMP - REPLACE WITH ACTUAL' + }); + }, []); + + const CareWalletContextStore: CareWalletContextData = { + user: user, + group: group + }; + + return ( + + {children} + + ); +} + +export const useCareWalletContext = (): CareWalletContextData => { + const context = useContext(CareWalletContext); + + if (!context) { + throw new Error( + 'useCareWalletContext must be used within a CareWalletContextProvider' + ); + } + + return context; +}; diff --git a/client/contexts/types.ts b/client/contexts/types.ts new file mode 100644 index 0000000..82969df --- /dev/null +++ b/client/contexts/types.ts @@ -0,0 +1,9 @@ +export interface User { + userID: string; + userEmail: string; +} + +export interface Group { + groupID: string; + role: string; // TODO: update to enum +} diff --git a/client/navigation/AppNavigation.tsx b/client/navigation/AppNavigation.tsx new file mode 100644 index 0000000..fe7531f --- /dev/null +++ b/client/navigation/AppNavigation.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { NavigationProp } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import LoginPage from '../screens/Login'; +import AppStackBottomTabNavigator from './AppStackBottomTabNavigator'; + +export type AppScreenNames = ['MainNavScreens', 'Landing', 'Login']; +type AppStackParamList = Record; + +export type AppStackNavigation = NavigationProp; +const AppStack = createNativeStackNavigator(); + +export default function AppNavigation() { + return ( + + + + + ); +} diff --git a/client/navigation/AppStackBottomTabNavigator.tsx b/client/navigation/AppStackBottomTabNavigator.tsx new file mode 100644 index 0000000..3e7dd99 --- /dev/null +++ b/client/navigation/AppStackBottomTabNavigator.tsx @@ -0,0 +1,23 @@ +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import React from 'react'; +import MedList from '../screens/Medication'; +import { Text } from 'react-native'; +import Home from '../assets/home.svg'; + +const AppStackBottomTab = createBottomTabNavigator(); + +export default function AppStackBottomTabNavigator() { + return ( + + , + tabBarLabel: () => Landing + }} + component={MedList} + /> + + ); +} diff --git a/client/navigation/Router.tsx b/client/navigation/Router.tsx new file mode 100644 index 0000000..a1c1257 --- /dev/null +++ b/client/navigation/Router.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { NavigationContainer } from '@react-navigation/native'; +import AppNavigation from './AppNavigation'; + +export default function Router() { + return ( + + + + ); +} diff --git a/client/screens/Login.tsx b/client/screens/Login.tsx index 622f17f..71d8cf5 100644 --- a/client/screens/Login.tsx +++ b/client/screens/Login.tsx @@ -1,19 +1,15 @@ import React, { useState } from 'react'; -import { View, TextInput, Button, Alert, StyleSheet } from 'react-native'; +import { View, TextInput, Button, Alert } from 'react-native'; import { logIn } from '../services/auth/login'; import { signUp } from '../services/auth/signup'; -import { - useNavigation, - StackActions, - useRoute -} from '@react-navigation/native'; +import { useNavigation } from '@react-navigation/native'; +import { AppStackNavigation } from '../navigation/AppNavigation'; const LoginPage: React.FC = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const navigation = useNavigation(); - const route = useRoute(); + const navigation = useNavigation(); const handleLogin = async () => { if (!email || !password) { @@ -26,7 +22,7 @@ const LoginPage: React.FC = () => { } else { Alert.alert('Login Success', 'Welcome back!'); // console.log('result: ', result); - navigation.navigate('BottomNav'); + navigation.navigate('MainNavScreens'); } }; @@ -41,7 +37,7 @@ const LoginPage: React.FC = () => { } else { Alert.alert('Signup Success', 'Welcome to the app!'); // console.log('result: ', result); - navigation.navigate('BottomNav'); + navigation.navigate('MainNavScreens'); } }; diff --git a/client/screens/Medication.tsx b/client/screens/Medication.tsx index d412928..0ddfbc0 100644 --- a/client/screens/Medication.tsx +++ b/client/screens/Medication.tsx @@ -1,22 +1,51 @@ import * as React from 'react'; -import { View, Text } from 'react-native'; +import { View, Text, ScrollView } from 'react-native'; import { getAllMedications } from '../services/medication'; import { Medication } from '../types/medication'; +import { useCareWalletContext } from '../contexts/CareWalletContext'; +import ClickableCard from '../components/Card'; +import PopupModal from '../components/PopupModal'; export default function MedList() { const [medications, setMedications] = React.useState(); + const [selectedMed, setSelectedMed] = React.useState(); + const { user, group } = useCareWalletContext(); + const [visible, setVisible] = React.useState(false); React.useEffect(() => { getAllMedications().then((med) => setMedications(med)); }, []); return ( - {medications && - medications.map((med, index) => ( - - {`Name: ${med.medication_name} id: ${med.medication_id}`} - - ))} + + + {selectedMed?.medication_name} + + ID: {selectedMed?.medication_id} + + + {medications && + medications.map((med, index) => ( + { + setSelectedMed(med); + setVisible(true); + }} + > + ID: {med.medication_id} + + ))} + + {user && group && ( + + The user id is: {user.userID} + The user email is: {user.userEmail} + The group id is: {group.groupID} + The group role is: {group.role} + + )} ); } diff --git a/client/tailwind.config.js b/client/tailwind.config.js index 0e7790f..2a6c6f1 100644 --- a/client/tailwind.config.js +++ b/client/tailwind.config.js @@ -1,6 +1,11 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ['./App.{js,jsx,ts,tsx}', './screens/**/*.{js,jsx,ts,tsx}'], + content: [ + './App.{js,jsx,ts,tsx}', + './screens/**/*.{js,jsx,ts,tsx}', + './components/**/*.{js,jsx,ts,tsx}', + './navigation/**/*.{js,jsx,ts,tsx}' + ], theme: { extend: {} }, From a0e1011c7a133e9528cc3554415fda63bd199367 Mon Sep 17 00:00:00 2001 From: Matt McCoy <59743922+MattCMcCoy@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:04:33 -0500 Subject: [PATCH 15/29] backend hot reload * feat: added hot reloading for backend code --- .pre-commit-config.yaml | 7 +++++++ Taskfile.yaml | 5 +++++ backend/configuration/config.go | 19 ++++++++++++------- backend/main.go | 6 +++++- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index de4c6af..cf5575b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,6 +10,13 @@ repos: - id: trailing-whitespace - id: no-commit-to-branch args: ['--branch', 'main'] + - repo: local + hooks: + - id: build + language: python + name: Parse Swag Docs + require_serial: true + entry: bash -c 'cd backend && swag i --parseDependency' - repo: local hooks: - id: format diff --git a/Taskfile.yaml b/Taskfile.yaml index e56ba02..4ba4c60 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -19,6 +19,11 @@ tasks: go vet ./... swag f + start-dev: + - | + echo -e "Starting Backend Live-Reload..." + nodemon -e go --ignore 'backend/docs/**' --signal SIGTERM --exec "task start-backend" + start-backend: - | cd backend diff --git a/backend/configuration/config.go b/backend/configuration/config.go index 87db10e..9b40906 100644 --- a/backend/configuration/config.go +++ b/backend/configuration/config.go @@ -41,13 +41,7 @@ func GetConfiguration() (Settings, error) { var settings Settings - var environment Environment - if env := os.Getenv("GITHUB_ACTIONS"); env != "" { - environment = EnvironmentGitHub - } else { - environment = EnvironmentLocal - } - + environment := GetEnviroment() v.SetConfigName(string(environment)) if err := v.ReadInConfig(); err != nil { @@ -60,3 +54,14 @@ func GetConfiguration() (Settings, error) { return settings, nil } + +func GetEnviroment() Environment { + var environment Environment + if env := os.Getenv("GITHUB_ACTIONS"); env != "" { + environment = EnvironmentGitHub + } else { + environment = EnvironmentLocal + } + + return environment +} diff --git a/backend/main.go b/backend/main.go index 6c88214..ca43b3e 100644 --- a/backend/main.go +++ b/backend/main.go @@ -21,6 +21,7 @@ import ( // @description This is an API for the Care-Wallet App. // @BasePath / func main() { + enviroment := configuration.GetEnviroment() config, err := configuration.GetConfiguration() if err != nil { @@ -40,7 +41,10 @@ func main() { files.GetFileGroup(v1, &files.PgModel{Conn: conn}) } - r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + if enviroment == configuration.EnvironmentLocal { + r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + } + err = r.Run(":8080") if err != nil { From 176c3018f7d8c590fd84be4ea05014e3ab9bd105 Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Fri, 2 Feb 2024 14:52:46 -0500 Subject: [PATCH 16/29] refactor: abstract queries, enable multiple --- backend/schema/tasks/task_test.go | 40 ++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 45aafce..9ba998a 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -39,16 +39,40 @@ func TestTaskGroup(t *testing.T) { TaskGroup(v1, &controller) } - t.Run("TestGetFilteredTasks", func(t *testing.T) { - getRequest := TaskQuery{ - GroupID: "", - CreatedBy: "", - TaskStatus: "", - TaskType: "other", - StartDate: "", - EndDate: "", + t.Run("TestGetTasksByGroupId", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/tasks/group/4", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to retrieve tasks by group id.") } + var responseTasks []models.Task + err := json.Unmarshal(w.Body.Bytes(), &responseTasks) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + createdDate, _ := time.Parse("2006-01-02 15:04:05", "2006-01-02 15:04:05") + expectedTasks := []models.Task{ + { + TaskID: 4, + GroupID: 4, + CreatedBy: "user1", + CreatedDate: createdDate, + TaskStatus: "COMPLETE", + TaskType: "other", + }, + } + + if !reflect.DeepEqual(expectedTasks, responseTasks) { + t.Error("Result was not correct") + } + }) + + t.Run("TestGetTasksByCreatedBy", func(t *testing.T) { w := httptest.NewRecorder() query := url.Values{} query.Set("GroupID", getRequest.GroupID) From 5009975e3f2a5818a1ea62f23a0e42ad75d5231f Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Mon, 5 Feb 2024 19:57:35 -0500 Subject: [PATCH 17/29] refactor: routes are updated --- backend/docs/docs.go | 176 ++++++++++++++++++++++++++++++ backend/docs/swagger.json | 176 ++++++++++++++++++++++++++++++ backend/docs/swagger.yaml | 134 +++++++++++++++++++++-- backend/schema/tasks/routes.go | 54 ++++++--- backend/schema/tasks/task_test.go | 47 ++------ 5 files changed, 520 insertions(+), 67 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 72485a2..cfa17f4 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -64,6 +64,64 @@ const docTemplate = `{ } } }, + "/tasks/filtered": { + "get": { + "description": "get filtered tasks", + "tags": [ + "tasks" + ], + "summary": "Get Filtered Tasks", + "parameters": [ + { + "type": "string", + "name": "createdBy", + "in": "query" + }, + { + "type": "string", + "name": "endDate", + "in": "query" + }, + { + "type": "string", + "name": "groupID", + "in": "query" + }, + { + "type": "string", + "name": "startDate", + "in": "query" + }, + { + "type": "string", + "name": "taskStatus", + "in": "query" + }, + { + "type": "string", + "name": "taskType", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/tasks/{tid}/assignees": { "post": { "description": "assign users to task", @@ -71,6 +129,24 @@ const docTemplate = `{ "tasks" ], "summary": "Assign Users To Task", + "parameters": [ + { + "type": "string", + "description": "Task ID to assign users to", + "name": "tid", + "in": "path", + "required": true + }, + { + "description": "Users to assign to task and assignee", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tasks.Assignment" + } + } + ], "responses": { "200": { "description": "OK", @@ -80,6 +156,12 @@ const docTemplate = `{ "$ref": "#/definitions/models.TaskUser" } } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } } } } @@ -91,6 +173,24 @@ const docTemplate = `{ "tasks" ], "summary": "Remove Users From Task", + "parameters": [ + { + "type": "string", + "description": "Task ID to remove users from", + "name": "tid", + "in": "path", + "required": true + }, + { + "description": "Users to remove from task", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tasks.Removal" + } + } + ], "responses": { "200": { "description": "OK", @@ -100,6 +200,12 @@ const docTemplate = `{ "$ref": "#/definitions/models.TaskUser" } } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } } } } @@ -143,6 +249,51 @@ const docTemplate = `{ } } }, + "models.Task": { + "type": "object", + "properties": { + "created_by": { + "description": "User ID", + "type": "string" + }, + "created_date": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "group_id": { + "type": "integer" + }, + "notes": { + "type": "string" + }, + "repeating": { + "type": "boolean" + }, + "repeating_end_date": { + "type": "string" + }, + "repeating_interval": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "task_id": { + "type": "integer" + }, + "task_info": { + "type": "string" + }, + "task_status": { + "type": "string" + }, + "task_type": { + "type": "string" + } + } + }, "models.TaskUser": { "type": "object", "properties": { @@ -153,6 +304,31 @@ const docTemplate = `{ "type": "string" } } + }, + "tasks.Assignment": { + "type": "object", + "properties": { + "assigner": { + "type": "string" + }, + "userIDs": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tasks.Removal": { + "type": "object", + "properties": { + "userIDs": { + "type": "array", + "items": { + "type": "string" + } + } + } } } }` diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 425d952..70fe58b 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -57,6 +57,64 @@ } } }, + "/tasks/filtered": { + "get": { + "description": "get filtered tasks", + "tags": [ + "tasks" + ], + "summary": "Get Filtered Tasks", + "parameters": [ + { + "type": "string", + "name": "createdBy", + "in": "query" + }, + { + "type": "string", + "name": "endDate", + "in": "query" + }, + { + "type": "string", + "name": "groupID", + "in": "query" + }, + { + "type": "string", + "name": "startDate", + "in": "query" + }, + { + "type": "string", + "name": "taskStatus", + "in": "query" + }, + { + "type": "string", + "name": "taskType", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/tasks/{tid}/assignees": { "post": { "description": "assign users to task", @@ -64,6 +122,24 @@ "tasks" ], "summary": "Assign Users To Task", + "parameters": [ + { + "type": "string", + "description": "Task ID to assign users to", + "name": "tid", + "in": "path", + "required": true + }, + { + "description": "Users to assign to task and assignee", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tasks.Assignment" + } + } + ], "responses": { "200": { "description": "OK", @@ -73,6 +149,12 @@ "$ref": "#/definitions/models.TaskUser" } } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } } } } @@ -84,6 +166,24 @@ "tasks" ], "summary": "Remove Users From Task", + "parameters": [ + { + "type": "string", + "description": "Task ID to remove users from", + "name": "tid", + "in": "path", + "required": true + }, + { + "description": "Users to remove from task", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/tasks.Removal" + } + } + ], "responses": { "200": { "description": "OK", @@ -93,6 +193,12 @@ "$ref": "#/definitions/models.TaskUser" } } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } } } } @@ -136,6 +242,51 @@ } } }, + "models.Task": { + "type": "object", + "properties": { + "created_by": { + "description": "User ID", + "type": "string" + }, + "created_date": { + "type": "string" + }, + "end_date": { + "type": "string" + }, + "group_id": { + "type": "integer" + }, + "notes": { + "type": "string" + }, + "repeating": { + "type": "boolean" + }, + "repeating_end_date": { + "type": "string" + }, + "repeating_interval": { + "type": "string" + }, + "start_date": { + "type": "string" + }, + "task_id": { + "type": "integer" + }, + "task_info": { + "type": "string" + }, + "task_status": { + "type": "string" + }, + "task_type": { + "type": "string" + } + } + }, "models.TaskUser": { "type": "object", "properties": { @@ -146,6 +297,31 @@ "type": "string" } } + }, + "tasks.Assignment": { + "type": "object", + "properties": { + "assigner": { + "type": "string" + }, + "userIDs": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "tasks.Removal": { + "type": "object", + "properties": { + "userIDs": { + "type": "array", + "items": { + "type": "string" + } + } + } } } } \ No newline at end of file diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 8b1d7e2..7cc0f19 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -24,6 +24,36 @@ definitions: medication_name: type: string type: object + models.Task: + properties: + created_by: + description: User ID + type: string + created_date: + type: string + end_date: + type: string + group_id: + type: integer + notes: + type: string + repeating: + type: boolean + repeating_end_date: + type: string + repeating_interval: + type: string + start_date: + type: string + task_id: + type: integer + task_info: + type: string + task_status: + type: string + task_type: + type: string + type: object models.TaskUser: properties: taskID: @@ -31,11 +61,27 @@ definitions: userID: type: string type: object + tasks.Assignment: + properties: + assigner: + type: string + userIDs: + items: + type: string + type: array + type: object + tasks.Removal: + properties: + userIDs: + items: + type: string + type: array + type: object info: contact: {} description: This is an API for the Care-Wallet App. title: Care-Wallet API - version: '1.0' + version: "1.0" paths: /files/upload: post: @@ -47,7 +93,7 @@ paths: required: true type: file responses: - '200': + "200": description: OK schema: $ref: '#/definitions/models.File' @@ -55,12 +101,12 @@ paths: description: Bad Request summary: Upload a file tags: - - file + - file /medications: get: description: get all user medications responses: - '200': + "200": description: OK schema: items: @@ -68,31 +114,99 @@ paths: type: array summary: Get All Meds tags: - - medications + - medications /tasks/{tid}/assignees: post: description: assign users to task + parameters: + - description: Task ID to assign users to + in: path + name: tid + required: true + type: string + - description: Users to assign to task and assignee + in: body + name: _ + required: true + schema: + $ref: '#/definitions/tasks.Assignment' responses: - '200': + "200": description: OK schema: items: $ref: '#/definitions/models.TaskUser' type: array + "400": + description: Bad Request + schema: + type: string summary: Assign Users To Task tags: - - tasks + - tasks /tasks/{tid}/remove: delete: description: remove users from task + parameters: + - description: Task ID to remove users from + in: path + name: tid + required: true + type: string + - description: Users to remove from task + in: body + name: _ + required: true + schema: + $ref: '#/definitions/tasks.Removal' responses: - '200': + "200": description: OK schema: items: $ref: '#/definitions/models.TaskUser' type: array + "400": + description: Bad Request + schema: + type: string summary: Remove Users From Task tags: - - tasks -swagger: '2.0' + - tasks + /tasks/filtered: + get: + description: get filtered tasks + parameters: + - in: query + name: createdBy + type: string + - in: query + name: endDate + type: string + - in: query + name: groupID + type: string + - in: query + name: startDate + type: string + - in: query + name: taskStatus + type: string + - in: query + name: taskType + type: string + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Task' + type: array + "400": + description: Bad Request + schema: + type: string + summary: Get Filtered Tasks + tags: + - tasks +swagger: "2.0" diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index 4f57eea..3368594 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -1,7 +1,6 @@ package tasks import ( - "fmt" "net/http" "github.com/gin-gonic/gin" @@ -38,7 +37,11 @@ type TaskQuery struct { // @summary Get Filtered Tasks // @description get filtered tasks // @tags tasks -// @success 200 {array} models.Task +// +// @param _ query TaskQuery true "Filters for task query" +// +// @success 200 {array} models.Task +// @failure 400 {object} string // @router /tasks/filtered [get] func (pg *PgModel) GetFilteredTasks(c *gin.Context) { filterQuery := TaskQuery{ @@ -53,62 +56,77 @@ func (pg *PgModel) GetFilteredTasks(c *gin.Context) { tasks, err := GetTasksByQueryFromDB(pg.Conn, filterQuery) if err != nil { - panic(err) + c.JSON(http.StatusBadRequest, err.Error()) + return } c.JSON(http.StatusOK, tasks) } +type Assignment struct { + UserIDs []string `json:"userIDs"` + Assigner string `json:"assigner"` +} + // AssignUsersToTask godoc // // @summary Assign Users To Task // @description assign users to task // @tags tasks -// @success 200 {array} models.TaskUser +// +// @param tid path string true "Task ID to assign users to" +// @param _ body Assignment true "Users to assign to task and assignee" +// +// @success 200 {array} models.TaskUser +// @failure 400 {object} string // @router /tasks/{tid}/assignees [post] func (pg *PgModel) AssignUsersToTask(c *gin.Context) { - var requestBody struct { - UserIDs []string `json:"userIDs"` - Assigner string `json:"assigner"` - } + var requestBody Assignment if err := c.BindJSON(&requestBody); err != nil { - fmt.Println("error binding to request body: ", err) - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, err.Error()) return } assignedUsers, err := AssignUsersToTaskInDB(pg.Conn, requestBody.UserIDs, c.Param("tid"), requestBody.Assigner) if err != nil { - panic(err) + c.JSON(http.StatusBadRequest, err.Error()) + return } c.JSON(http.StatusOK, assignedUsers) } +type Removal struct { + UserIDs []string `json:"userIDs"` +} + // RemoveUsersFromTask godoc // // @summary Remove Users From Task // @description remove users from task // @tags tasks -// @success 200 {array} models.TaskUser +// +// @param tid path string true "Task ID to remove users from" +// @param _ body Removal true "Users to remove from task" +// +// @success 200 {array} models.TaskUser +// @failure 400 {object} string // @router /tasks/{tid}/remove [delete] func (pg *PgModel) RemoveUsersFromTask(c *gin.Context) { - var requestBody struct { - UserIDs []string `json:"userIDs"` - } + var requestBody Removal if err := c.BindJSON(&requestBody); err != nil { - fmt.Println("error binding to request body: ", err) - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, err.Error()) return } removedUsers, err := RemoveUsersFromTaskInDB(pg.Conn, requestBody.UserIDs, c.Param("tid")) if err != nil { - panic(err) + c.JSON(http.StatusBadRequest, err.Error()) + return } c.JSON(http.StatusOK, removedUsers) diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 9ba998a..b8c3f30 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -39,40 +39,16 @@ func TestTaskGroup(t *testing.T) { TaskGroup(v1, &controller) } - t.Run("TestGetTasksByGroupId", func(t *testing.T) { - w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/tasks/group/4", nil) - router.ServeHTTP(w, req) - - if http.StatusOK != w.Code { - t.Error("Failed to retrieve tasks by group id.") - } - - var responseTasks []models.Task - err := json.Unmarshal(w.Body.Bytes(), &responseTasks) - - if err != nil { - t.Error("Failed to unmarshal json") - } - - createdDate, _ := time.Parse("2006-01-02 15:04:05", "2006-01-02 15:04:05") - expectedTasks := []models.Task{ - { - TaskID: 4, - GroupID: 4, - CreatedBy: "user1", - CreatedDate: createdDate, - TaskStatus: "COMPLETE", - TaskType: "other", - }, + t.Run("TestGetFilteredTasks", func(t *testing.T) { + getRequest := TaskQuery{ + GroupID: "", + CreatedBy: "", + TaskStatus: "", + TaskType: "other", + StartDate: "", + EndDate: "", } - if !reflect.DeepEqual(expectedTasks, responseTasks) { - t.Error("Result was not correct") - } - }) - - t.Run("TestGetTasksByCreatedBy", func(t *testing.T) { w := httptest.NewRecorder() query := url.Values{} query.Set("GroupID", getRequest.GroupID) @@ -115,8 +91,6 @@ func TestTaskGroup(t *testing.T) { }, } - fmt.Println(expectedTasks) - fmt.Println(responseTasks) if !reflect.DeepEqual(expectedTasks, responseTasks) { t.Error("Result was not correct") } @@ -138,7 +112,6 @@ func TestTaskGroup(t *testing.T) { t.Error("Failed to marshal userIds to JSON") } - // Create a request with the userIds JSON w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/tasks/4/assign", bytes.NewBuffer(userIdsJSON)) router.ServeHTTP(w, req) @@ -161,9 +134,6 @@ func TestTaskGroup(t *testing.T) { }, } - fmt.Println("responseTaskUsers: ", responseTaskUsers) - fmt.Println("expectedTaskUsers: ", expectedTaskUsers) - if !reflect.DeepEqual(expectedTaskUsers, responseTaskUsers) { t.Error("Result was not correct") } @@ -183,7 +153,6 @@ func TestTaskGroup(t *testing.T) { t.Error("Failed to marshal userIds to JSON") } - // Create a request with the userIds JSON w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/tasks/4/remove", bytes.NewBuffer(userIdsJSON)) router.ServeHTTP(w, req) From c2a8fc454cc1369d8b48e877e0131328fbd34946 Mon Sep 17 00:00:00 2001 From: Olivia Sedarski Date: Tue, 6 Feb 2024 11:44:22 -0500 Subject: [PATCH 18/29] feat: new route backend --- backend/db/migrations/init.sql | 2 +- backend/docs/docs.go | 39 +++++++++- backend/docs/swagger.json | 39 +++++++++- backend/docs/swagger.yaml | 26 ++++++- backend/main.go | 2 + backend/schema/tasks/routes.go | 50 +++++++++++-- backend/schema/tasks/task_test.go | 106 +++++++++++++++++---------- backend/schema/tasks/transactions.go | 55 +++++++++++--- 8 files changed, 258 insertions(+), 61 deletions(-) diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql index 132f1b8..dd0d026 100644 --- a/backend/db/migrations/init.sql +++ b/backend/db/migrations/init.sql @@ -57,7 +57,7 @@ CREATE TABLE IF NOT EXISTS task ( start_date timestamp, end_date timestamp, notes varchar, - repeating BOOLEAN, + repeating BOOLEAN DEFAULT FALSE, repeating_interval varchar, repeating_end_date timestamp, task_status task_status NOT NULL, diff --git a/backend/docs/docs.go b/backend/docs/docs.go index cfa17f4..5878a3a 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -64,6 +64,43 @@ const docTemplate = `{ } } }, + "/tasks/assigned": { + "get": { + "description": "get tasks assigned to given users", + "tags": [ + "tasks" + ], + "summary": "Get Tasks Assigned To Given Users", + "parameters": [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "userIDs", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/tasks/filtered": { "get": { "description": "get filtered tasks", @@ -122,7 +159,7 @@ const docTemplate = `{ } } }, - "/tasks/{tid}/assignees": { + "/tasks/{tid}/assign": { "post": { "description": "assign users to task", "tags": [ diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 70fe58b..29d37ff 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -57,6 +57,43 @@ } } }, + "/tasks/assigned": { + "get": { + "description": "get tasks assigned to given users", + "tags": [ + "tasks" + ], + "summary": "Get Tasks Assigned To Given Users", + "parameters": [ + { + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "name": "userIDs", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/tasks/filtered": { "get": { "description": "get filtered tasks", @@ -115,7 +152,7 @@ } } }, - "/tasks/{tid}/assignees": { + "/tasks/{tid}/assign": { "post": { "description": "assign users to task", "tags": [ diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 7cc0f19..66b8caf 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -115,7 +115,7 @@ paths: summary: Get All Meds tags: - medications - /tasks/{tid}/assignees: + /tasks/{tid}/assign: post: description: assign users to task parameters: @@ -173,6 +173,30 @@ paths: summary: Remove Users From Task tags: - tasks + /tasks/assigned: + get: + description: get tasks assigned to given users + parameters: + - collectionFormat: csv + in: query + items: + type: string + name: userIDs + type: array + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Task' + type: array + "400": + description: Bad Request + schema: + type: string + summary: Get Tasks Assigned To Given Users + tags: + - tasks /tasks/filtered: get: description: get filtered tasks diff --git a/backend/main.go b/backend/main.go index ca43b3e..661c2e3 100644 --- a/backend/main.go +++ b/backend/main.go @@ -6,6 +6,7 @@ import ( _ "carewallet/docs" "carewallet/schema/files" "carewallet/schema/medication" + "carewallet/schema/tasks" "fmt" "os" @@ -39,6 +40,7 @@ func main() { { medication.GetMedicationGroup(v1, &medication.PgModel{Conn: conn}) files.GetFileGroup(v1, &files.PgModel{Conn: conn}) + tasks.TaskGroup(v1, &tasks.PgModel{Conn: conn}) } if enviroment == configuration.EnvironmentLocal { diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index 3368594..80ec410 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -1,7 +1,9 @@ package tasks import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/jackc/pgx" @@ -18,6 +20,7 @@ func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { tasks.GET("/filtered", c.GetFilteredTasks) tasks.POST("/:tid/assign", c.AssignUsersToTask) tasks.DELETE("/:tid/remove", c.RemoveUsersFromTask) + tasks.GET("/assigned", c.GetTasksByAssignedUsers) } return tasks @@ -45,12 +48,12 @@ type TaskQuery struct { // @router /tasks/filtered [get] func (pg *PgModel) GetFilteredTasks(c *gin.Context) { filterQuery := TaskQuery{ - GroupID: c.Query("GroupID"), - CreatedBy: c.Query("CreatedBy"), - TaskStatus: c.Query("TaskStatus"), - TaskType: c.Query("TaskType"), - StartDate: c.Query("StartDate"), - EndDate: c.Query("EndDate"), + GroupID: c.Query("groupID"), + CreatedBy: c.Query("createdBy"), + TaskStatus: c.Query("taskStatus"), + TaskType: c.Query("taskType"), + StartDate: c.Query("startDate"), + EndDate: c.Query("endDate"), } tasks, err := GetTasksByQueryFromDB(pg.Conn, filterQuery) @@ -79,11 +82,12 @@ type Assignment struct { // // @success 200 {array} models.TaskUser // @failure 400 {object} string -// @router /tasks/{tid}/assignees [post] +// @router /tasks/{tid}/assign [post] func (pg *PgModel) AssignUsersToTask(c *gin.Context) { var requestBody Assignment if err := c.BindJSON(&requestBody); err != nil { + print(err.Error()) c.JSON(http.StatusBadRequest, err.Error()) return } @@ -131,3 +135,35 @@ func (pg *PgModel) RemoveUsersFromTask(c *gin.Context) { c.JSON(http.StatusOK, removedUsers) } + +type AssignedQuery struct { + UserIDs []string `query:"userIDs"` +} + +// GetTasksByAssignedUsers godoc +// +// @summary Get Tasks Assigned To Given Users +// @description get tasks assigned to given users +// @tags tasks +// +// @param _ query AssignedQuery true "Users to return tasks for" +// +// @success 200 {array} models.Task +// @failure 400 {object} string +// @router /tasks/assigned [get] +func (pg *PgModel) GetTasksByAssignedUsers(c *gin.Context) { + userIDs := c.Query("userIDs") + assignedQuery := AssignedQuery{ + UserIDs: strings.Split(userIDs, ","), + } + fmt.Println(assignedQuery.UserIDs) + + tasks, err := GetTasksByAssignedFromDB(pg.Conn, assignedQuery.UserIDs) + + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, tasks) +} diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index b8c3f30..2a9048c 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -51,12 +51,12 @@ func TestTaskGroup(t *testing.T) { w := httptest.NewRecorder() query := url.Values{} - query.Set("GroupID", getRequest.GroupID) - query.Set("CreatedBy", getRequest.CreatedBy) - query.Set("TaskStatus", getRequest.TaskStatus) - query.Set("TaskType", getRequest.TaskType) - query.Set("StartDate", getRequest.StartDate) - query.Set("EndDate", getRequest.EndDate) + query.Set("groupID", getRequest.GroupID) + query.Set("createdBy", getRequest.CreatedBy) + query.Set("taskStatus", getRequest.TaskStatus) + query.Set("taskType", getRequest.TaskType) + query.Set("startDate", getRequest.StartDate) + query.Set("endDate", getRequest.EndDate) req, _ := http.NewRequest("GET", "/tasks/filtered?"+query.Encode(), nil) router.ServeHTTP(w, req) @@ -96,32 +96,26 @@ func TestTaskGroup(t *testing.T) { } }) - t.Run("TestAssignUsersToTask", func(t *testing.T) { - type AssignRequest struct { - UserIDs []string `json:"userIDs"` - Assigner string `json:"assigner"` - } - - assignRequest := AssignRequest{ - UserIDs: []string{"user3"}, - Assigner: "user3", + t.Run("TestRemoveUsersFromTask", func(t *testing.T) { + var removeRequest = Removal{ + UserIDs: []string{"user1"}, } - userIdsJSON, err := json.Marshal(assignRequest) + requestJSON, err := json.Marshal(removeRequest) if err != nil { - t.Error("Failed to marshal userIds to JSON") + t.Error("Failed to marshal remove request to JSON") } w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/tasks/4/assign", bytes.NewBuffer(userIdsJSON)) + req, _ := http.NewRequest("DELETE", "/tasks/1/remove", bytes.NewBuffer(requestJSON)) router.ServeHTTP(w, req) if http.StatusOK != w.Code { - t.Error("Failed to assign users to task.") + t.Error("Failed to remove users from task.") } - var responseTaskUsers []models.TaskUser - err = json.Unmarshal(w.Body.Bytes(), &responseTaskUsers) + var removeResponse []models.TaskUser + err = json.Unmarshal(w.Body.Bytes(), &removeResponse) if err != nil { t.Error("Failed to unmarshal json") @@ -129,40 +123,37 @@ func TestTaskGroup(t *testing.T) { expectedTaskUsers := []models.TaskUser{ { - TaskID: 4, - UserID: "user3", + TaskID: 1, + UserID: "user1", }, } - if !reflect.DeepEqual(expectedTaskUsers, responseTaskUsers) { + if !reflect.DeepEqual(expectedTaskUsers, removeResponse) { t.Error("Result was not correct") } }) - t.Run("TestRemoveUsersFromTask", func(t *testing.T) { - type RemoveRequest struct { - UserIDs []string `json:"userIDs"` - } - - removeRequest := RemoveRequest{ - UserIDs: []string{"user2"}, + t.Run("TestAssignUsersToTask", func(t *testing.T) { + assignRequest := Assignment{ + UserIDs: []string{"user4"}, + Assigner: "user1", } - userIdsJSON, err := json.Marshal(removeRequest) + requestJSON, err := json.Marshal(assignRequest) if err != nil { - t.Error("Failed to marshal userIds to JSON") + t.Error("Failed to marshal assign request to JSON") } w := httptest.NewRecorder() - req, _ := http.NewRequest("DELETE", "/tasks/4/remove", bytes.NewBuffer(userIdsJSON)) + req, _ := http.NewRequest("POST", "/tasks/2/assign", bytes.NewBuffer(requestJSON)) router.ServeHTTP(w, req) if http.StatusOK != w.Code { - t.Error("Failed to remove users from task.") + t.Error("Failed to assign users to task.") } - var responseTaskUsers []models.TaskUser - err = json.Unmarshal(w.Body.Bytes(), &responseTaskUsers) + var assignResponse []models.TaskUser + err = json.Unmarshal(w.Body.Bytes(), &assignResponse) if err != nil { t.Error("Failed to unmarshal json") @@ -170,14 +161,49 @@ func TestTaskGroup(t *testing.T) { expectedTaskUsers := []models.TaskUser{ { - TaskID: 4, - UserID: "user2", + TaskID: 2, + UserID: "user4", }, } - if !reflect.DeepEqual(expectedTaskUsers, responseTaskUsers) { + if !reflect.DeepEqual(expectedTaskUsers, assignResponse) { t.Error("Result was not correct") } }) + t.Run("TestGetTasksByAssigned", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/tasks/assigned?userIDs=user2", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to retrieve tasks by assigned user.") + } + + var responseTasks []models.Task + err = json.Unmarshal(w.Body.Bytes(), &responseTasks) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + note := "Refill water pitcher" + expectedTasks := []models.Task{ + { + TaskID: 4, + GroupID: 4, + CreatedBy: "user1", + CreatedDate: time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC), + Notes: ¬e, + TaskStatus: "COMPLETE", + TaskType: "other", + }, + } + + fmt.Println("Expected: ", expectedTasks) + fmt.Println("Response: ", responseTasks) + if !reflect.DeepEqual(expectedTasks, responseTasks) { + t.Error("Result was not correct") + } + }) } diff --git a/backend/schema/tasks/transactions.go b/backend/schema/tasks/transactions.go index 5386f38..8be8e97 100644 --- a/backend/schema/tasks/transactions.go +++ b/backend/schema/tasks/transactions.go @@ -17,6 +17,7 @@ func GetTasksByQueryFromDB(pool *pgx.Conn, filterQuery TaskQuery) ([]models.Task filterQuery.TaskType, filterQuery.StartDate, filterQuery.EndDate} + field_names := []string{"group_id", "created_by", "task_status", "task_type", "start_date", "end_date"} var query string var args []interface{} @@ -31,11 +32,10 @@ func GetTasksByQueryFromDB(pool *pgx.Conn, filterQuery TaskQuery) ([]models.Task } } - rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE "+query+";", args...) + rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE "+query, args...) if err != nil { print(err, "error selecting tasks by query") - return nil, err } @@ -49,7 +49,6 @@ func GetTasksByQueryFromDB(pool *pgx.Conn, filterQuery TaskQuery) ([]models.Task if err != nil { print(err, "error scanning tasks by query") - return nil, err } @@ -62,24 +61,20 @@ func GetTasksByQueryFromDB(pool *pgx.Conn, filterQuery TaskQuery) ([]models.Task func AssignUsersToTaskInDB(pool *pgx.Conn, users []string, taskID string, assigner string) ([]models.TaskUser, error) { task_id, err := strconv.Atoi(taskID) if err != nil { - print(err, "error converting task ID to int") return nil, err } var assignedUsers []models.TaskUser for _, user := range users { - print(task_id, " ", user) _, err := pool.Exec("INSERT INTO task_assignees (task_id, user_id, assignment_status, assigned_by, assigned_date) VALUES ($1, $2, $3, $4, $5);", task_id, user, "NOTIFIED", assigner, time.Now()) if err != nil { - print(err, "error inserting users into task_assignees") - + print(err.Error(), "error inserting users into task_assignees") return nil, err } assignedUsers = append(assignedUsers, models.TaskUser{TaskID: task_id, UserID: user}) - fmt.Println(assignedUsers) } return assignedUsers, nil @@ -95,12 +90,10 @@ func RemoveUsersFromTaskInDB(pool *pgx.Conn, users []string, taskID string) ([]m var removedUsers []models.TaskUser for _, user := range users { - // Check if the user ID and task ID exist in the table var exists int err := pool.QueryRow("SELECT 1 FROM task_assignees WHERE task_id = $1 AND user_id = $2 LIMIT 1;", task_id, user).Scan(&exists) if err != nil { if err == pgx.ErrNoRows { - // User ID or task ID does not exist, return an error return nil, fmt.Errorf("user not assigned to task") } print(err, "error checking if user and task exist in task_assignees") @@ -118,3 +111,45 @@ func RemoveUsersFromTaskInDB(pool *pgx.Conn, users []string, taskID string) ([]m return removedUsers, nil } + +func GetTasksByAssignedFromDB(pool *pgx.Conn, userIDs []string) ([]models.Task, error) { + var task_ids []int + var tasks []models.Task + + // Get all task IDs assigned to the user + for _, userID := range userIDs { + fmt.Println(userID) + taskIDs, err := pool.Query("SELECT task_id FROM task_assignees WHERE user_id = $1;", userID) + if err != nil { + print(err, "error selecting task assignees") + return nil, err + } + defer taskIDs.Close() + + for taskIDs.Next() { + var task_id int + + err := taskIDs.Scan(&task_id) + if err != nil { + print(err, "error scanning task ID") + return nil, err + } + fmt.Println(task_id) + task_ids = append(task_ids, task_id) + } + } + + // Get all tasks by task ID + var task models.Task + for _, task_id := range task_ids { + err := pool.QueryRow("SELECT * FROM task WHERE task_id = $1;", task_id).Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.StartDate, &task.EndDate, &task.Notes, &task.Repeating, &task.RepeatingInterval, &task.RepeatingEndDate, &task.TaskStatus, &task.TaskType, &task.TaskInfo) + if err != nil { + print(err, "error querying task by ID") + return nil, err + } + + tasks = append(tasks, task) + } + + return tasks, nil +} From 9900455d3f5181bb30406e09a37c6bc812a6f69e Mon Sep 17 00:00:00 2001 From: oliviaseds Date: Thu, 8 Feb 2024 10:09:16 -0500 Subject: [PATCH 19/29] feat: add a new label crud and test --- backend/docs/docs.go | 62 +++++++++++++++++++++ backend/docs/swagger.json | 62 +++++++++++++++++++++ backend/docs/swagger.yaml | 40 ++++++++++++++ backend/main.go | 4 +- backend/models/label.go | 7 +++ backend/schema/files/routes.go | 2 +- backend/schema/labels/label_test.go | 77 +++++++++++++++++++++++++++ backend/schema/labels/routes.go | 56 +++++++++++++++++++ backend/schema/labels/transactions.go | 26 +++++++++ backend/schema/tasks/routes.go | 3 -- 10 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 backend/models/label.go create mode 100644 backend/schema/labels/label_test.go create mode 100644 backend/schema/labels/routes.go create mode 100644 backend/schema/labels/transactions.go diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 5878a3a..0ee1eaa 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -44,6 +44,40 @@ const docTemplate = `{ } } }, + "/labels/new": { + "post": { + "description": "create a new label for a group", + "tags": [ + "labels" + ], + "summary": "Create A New Label", + "parameters": [ + { + "description": "Label creation data", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/labels.LabelCreation" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Label" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/medications": { "get": { "description": "get all user medications", @@ -249,6 +283,20 @@ const docTemplate = `{ } }, "definitions": { + "labels.LabelCreation": { + "type": "object", + "properties": { + "group_id": { + "type": "integer" + }, + "label_color": { + "type": "string" + }, + "label_name": { + "type": "string" + } + } + }, "models.File": { "type": "object", "properties": { @@ -275,6 +323,20 @@ const docTemplate = `{ } } }, + "models.Label": { + "type": "object", + "properties": { + "group_id": { + "type": "integer" + }, + "label_color": { + "type": "string" + }, + "label_name": { + "type": "string" + } + } + }, "models.Medication": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 29d37ff..c8ba444 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -37,6 +37,40 @@ } } }, + "/labels/new": { + "post": { + "description": "create a new label for a group", + "tags": [ + "labels" + ], + "summary": "Create A New Label", + "parameters": [ + { + "description": "Label creation data", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/labels.LabelCreation" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Label" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/medications": { "get": { "description": "get all user medications", @@ -242,6 +276,20 @@ } }, "definitions": { + "labels.LabelCreation": { + "type": "object", + "properties": { + "group_id": { + "type": "integer" + }, + "label_color": { + "type": "string" + }, + "label_name": { + "type": "string" + } + } + }, "models.File": { "type": "object", "properties": { @@ -268,6 +316,20 @@ } } }, + "models.Label": { + "type": "object", + "properties": { + "group_id": { + "type": "integer" + }, + "label_color": { + "type": "string" + }, + "label_name": { + "type": "string" + } + } + }, "models.Medication": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 66b8caf..212af03 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1,5 +1,14 @@ basePath: / definitions: + labels.LabelCreation: + properties: + group_id: + type: integer + label_color: + type: string + label_name: + type: string + type: object models.File: properties: file_id: @@ -17,6 +26,15 @@ definitions: upload_date: type: string type: object + models.Label: + properties: + group_id: + type: integer + label_color: + type: string + label_name: + type: string + type: object models.Medication: properties: medication_id: @@ -102,6 +120,28 @@ paths: summary: Upload a file tags: - file + /labels/new: + post: + description: create a new label for a group + parameters: + - description: Label creation data + in: body + name: _ + required: true + schema: + $ref: '#/definitions/labels.LabelCreation' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Label' + "400": + description: Bad Request + schema: + type: string + summary: Create A New Label + tags: + - labels /medications: get: description: get all user medications diff --git a/backend/main.go b/backend/main.go index 661c2e3..0a7d42a 100644 --- a/backend/main.go +++ b/backend/main.go @@ -5,6 +5,7 @@ import ( "carewallet/db" _ "carewallet/docs" "carewallet/schema/files" + "carewallet/schema/labels" "carewallet/schema/medication" "carewallet/schema/tasks" "fmt" @@ -39,8 +40,9 @@ func main() { v1 := r.Group("/") { medication.GetMedicationGroup(v1, &medication.PgModel{Conn: conn}) - files.GetFileGroup(v1, &files.PgModel{Conn: conn}) + files.FileGroup(v1, &files.PgModel{Conn: conn}) tasks.TaskGroup(v1, &tasks.PgModel{Conn: conn}) + labels.LabelGroup(v1, &labels.PgModel{Conn: conn}) } if enviroment == configuration.EnvironmentLocal { diff --git a/backend/models/label.go b/backend/models/label.go new file mode 100644 index 0000000..464f0f6 --- /dev/null +++ b/backend/models/label.go @@ -0,0 +1,7 @@ +package models + +type Label struct { + GroupID int `json:"group_id"` + LabelName string `json:"label_name"` + LabelColor string `json:"label_color"` +} diff --git a/backend/schema/files/routes.go b/backend/schema/files/routes.go index f32a874..0507466 100644 --- a/backend/schema/files/routes.go +++ b/backend/schema/files/routes.go @@ -13,7 +13,7 @@ type PgModel struct { Conn *pgx.Conn } -func GetFileGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { +func FileGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { files := v1.Group("files") { diff --git a/backend/schema/labels/label_test.go b/backend/schema/labels/label_test.go new file mode 100644 index 0000000..556dbc8 --- /dev/null +++ b/backend/schema/labels/label_test.go @@ -0,0 +1,77 @@ +package labels + +import ( + "bytes" + "carewallet/configuration" + "carewallet/db" + "carewallet/models" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "reflect" + "testing" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +func TestLabelGroup(t *testing.T) { + config, err := configuration.GetConfiguration() + + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to retreive configuration file: %v\n", err) + os.Exit(1) + } + + conn := db.ConnectPosgresDatabase(config) + defer conn.Close() + + controller := PgModel{Conn: conn} + router := gin.Default() + router.Use(cors.Default()) + + v1 := router.Group("/") + { + LabelGroup(v1, &controller) + } + + t.Run("TestCreateNewLabel", func(t *testing.T) { + postRequest := LabelCreation{ + GroupID: 1, + LabelName: "Laundry", + LabelColor: "Orange", + } + + requestJSON, err := json.Marshal(postRequest) + if err != nil { + t.Error("Failed to marshal remove request to JSON") + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/labels/new", bytes.NewBuffer(requestJSON)) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to create new label.") + } + + var postResponse models.Label + err = json.Unmarshal(w.Body.Bytes(), &postResponse) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + expectedResponse := models.Label{ + GroupID: 1, + LabelName: "Laundry", + LabelColor: "Orange", + } + + if !reflect.DeepEqual(expectedResponse, postResponse) { + t.Error("Result was not correct") + } + }) +} diff --git a/backend/schema/labels/routes.go b/backend/schema/labels/routes.go new file mode 100644 index 0000000..791feb2 --- /dev/null +++ b/backend/schema/labels/routes.go @@ -0,0 +1,56 @@ +package labels + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/jackc/pgx" +) + +type PgModel struct { + Conn *pgx.Conn +} + +func LabelGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { + + labels := v1.Group("labels") + { + labels.POST("/new", c.CreateNewLabel) + } + + return labels +} + +type LabelCreation struct { + GroupID int `json:"group_id"` + LabelName string `json:"label_name"` + LabelColor string `json:"label_color"` +} + +// CreateNewLabel godoc +// +// @summary Create A New Label +// @description create a new label for a group +// @tags labels +// +// @param _ body LabelCreation true "Label creation data" +// +// @success 200 {object} models.Label +// @failure 400 {object} string +// @router /labels/new [post] +func (pg *PgModel) CreateNewLabel(c *gin.Context) { + var requestBody LabelCreation + + if err := c.BindJSON(&requestBody); err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + label, err := CreateNewLabelInDB(pg.Conn, requestBody) + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, label) +} diff --git a/backend/schema/labels/transactions.go b/backend/schema/labels/transactions.go new file mode 100644 index 0000000..e82edef --- /dev/null +++ b/backend/schema/labels/transactions.go @@ -0,0 +1,26 @@ +package labels + +import ( + "carewallet/models" + + "github.com/jackc/pgx" +) + +func CreateNewLabelInDB(pool *pgx.Conn, requestBody LabelCreation) (models.Label, error) { + groupID := requestBody.GroupID + labelName := requestBody.LabelName + labelColor := requestBody.LabelColor + + _, err := pool.Exec("INSERT INTO label (group_id, label_name, label_color) VALUES ($1, $2, $3)", groupID, labelName, labelColor) + + if err != nil { + return models.Label{}, err + } + + label := models.Label{ + GroupID: groupID, + LabelName: labelName, + LabelColor: labelColor, + } + return label, nil +} diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index 80ec410..2ccef46 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -1,7 +1,6 @@ package tasks import ( - "fmt" "net/http" "strings" @@ -87,7 +86,6 @@ func (pg *PgModel) AssignUsersToTask(c *gin.Context) { var requestBody Assignment if err := c.BindJSON(&requestBody); err != nil { - print(err.Error()) c.JSON(http.StatusBadRequest, err.Error()) return } @@ -156,7 +154,6 @@ func (pg *PgModel) GetTasksByAssignedUsers(c *gin.Context) { assignedQuery := AssignedQuery{ UserIDs: strings.Split(userIDs, ","), } - fmt.Println(assignedQuery.UserIDs) tasks, err := GetTasksByAssignedFromDB(pg.Conn, assignedQuery.UserIDs) From 395db6c768cfd801311b3a2652bce0e7ff6c39d4 Mon Sep 17 00:00:00 2001 From: oliviaseds Date: Thu, 8 Feb 2024 11:40:49 -0500 Subject: [PATCH 20/29] feat: delete label crud and test --- backend/docs/docs.go | 43 +++++++++++++++++++++++++-- backend/docs/swagger.json | 43 +++++++++++++++++++++++++-- backend/docs/swagger.yaml | 30 +++++++++++++++++-- backend/schema/labels/label_test.go | 14 +++++++-- backend/schema/labels/routes.go | 26 ++++++++++++++++ backend/schema/labels/transactions.go | 16 ++++++++++ backend/schema/tasks/routes.go | 16 +++++----- 7 files changed, 172 insertions(+), 16 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 0ee1eaa..7579b6b 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -44,6 +44,45 @@ const docTemplate = `{ } } }, + "/labels/delete/{:gid}/{:lname}": { + "delete": { + "description": "delete a label", + "tags": [ + "labels" + ], + "summary": "Delete A Label", + "parameters": [ + { + "type": "string", + "description": "Group to delete label from", + "name": ":gid", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Name of label to delete", + "name": ":lname", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Label" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/labels/new": { "post": { "description": "create a new label for a group", @@ -204,7 +243,7 @@ const docTemplate = `{ { "type": "string", "description": "Task ID to assign users to", - "name": "tid", + "name": ":tid", "in": "path", "required": true }, @@ -248,7 +287,7 @@ const docTemplate = `{ { "type": "string", "description": "Task ID to remove users from", - "name": "tid", + "name": ":tid", "in": "path", "required": true }, diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index c8ba444..376e99d 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -37,6 +37,45 @@ } } }, + "/labels/delete/{:gid}/{:lname}": { + "delete": { + "description": "delete a label", + "tags": [ + "labels" + ], + "summary": "Delete A Label", + "parameters": [ + { + "type": "string", + "description": "Group to delete label from", + "name": ":gid", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Name of label to delete", + "name": ":lname", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Label" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/labels/new": { "post": { "description": "create a new label for a group", @@ -197,7 +236,7 @@ { "type": "string", "description": "Task ID to assign users to", - "name": "tid", + "name": ":tid", "in": "path", "required": true }, @@ -241,7 +280,7 @@ { "type": "string", "description": "Task ID to remove users from", - "name": "tid", + "name": ":tid", "in": "path", "required": true }, diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 212af03..5b3ec14 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -120,6 +120,32 @@ paths: summary: Upload a file tags: - file + /labels/delete/{:gid}/{:lname}: + delete: + description: delete a label + parameters: + - description: Group to delete label from + in: path + name: :gid + required: true + type: string + - description: Name of label to delete + in: path + name: :lname + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Label' + "400": + description: Bad Request + schema: + type: string + summary: Delete A Label + tags: + - labels /labels/new: post: description: create a new label for a group @@ -161,7 +187,7 @@ paths: parameters: - description: Task ID to assign users to in: path - name: tid + name: :tid required: true type: string - description: Users to assign to task and assignee @@ -190,7 +216,7 @@ paths: parameters: - description: Task ID to remove users from in: path - name: tid + name: :tid required: true type: string - description: Users to remove from task diff --git a/backend/schema/labels/label_test.go b/backend/schema/labels/label_test.go index 556dbc8..f125314 100644 --- a/backend/schema/labels/label_test.go +++ b/backend/schema/labels/label_test.go @@ -39,7 +39,7 @@ func TestLabelGroup(t *testing.T) { t.Run("TestCreateNewLabel", func(t *testing.T) { postRequest := LabelCreation{ - GroupID: 1, + GroupID: 3, LabelName: "Laundry", LabelColor: "Orange", } @@ -65,7 +65,7 @@ func TestLabelGroup(t *testing.T) { } expectedResponse := models.Label{ - GroupID: 1, + GroupID: 3, LabelName: "Laundry", LabelColor: "Orange", } @@ -74,4 +74,14 @@ func TestLabelGroup(t *testing.T) { t.Error("Result was not correct") } }) + + t.Run("TestDeleteLabel", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("DELETE", "/labels/delete/2/Appointment", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to delete label.") + } + }) } diff --git a/backend/schema/labels/routes.go b/backend/schema/labels/routes.go index 791feb2..06827b5 100644 --- a/backend/schema/labels/routes.go +++ b/backend/schema/labels/routes.go @@ -16,6 +16,7 @@ func LabelGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { labels := v1.Group("labels") { labels.POST("/new", c.CreateNewLabel) + labels.DELETE("/delete/:gid/:lname", c.DeleteLabel) } return labels @@ -54,3 +55,28 @@ func (pg *PgModel) CreateNewLabel(c *gin.Context) { c.JSON(http.StatusOK, label) } + +// DeleteLabel godoc +// +// @summary Delete A Label +// @description delete a label +// @tags labels +// +// @param :gid path string true "Group to delete label from" +// @param :lname path string true "Name of label to delete" +// +// @success 200 {object} models.Label +// @failure 400 {object} string +// @router /labels/delete/{:gid}/{:lname} [DELETE] +func (pg *PgModel) DeleteLabel(c *gin.Context) { + group_id := c.Param("gid") + label_name := c.Param("lname") + + err := DeleteLabelFromDB(pg.Conn, group_id, label_name) + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, nil) +} diff --git a/backend/schema/labels/transactions.go b/backend/schema/labels/transactions.go index e82edef..66616c0 100644 --- a/backend/schema/labels/transactions.go +++ b/backend/schema/labels/transactions.go @@ -2,6 +2,7 @@ package labels import ( "carewallet/models" + "strconv" "github.com/jackc/pgx" ) @@ -14,6 +15,7 @@ func CreateNewLabelInDB(pool *pgx.Conn, requestBody LabelCreation) (models.Label _, err := pool.Exec("INSERT INTO label (group_id, label_name, label_color) VALUES ($1, $2, $3)", groupID, labelName, labelColor) if err != nil { + print(err.Error()) return models.Label{}, err } @@ -24,3 +26,17 @@ func CreateNewLabelInDB(pool *pgx.Conn, requestBody LabelCreation) (models.Label } return label, nil } + +func DeleteLabelFromDB(pool *pgx.Conn, groupID string, labelName string) error { + groupIDInt, err := strconv.Atoi(groupID) + if err != nil { + return err + } + + _, err = pool.Exec("DELETE FROM label WHERE group_id = $1 AND label_name = $2", groupIDInt, labelName) + if err != nil { + return err + } + + return nil +} diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index 2ccef46..658ca7e 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -76,11 +76,11 @@ type Assignment struct { // @description assign users to task // @tags tasks // -// @param tid path string true "Task ID to assign users to" -// @param _ body Assignment true "Users to assign to task and assignee" +// @param :tid path string true "Task ID to assign users to" +// @param _ body Assignment true "Users to assign to task and assignee" // -// @success 200 {array} models.TaskUser -// @failure 400 {object} string +// @success 200 {array} models.TaskUser +// @failure 400 {object} string // @router /tasks/{tid}/assign [post] func (pg *PgModel) AssignUsersToTask(c *gin.Context) { var requestBody Assignment @@ -110,11 +110,11 @@ type Removal struct { // @description remove users from task // @tags tasks // -// @param tid path string true "Task ID to remove users from" -// @param _ body Removal true "Users to remove from task" +// @param :tid path string true "Task ID to remove users from" +// @param _ body Removal true "Users to remove from task" // -// @success 200 {array} models.TaskUser -// @failure 400 {object} string +// @success 200 {array} models.TaskUser +// @failure 400 {object} string // @router /tasks/{tid}/remove [delete] func (pg *PgModel) RemoveUsersFromTask(c *gin.Context) { var requestBody Removal From 94d567442be17a8cae32d51e7b3a87268bff27c3 Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Tue, 13 Feb 2024 08:44:47 -0500 Subject: [PATCH 21/29] refactor: FE remove extra card file --- client/components/Card.tsx | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 client/components/Card.tsx diff --git a/client/components/Card.tsx b/client/components/Card.tsx deleted file mode 100644 index 27bdb82..0000000 --- a/client/components/Card.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -import { Card } from 'react-native-paper'; - -interface ClickableCardProps { - title: string; - onPress: () => void; - children?: JSX.Element[] | JSX.Element; -} - -export function ClickableCard({ - title, - onPress, - children -}: ClickableCardProps) { - return ( - - - {children} - - ); -} From 8984b93f5b0e51ccd68bbc114c020ecd2ce92bb6 Mon Sep 17 00:00:00 2001 From: oliviaseds Date: Tue, 13 Feb 2024 15:26:49 -0500 Subject: [PATCH 22/29] test: fix typo in db, tests passing locally --- backend/db/migrations/init.sql | 4 ++-- backend/schema/labels/label_test.go | 8 ++++---- backend/schema/labels/routes.go | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql index e57ba99..5feeb2c 100644 --- a/backend/db/migrations/init.sql +++ b/backend/db/migrations/init.sql @@ -130,7 +130,7 @@ VALUES ('Johnson Support Network', NOW()), ('Williams Care Team', NOW()), ('Brown Medical Group', NOW()), - ('Care-Wallet Group', NOW()), + ('Care-Wallet Group', NOW()) ; INSERT INTO users (user_id, first_name, last_name, email, phone, address) @@ -139,7 +139,7 @@ VALUES ('user2', 'Jane', 'Doe', 'jane.doe@example.com', '987-654-3210', '456 Elm St'), ('user3', 'Bob', 'Johnson', 'bob.johnson@example.com', NULL, NULL), ('user4', 'Emily', 'Garcia', 'emily.garcia@example.com', '555-1212', '789 Oak Ave'), - ('fIoFY26mJnYWH8sNdfuVoxpnVnr1', 'Matt', 'McCoy', '', '', ''); + ('fIoFY26mJnYWH8sNdfuVoxpnVnr1', 'Matt', 'McCoy', '', '', '') ; INSERT INTO group_roles (group_id, user_id, role) diff --git a/backend/schema/labels/label_test.go b/backend/schema/labels/label_test.go index f125314..0bd9df5 100644 --- a/backend/schema/labels/label_test.go +++ b/backend/schema/labels/label_test.go @@ -39,8 +39,8 @@ func TestLabelGroup(t *testing.T) { t.Run("TestCreateNewLabel", func(t *testing.T) { postRequest := LabelCreation{ - GroupID: 3, - LabelName: "Laundry", + GroupID: 1, + LabelName: "Office", LabelColor: "Orange", } @@ -65,8 +65,8 @@ func TestLabelGroup(t *testing.T) { } expectedResponse := models.Label{ - GroupID: 3, - LabelName: "Laundry", + GroupID: 1, + LabelName: "Office", LabelColor: "Orange", } diff --git a/backend/schema/labels/routes.go b/backend/schema/labels/routes.go index 06827b5..8d0ff9e 100644 --- a/backend/schema/labels/routes.go +++ b/backend/schema/labels/routes.go @@ -1,6 +1,7 @@ package labels import ( + "fmt" "net/http" "github.com/gin-gonic/gin" @@ -43,6 +44,7 @@ func (pg *PgModel) CreateNewLabel(c *gin.Context) { var requestBody LabelCreation if err := c.BindJSON(&requestBody); err != nil { + fmt.Println("Error binding JSON: ", err.Error()) c.JSON(http.StatusBadRequest, err.Error()) return } From f1ec4052606aff28506e1de916c8a0b09fe2508e Mon Sep 17 00:00:00 2001 From: oliviaseds Date: Tue, 13 Feb 2024 15:50:06 -0500 Subject: [PATCH 23/29] refactor: modify task filter to bind to query --- backend/schema/tasks/routes.go | 23 ++++++++++------------- backend/schema/tasks/task_test.go | 12 ++++++------ 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index 658ca7e..dc1c2f0 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -26,12 +26,12 @@ func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { } type TaskQuery struct { - GroupID string `query:"groupID"` - CreatedBy string `query:"createdBy"` - TaskStatus string `query:"taskStatus"` - TaskType string `query:"taskType"` - StartDate string `query:"startDate"` - EndDate string `query:"endDate"` + GroupID string `form:"groupID"` + CreatedBy string `form:"createdBy"` + TaskStatus string `form:"taskStatus"` + TaskType string `form:"taskType"` + StartDate string `form:"startDate"` + EndDate string `form:"endDate"` } // GetFilteredTasks godoc @@ -46,13 +46,10 @@ type TaskQuery struct { // @failure 400 {object} string // @router /tasks/filtered [get] func (pg *PgModel) GetFilteredTasks(c *gin.Context) { - filterQuery := TaskQuery{ - GroupID: c.Query("groupID"), - CreatedBy: c.Query("createdBy"), - TaskStatus: c.Query("taskStatus"), - TaskType: c.Query("taskType"), - StartDate: c.Query("startDate"), - EndDate: c.Query("endDate"), + var filterQuery TaskQuery + if err := c.ShouldBindQuery(&filterQuery); err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return } tasks, err := GetTasksByQueryFromDB(pg.Conn, filterQuery) diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 2a9048c..309d4f3 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -51,12 +51,12 @@ func TestTaskGroup(t *testing.T) { w := httptest.NewRecorder() query := url.Values{} - query.Set("groupID", getRequest.GroupID) - query.Set("createdBy", getRequest.CreatedBy) - query.Set("taskStatus", getRequest.TaskStatus) - query.Set("taskType", getRequest.TaskType) - query.Set("startDate", getRequest.StartDate) - query.Set("endDate", getRequest.EndDate) + query.Add("groupID", getRequest.GroupID) + query.Add("createdBy", getRequest.CreatedBy) + query.Add("taskStatus", getRequest.TaskStatus) + query.Add("taskType", getRequest.TaskType) + query.Add("startDate", getRequest.StartDate) + query.Add("endDate", getRequest.EndDate) req, _ := http.NewRequest("GET", "/tasks/filtered?"+query.Encode(), nil) router.ServeHTTP(w, req) From b8b16a80f53d2a26cb0936bd344684f2af69eca7 Mon Sep 17 00:00:00 2001 From: oliviaseds Date: Tue, 13 Feb 2024 16:30:14 -0500 Subject: [PATCH 24/29] feat: update task name and color route and test --- backend/db/migrations/init.sql | 4 +-- backend/docs/docs.go | 52 +++++++++++++++++++++++++-- backend/docs/swagger.json | 52 +++++++++++++++++++++++++-- backend/docs/swagger.yaml | 36 +++++++++++++++++-- backend/schema/labels/label_test.go | 40 ++++++++++++++++++++- backend/schema/labels/routes.go | 40 +++++++++++++++++++-- backend/schema/labels/transactions.go | 33 ++++++++++++++++- 7 files changed, 244 insertions(+), 13 deletions(-) diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql index 5feeb2c..e2a77a1 100644 --- a/backend/db/migrations/init.sql +++ b/backend/db/migrations/init.sql @@ -94,8 +94,8 @@ CREATE TABLE IF NOT EXISTS task_assignees ( group_id integer NOT NULL, label_name varchar NOT NULL, PRIMARY KEY (task_id, label_name), - FOREIGN KEY (task_id) REFERENCES task (task_id), - FOREIGN KEY (group_id, label_name) REFERENCES label (group_id, label_name) -- NOTE: unsure about label/task_labels table constraints, uncommenting this line is err + FOREIGN KEY (task_id) REFERENCES task (task_id) ON UPDATE CASCADE, + FOREIGN KEY (group_id, label_name) REFERENCES label (group_id, label_name) ON UPDATE CASCADE -- NOTE: unsure about label/task_labels table constraints, uncommenting this line is err ); CREATE TABLE IF NOT EXISTS files ( diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 52d453c..deb2f42 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -100,6 +100,54 @@ const docTemplate = `{ } } }, + "/labels/edit/{:gid}/{:lname}": { + "patch": { + "description": "edit a label", + "tags": [ + "labels" + ], + "summary": "Edit A Label", + "parameters": [ + { + "type": "string", + "description": "Group of label to edit", + "name": ":gid", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Name of label to edit", + "name": ":lname", + "in": "path", + "required": true + }, + { + "description": "Label edit data", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/labels.LabelData" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Label" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/labels/new": { "post": { "description": "create a new label for a group", @@ -114,7 +162,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/labels.LabelCreation" + "$ref": "#/definitions/labels.LabelData" } } ], @@ -371,7 +419,7 @@ const docTemplate = `{ } }, "definitions": { - "labels.LabelCreation": { + "labels.LabelData": { "type": "object", "properties": { "group_id": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 1dd62dc..23eff52 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -93,6 +93,54 @@ } } }, + "/labels/edit/{:gid}/{:lname}": { + "patch": { + "description": "edit a label", + "tags": [ + "labels" + ], + "summary": "Edit A Label", + "parameters": [ + { + "type": "string", + "description": "Group of label to edit", + "name": ":gid", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Name of label to edit", + "name": ":lname", + "in": "path", + "required": true + }, + { + "description": "Label edit data", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/labels.LabelData" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Label" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/labels/new": { "post": { "description": "create a new label for a group", @@ -107,7 +155,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/labels.LabelCreation" + "$ref": "#/definitions/labels.LabelData" } } ], @@ -364,7 +412,7 @@ } }, "definitions": { - "labels.LabelCreation": { + "labels.LabelData": { "type": "object", "properties": { "group_id": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index eca5625..bf324b2 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1,6 +1,6 @@ basePath: / definitions: - labels.LabelCreation: + labels.LabelData: properties: group_id: type: integer @@ -158,6 +158,38 @@ paths: summary: Delete A Label tags: - labels + /labels/edit/{:gid}/{:lname}: + patch: + description: edit a label + parameters: + - description: Group of label to edit + in: path + name: :gid + required: true + type: string + - description: Name of label to edit + in: path + name: :lname + required: true + type: string + - description: Label edit data + in: body + name: _ + required: true + schema: + $ref: '#/definitions/labels.LabelData' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Label' + "400": + description: Bad Request + schema: + type: string + summary: Edit A Label + tags: + - labels /labels/new: post: description: create a new label for a group @@ -167,7 +199,7 @@ paths: name: _ required: true schema: - $ref: '#/definitions/labels.LabelCreation' + $ref: '#/definitions/labels.LabelData' responses: "200": description: OK diff --git a/backend/schema/labels/label_test.go b/backend/schema/labels/label_test.go index 0bd9df5..c504ec5 100644 --- a/backend/schema/labels/label_test.go +++ b/backend/schema/labels/label_test.go @@ -38,7 +38,7 @@ func TestLabelGroup(t *testing.T) { } t.Run("TestCreateNewLabel", func(t *testing.T) { - postRequest := LabelCreation{ + postRequest := LabelData{ GroupID: 1, LabelName: "Office", LabelColor: "Orange", @@ -84,4 +84,42 @@ func TestLabelGroup(t *testing.T) { t.Error("Failed to delete label.") } }) + + t.Run("TestEditLabel", func(t *testing.T) { + postRequest := LabelData{ + GroupID: 4, + LabelName: "Family", + LabelColor: "Yellow", + } + + requestJSON, err := json.Marshal(postRequest) + if err != nil { + t.Error("Failed to marshal remove request to JSON") + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest("PATCH", "/labels/edit/4/Household", bytes.NewBuffer(requestJSON)) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to edit label.") + } + + var postResponse models.Label + err = json.Unmarshal(w.Body.Bytes(), &postResponse) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + expectedResponse := models.Label{ + GroupID: 4, + LabelName: "Family", + LabelColor: "Yellow", + } + + if !reflect.DeepEqual(expectedResponse, postResponse) { + t.Error("Result was not correct") + } + }) } diff --git a/backend/schema/labels/routes.go b/backend/schema/labels/routes.go index 8d0ff9e..55038d3 100644 --- a/backend/schema/labels/routes.go +++ b/backend/schema/labels/routes.go @@ -18,12 +18,13 @@ func LabelGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { { labels.POST("/new", c.CreateNewLabel) labels.DELETE("/delete/:gid/:lname", c.DeleteLabel) + labels.PATCH("/edit/:gid/:lname", c.EditLabel) } return labels } -type LabelCreation struct { +type LabelData struct { GroupID int `json:"group_id"` LabelName string `json:"label_name"` LabelColor string `json:"label_color"` @@ -35,13 +36,13 @@ type LabelCreation struct { // @description create a new label for a group // @tags labels // -// @param _ body LabelCreation true "Label creation data" +// @param _ body LabelData true "Label creation data" // // @success 200 {object} models.Label // @failure 400 {object} string // @router /labels/new [post] func (pg *PgModel) CreateNewLabel(c *gin.Context) { - var requestBody LabelCreation + var requestBody LabelData if err := c.BindJSON(&requestBody); err != nil { fmt.Println("Error binding JSON: ", err.Error()) @@ -82,3 +83,36 @@ func (pg *PgModel) DeleteLabel(c *gin.Context) { c.JSON(http.StatusOK, nil) } + +// EditLabel godoc +// +// @summary Edit A Label +// @description edit a label +// @tags labels +// +// @param :gid path string true "Group of label to edit" +// @param :lname path string true "Name of label to edit" +// @param _ body LabelData true "Label edit data" +// +// @success 200 {object} models.Label +// @failure 400 {object} string +// @router /labels/edit/{:gid}/{:lname} [PATCH] +func (pg *PgModel) EditLabel(c *gin.Context) { + group_id := c.Param("gid") + label_name := c.Param("lname") + + var requestBody LabelData + + if err := c.BindJSON(&requestBody); err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + label, err := EditLabelInDB(pg.Conn, group_id, label_name, requestBody) + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, label) +} diff --git a/backend/schema/labels/transactions.go b/backend/schema/labels/transactions.go index 66616c0..7068842 100644 --- a/backend/schema/labels/transactions.go +++ b/backend/schema/labels/transactions.go @@ -7,7 +7,7 @@ import ( "github.com/jackc/pgx" ) -func CreateNewLabelInDB(pool *pgx.Conn, requestBody LabelCreation) (models.Label, error) { +func CreateNewLabelInDB(pool *pgx.Conn, requestBody LabelData) (models.Label, error) { groupID := requestBody.GroupID labelName := requestBody.LabelName labelColor := requestBody.LabelColor @@ -40,3 +40,34 @@ func DeleteLabelFromDB(pool *pgx.Conn, groupID string, labelName string) error { return nil } + +func EditLabelInDB(pool *pgx.Conn, groupID string, labelName string, data LabelData) (models.Label, error) { + groupIDInt, err := strconv.Atoi(groupID) + if err != nil { + return models.Label{}, err + } + + _, err = pool.Exec("UPDATE label SET label_color = $1, label_name = $2 WHERE group_id = $3 AND label_name = $4", data.LabelColor, data.LabelName, groupIDInt, labelName) + if err != nil { + print(err.Error()) + return models.Label{}, err + } + + // Is there a better way to do this when we don't know which fields are being edited? + editedName := data.LabelName + if editedName == "" { + editedName = labelName + } + + var label = models.Label{ + GroupID: groupIDInt, + LabelName: editedName, + } + + err = pool.QueryRow("SELECT label_color FROM label WHERE group_id = $1 AND label_name = $2", groupIDInt, editedName).Scan(&label.LabelColor) + if err != nil { + return models.Label{}, err + } + + return label, nil +} From 97f971aea7e98c773a5b007107a1cbff07a441f5 Mon Sep 17 00:00:00 2001 From: oliviaseds Date: Wed, 14 Feb 2024 13:07:53 -0500 Subject: [PATCH 25/29] docs: fix swaggo comment style for task assignment and deletion --- backend/docs/docs.go | 4 ++-- backend/docs/swagger.json | 4 ++-- backend/docs/swagger.yaml | 4 ++-- backend/schema/tasks/routes.go | 16 ++++++++-------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index deb2f42..f53b2e8 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -340,7 +340,7 @@ const docTemplate = `{ { "type": "string", "description": "Task ID to assign users to", - "name": ":tid", + "name": "tid", "in": "path", "required": true }, @@ -384,7 +384,7 @@ const docTemplate = `{ { "type": "string", "description": "Task ID to remove users from", - "name": ":tid", + "name": "tid", "in": "path", "required": true }, diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 23eff52..eec760c 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -333,7 +333,7 @@ { "type": "string", "description": "Task ID to assign users to", - "name": ":tid", + "name": "tid", "in": "path", "required": true }, @@ -377,7 +377,7 @@ { "type": "string", "description": "Task ID to remove users from", - "name": ":tid", + "name": "tid", "in": "path", "required": true }, diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index bf324b2..18976e0 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -252,7 +252,7 @@ paths: parameters: - description: Task ID to assign users to in: path - name: :tid + name: tid required: true type: string - description: Users to assign to task and assignee @@ -281,7 +281,7 @@ paths: parameters: - description: Task ID to remove users from in: path - name: :tid + name: tid required: true type: string - description: Users to remove from task diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index dc1c2f0..5b8bf48 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -73,11 +73,11 @@ type Assignment struct { // @description assign users to task // @tags tasks // -// @param :tid path string true "Task ID to assign users to" -// @param _ body Assignment true "Users to assign to task and assignee" +// @param tid path string true "Task ID to assign users to" +// @param _ body Assignment true "Users to assign to task and assignee" // -// @success 200 {array} models.TaskUser -// @failure 400 {object} string +// @success 200 {array} models.TaskUser +// @failure 400 {object} string // @router /tasks/{tid}/assign [post] func (pg *PgModel) AssignUsersToTask(c *gin.Context) { var requestBody Assignment @@ -107,11 +107,11 @@ type Removal struct { // @description remove users from task // @tags tasks // -// @param :tid path string true "Task ID to remove users from" -// @param _ body Removal true "Users to remove from task" +// @param tid path string true "Task ID to remove users from" +// @param _ body Removal true "Users to remove from task" // -// @success 200 {array} models.TaskUser -// @failure 400 {object} string +// @success 200 {array} models.TaskUser +// @failure 400 {object} string // @router /tasks/{tid}/remove [delete] func (pg *PgModel) RemoveUsersFromTask(c *gin.Context) { var requestBody Removal From 42d4129f9a685e185b8340c785b2e0ea9549943e Mon Sep 17 00:00:00 2001 From: oliviaseds Date: Wed, 14 Feb 2024 13:10:39 -0500 Subject: [PATCH 26/29] docs: same swagger changes for label routes --- backend/docs/docs.go | 12 ++++++------ backend/docs/swagger.json | 12 ++++++------ backend/docs/swagger.yaml | 12 ++++++------ backend/schema/labels/routes.go | 12 ++++++------ 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index f53b2e8..68843c2 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -61,7 +61,7 @@ const docTemplate = `{ } } }, - "/labels/delete/{:gid}/{:lname}": { + "/labels/delete/{gid}/{lname}": { "delete": { "description": "delete a label", "tags": [ @@ -72,14 +72,14 @@ const docTemplate = `{ { "type": "string", "description": "Group to delete label from", - "name": ":gid", + "name": "gid", "in": "path", "required": true }, { "type": "string", "description": "Name of label to delete", - "name": ":lname", + "name": "lname", "in": "path", "required": true } @@ -100,7 +100,7 @@ const docTemplate = `{ } } }, - "/labels/edit/{:gid}/{:lname}": { + "/labels/edit/{gid}/{lname}": { "patch": { "description": "edit a label", "tags": [ @@ -111,14 +111,14 @@ const docTemplate = `{ { "type": "string", "description": "Group of label to edit", - "name": ":gid", + "name": "gid", "in": "path", "required": true }, { "type": "string", "description": "Name of label to edit", - "name": ":lname", + "name": "lname", "in": "path", "required": true }, diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index eec760c..db93252 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -54,7 +54,7 @@ } } }, - "/labels/delete/{:gid}/{:lname}": { + "/labels/delete/{gid}/{lname}": { "delete": { "description": "delete a label", "tags": [ @@ -65,14 +65,14 @@ { "type": "string", "description": "Group to delete label from", - "name": ":gid", + "name": "gid", "in": "path", "required": true }, { "type": "string", "description": "Name of label to delete", - "name": ":lname", + "name": "lname", "in": "path", "required": true } @@ -93,7 +93,7 @@ } } }, - "/labels/edit/{:gid}/{:lname}": { + "/labels/edit/{gid}/{lname}": { "patch": { "description": "edit a label", "tags": [ @@ -104,14 +104,14 @@ { "type": "string", "description": "Group of label to edit", - "name": ":gid", + "name": "gid", "in": "path", "required": true }, { "type": "string", "description": "Name of label to edit", - "name": ":lname", + "name": "lname", "in": "path", "required": true }, diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 18976e0..5c0bf92 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -132,18 +132,18 @@ paths: summary: Upload a file tags: - file - /labels/delete/{:gid}/{:lname}: + /labels/delete/{gid}/{lname}: delete: description: delete a label parameters: - description: Group to delete label from in: path - name: :gid + name: gid required: true type: string - description: Name of label to delete in: path - name: :lname + name: lname required: true type: string responses: @@ -158,18 +158,18 @@ paths: summary: Delete A Label tags: - labels - /labels/edit/{:gid}/{:lname}: + /labels/edit/{gid}/{lname}: patch: description: edit a label parameters: - description: Group of label to edit in: path - name: :gid + name: gid required: true type: string - description: Name of label to edit in: path - name: :lname + name: lname required: true type: string - description: Label edit data diff --git a/backend/schema/labels/routes.go b/backend/schema/labels/routes.go index 55038d3..8a53e44 100644 --- a/backend/schema/labels/routes.go +++ b/backend/schema/labels/routes.go @@ -65,12 +65,12 @@ func (pg *PgModel) CreateNewLabel(c *gin.Context) { // @description delete a label // @tags labels // -// @param :gid path string true "Group to delete label from" -// @param :lname path string true "Name of label to delete" +// @param gid path string true "Group to delete label from" +// @param lname path string true "Name of label to delete" // // @success 200 {object} models.Label // @failure 400 {object} string -// @router /labels/delete/{:gid}/{:lname} [DELETE] +// @router /labels/delete/{gid}/{lname} [DELETE] func (pg *PgModel) DeleteLabel(c *gin.Context) { group_id := c.Param("gid") label_name := c.Param("lname") @@ -90,13 +90,13 @@ func (pg *PgModel) DeleteLabel(c *gin.Context) { // @description edit a label // @tags labels // -// @param :gid path string true "Group of label to edit" -// @param :lname path string true "Name of label to edit" +// @param gid path string true "Group of label to edit" +// @param lname path string true "Name of label to edit" // @param _ body LabelData true "Label edit data" // // @success 200 {object} models.Label // @failure 400 {object} string -// @router /labels/edit/{:gid}/{:lname} [PATCH] +// @router /labels/edit/{gid}/{lname} [PATCH] func (pg *PgModel) EditLabel(c *gin.Context) { group_id := c.Param("gid") label_name := c.Param("lname") From 01919d349234e629366147bd053caaba51cfdedd Mon Sep 17 00:00:00 2001 From: oliviaseds Date: Wed, 14 Feb 2024 16:21:58 -0500 Subject: [PATCH 27/29] refactor: implement inclusive data range filter --- backend/docs/docs.go | 5 +++++ backend/docs/swagger.json | 5 +++++ backend/docs/swagger.yaml | 3 +++ backend/schema/tasks/routes.go | 1 + backend/schema/tasks/task_test.go | 3 ++- backend/schema/tasks/transactions.go | 9 +++++---- 6 files changed, 21 insertions(+), 5 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 68843c2..58a8ddb 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -299,6 +299,11 @@ const docTemplate = `{ "name": "startDate", "in": "query" }, + { + "type": "string", + "name": "taskID", + "in": "query" + }, { "type": "string", "name": "taskStatus", diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index db93252..a741a2d 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -292,6 +292,11 @@ "name": "startDate", "in": "query" }, + { + "type": "string", + "name": "taskID", + "in": "query" + }, { "type": "string", "name": "taskStatus", diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 5c0bf92..32f6e74 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -344,6 +344,9 @@ paths: - in: query name: startDate type: string + - in: query + name: taskID + type: string - in: query name: taskStatus type: string diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index 5b8bf48..f24d619 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -26,6 +26,7 @@ func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { } type TaskQuery struct { + TaskID string `form:"taskID"` GroupID string `form:"groupID"` CreatedBy string `form:"createdBy"` TaskStatus string `form:"taskStatus"` diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 309d4f3..56e1459 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -71,13 +71,14 @@ func TestTaskGroup(t *testing.T) { if err != nil { t.Error("Failed to unmarshal json") } - + start_date_1 := time.Date(2024, 2, 10, 14, 30, 0, 0, time.UTC) expectedTasks := []models.Task{ { TaskID: 2, GroupID: 2, CreatedBy: "user3", CreatedDate: time.Date(2024, 2, 20, 23, 59, 59, 0, time.UTC), + StartDate: &start_date_1, TaskStatus: "INCOMPLETE", TaskType: "other", }, diff --git a/backend/schema/tasks/transactions.go b/backend/schema/tasks/transactions.go index 8be8e97..77f87b2 100644 --- a/backend/schema/tasks/transactions.go +++ b/backend/schema/tasks/transactions.go @@ -11,6 +11,7 @@ import ( func GetTasksByQueryFromDB(pool *pgx.Conn, filterQuery TaskQuery) ([]models.Task, error) { query_fields := []string{ + filterQuery.TaskID, filterQuery.GroupID, filterQuery.CreatedBy, filterQuery.TaskStatus, @@ -18,7 +19,7 @@ func GetTasksByQueryFromDB(pool *pgx.Conn, filterQuery TaskQuery) ([]models.Task filterQuery.StartDate, filterQuery.EndDate} - field_names := []string{"group_id", "created_by", "task_status", "task_type", "start_date", "end_date"} + field_names := []string{"task_id =", "group_id =", "created_by =", "task_status =", "task_type =", "start_date >=", "end_date <="} var query string var args []interface{} @@ -27,12 +28,12 @@ func GetTasksByQueryFromDB(pool *pgx.Conn, filterQuery TaskQuery) ([]models.Task if query != "" { query += " AND " } - query += fmt.Sprintf("%s = $%d", field_names[i], len(args)+1) + query += fmt.Sprintf("%s $%d", field_names[i], len(args)+1) args = append(args, field) } } - rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, task_status, task_type FROM task WHERE "+query, args...) + rows, err := pool.Query("SELECT task_id, group_id, created_by, created_date, start_date, end_date, task_status, task_type FROM task WHERE "+query, args...) if err != nil { print(err, "error selecting tasks by query") @@ -45,7 +46,7 @@ func GetTasksByQueryFromDB(pool *pgx.Conn, filterQuery TaskQuery) ([]models.Task for rows.Next() { task := models.Task{} - err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.TaskStatus, &task.TaskType) + err := rows.Scan(&task.TaskID, &task.GroupID, &task.CreatedBy, &task.CreatedDate, &task.StartDate, &task.EndDate, &task.TaskStatus, &task.TaskType) if err != nil { print(err, "error scanning tasks by query") From e735054b8597c877494d446b0f4d527a119b908a Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Fri, 16 Feb 2024 18:11:52 -0500 Subject: [PATCH 28/29] refactor: keep consistency between route groups --- backend/db/migrations/init.sql | 3 +- backend/docs/docs.go | 241 +++++++++++++++--- backend/docs/swagger.json | 241 +++++++++++++++--- backend/docs/swagger.yaml | 206 +++++++++++---- backend/main.go | 16 +- backend/models/task_label.go | 7 + .../schema/group-roles/group_roles_test.go | 6 +- backend/schema/group-roles/routes.go | 2 +- backend/schema/groups/groups_test.go | 6 +- backend/schema/groups/routes.go | 2 +- backend/schema/labels/label_test.go | 48 +++- backend/schema/labels/routes.go | 60 +++-- backend/schema/labels/transactions.go | 32 ++- backend/schema/task_labels/routes.go | 111 ++++++++ .../schema/task_labels/task_labels_test.go | 124 +++++++++ backend/schema/task_labels/transactions.go | 58 +++++ backend/schema/tasks/routes.go | 2 +- backend/schema/tasks/task_test.go | 2 +- 18 files changed, 1014 insertions(+), 153 deletions(-) create mode 100644 backend/models/task_label.go create mode 100644 backend/schema/task_labels/routes.go create mode 100644 backend/schema/task_labels/task_labels_test.go create mode 100644 backend/schema/task_labels/transactions.go diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql index e2a77a1..b7d4f46 100644 --- a/backend/db/migrations/init.sql +++ b/backend/db/migrations/init.sql @@ -175,7 +175,8 @@ VALUES (1, 'Medication', 'blue'), (2, 'Appointments', 'green'), (3, 'Financial', 'orange'), - (4, 'Household', 'purple') + (4, 'Household', 'purple'), + (1, 'Household', 'purple') ; INSERT INTO task_labels (task_id, group_id, label_name) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index d7ed995..bd3c031 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -192,17 +192,17 @@ const docTemplate = `{ } } }, - "/group/{groupId}/roles": { + "/group/{groupId}/labels": { "get": { - "description": "get all group members for a group given group id from the db", + "description": "get all labels for a group given their group id", "tags": [ - "group" + "labels" ], - "summary": "Get all members of a group", + "summary": "get labels for a group", "parameters": [ { "type": "integer", - "description": "group id", + "description": "the group id to get labels for", "name": "groupId", "in": "path", "required": true @@ -214,14 +214,59 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.GroupRole" + "$ref": "#/definitions/models.Label" } } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "create a new label for a group", + "tags": [ + "labels" + ], + "summary": "Create A New Label", + "parameters": [ + { + "type": "string", + "description": "Group to create label for", + "name": "groupId", + "in": "path", + "required": true + }, + { + "description": "Label creation data", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/labels.LabelData" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Label" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } } } } }, - "/labels/delete/{gid}/{lname}": { + "/group/{groupId}/labels/{lname}": { "delete": { "description": "delete a label", "tags": [ @@ -232,7 +277,7 @@ const docTemplate = `{ { "type": "string", "description": "Group to delete label from", - "name": "gid", + "name": "groupId", "in": "path", "required": true }, @@ -258,9 +303,7 @@ const docTemplate = `{ } } } - } - }, - "/labels/edit/{gid}/{lname}": { + }, "patch": { "description": "edit a label", "tags": [ @@ -271,7 +314,7 @@ const docTemplate = `{ { "type": "string", "description": "Group of label to edit", - "name": "gid", + "name": "groupId", "in": "path", "required": true }, @@ -308,35 +351,30 @@ const docTemplate = `{ } } }, - "/labels/new": { - "post": { - "description": "create a new label for a group", + "/group/{groupId}/roles": { + "get": { + "description": "get all group members for a group given group id from the db", "tags": [ - "labels" + "group" ], - "summary": "Create A New Label", + "summary": "Get all members of a group", "parameters": [ { - "description": "Label creation data", - "name": "_", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/labels.LabelData" - } + "type": "integer", + "description": "group id", + "name": "groupId", + "in": "path", + "required": true } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.Label" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" + "type": "array", + "items": { + "$ref": "#/definitions/models.GroupRole" + } } } } @@ -538,6 +576,119 @@ const docTemplate = `{ } } }, + "/tasks/{tid}/labels": { + "get": { + "description": "get a tasks labels given the task id", + "tags": [ + "task labels" + ], + "summary": "get a tasks labels", + "parameters": [ + { + "type": "string", + "description": "the task id to get labels for", + "name": "tid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task_Label" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "add a label to a task given the task id, group id, and label name", + "tags": [ + "task labels" + ], + "summary": "add a label to a task", + "parameters": [ + { + "type": "integer", + "description": "the task id to add the label to", + "name": "tid", + "in": "path", + "required": true + }, + { + "description": "The label data to add to the task", + "name": "requestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/task_labels.LabelData" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Task_Label" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "remove a label from a task given the task id, group id, and label name", + "tags": [ + "task labels" + ], + "summary": "remove a label from a task", + "parameters": [ + { + "type": "integer", + "description": "the task id to get labels for", + "name": "tid", + "in": "path", + "required": true + }, + { + "description": "The label data to remove from the task", + "name": "requestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/task_labels.LabelData" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/tasks/{tid}/remove": { "delete": { "description": "remove users from task", @@ -598,9 +749,6 @@ const docTemplate = `{ "labels.LabelData": { "type": "object", "properties": { - "group_id": { - "type": "integer" - }, "label_color": { "type": "string" }, @@ -757,6 +905,31 @@ const docTemplate = `{ } } }, + "models.Task_Label": { + "type": "object", + "properties": { + "group_id": { + "type": "integer" + }, + "label_name": { + "type": "string" + }, + "task_id": { + "type": "integer" + } + } + }, + "task_labels.LabelData": { + "type": "object", + "properties": { + "group_id": { + "type": "integer" + }, + "label_name": { + "type": "string" + } + } + }, "tasks.Assignment": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 9137b85..c1a750e 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -185,17 +185,17 @@ } } }, - "/group/{groupId}/roles": { + "/group/{groupId}/labels": { "get": { - "description": "get all group members for a group given group id from the db", + "description": "get all labels for a group given their group id", "tags": [ - "group" + "labels" ], - "summary": "Get all members of a group", + "summary": "get labels for a group", "parameters": [ { "type": "integer", - "description": "group id", + "description": "the group id to get labels for", "name": "groupId", "in": "path", "required": true @@ -207,14 +207,59 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/models.GroupRole" + "$ref": "#/definitions/models.Label" } } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "create a new label for a group", + "tags": [ + "labels" + ], + "summary": "Create A New Label", + "parameters": [ + { + "type": "string", + "description": "Group to create label for", + "name": "groupId", + "in": "path", + "required": true + }, + { + "description": "Label creation data", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/labels.LabelData" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Label" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } } } } }, - "/labels/delete/{gid}/{lname}": { + "/group/{groupId}/labels/{lname}": { "delete": { "description": "delete a label", "tags": [ @@ -225,7 +270,7 @@ { "type": "string", "description": "Group to delete label from", - "name": "gid", + "name": "groupId", "in": "path", "required": true }, @@ -251,9 +296,7 @@ } } } - } - }, - "/labels/edit/{gid}/{lname}": { + }, "patch": { "description": "edit a label", "tags": [ @@ -264,7 +307,7 @@ { "type": "string", "description": "Group of label to edit", - "name": "gid", + "name": "groupId", "in": "path", "required": true }, @@ -301,35 +344,30 @@ } } }, - "/labels/new": { - "post": { - "description": "create a new label for a group", + "/group/{groupId}/roles": { + "get": { + "description": "get all group members for a group given group id from the db", "tags": [ - "labels" + "group" ], - "summary": "Create A New Label", + "summary": "Get all members of a group", "parameters": [ { - "description": "Label creation data", - "name": "_", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/labels.LabelData" - } + "type": "integer", + "description": "group id", + "name": "groupId", + "in": "path", + "required": true } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.Label" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "type": "string" + "type": "array", + "items": { + "$ref": "#/definitions/models.GroupRole" + } } } } @@ -531,6 +569,119 @@ } } }, + "/tasks/{tid}/labels": { + "get": { + "description": "get a tasks labels given the task id", + "tags": [ + "task labels" + ], + "summary": "get a tasks labels", + "parameters": [ + { + "type": "string", + "description": "the task id to get labels for", + "name": "tid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Task_Label" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "add a label to a task given the task id, group id, and label name", + "tags": [ + "task labels" + ], + "summary": "add a label to a task", + "parameters": [ + { + "type": "integer", + "description": "the task id to add the label to", + "name": "tid", + "in": "path", + "required": true + }, + { + "description": "The label data to add to the task", + "name": "requestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/task_labels.LabelData" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Task_Label" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "remove a label from a task given the task id, group id, and label name", + "tags": [ + "task labels" + ], + "summary": "remove a label from a task", + "parameters": [ + { + "type": "integer", + "description": "the task id to get labels for", + "name": "tid", + "in": "path", + "required": true + }, + { + "description": "The label data to remove from the task", + "name": "requestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/task_labels.LabelData" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } + } + }, "/tasks/{tid}/remove": { "delete": { "description": "remove users from task", @@ -591,9 +742,6 @@ "labels.LabelData": { "type": "object", "properties": { - "group_id": { - "type": "integer" - }, "label_color": { "type": "string" }, @@ -750,6 +898,31 @@ } } }, + "models.Task_Label": { + "type": "object", + "properties": { + "group_id": { + "type": "integer" + }, + "label_name": { + "type": "string" + }, + "task_id": { + "type": "integer" + } + } + }, + "task_labels.LabelData": { + "type": "object", + "properties": { + "group_id": { + "type": "integer" + }, + "label_name": { + "type": "string" + } + } + }, "tasks.Assignment": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 753f158..fb72110 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -9,8 +9,6 @@ definitions: type: object labels.LabelData: properties: - group_id: - type: integer label_color: type: string label_name: @@ -107,6 +105,15 @@ definitions: task_type: type: string type: object + models.Task_Label: + properties: + group_id: + type: integer + label_name: + type: string + task_id: + type: integer + type: object models.TaskUser: properties: taskID: @@ -114,6 +121,13 @@ definitions: userID: type: string type: object + task_labels.LabelData: + properties: + group_id: + type: integer + label_name: + type: string + type: object tasks.Assignment: properties: assigner: @@ -215,11 +229,11 @@ paths: summary: Adds a user to a care group tags: - group - /group/{groupId}/roles: + /group/{groupId}/labels: get: - description: get all group members for a group given group id from the db + description: get all labels for a group given their group id parameters: - - description: group id + - description: the group id to get labels for in: path name: groupId required: true @@ -229,56 +243,48 @@ paths: description: OK schema: items: - $ref: '#/definitions/models.GroupRole' + $ref: '#/definitions/models.Label' type: array - summary: Get all members of a group + "400": + description: Bad Request + schema: + type: string + summary: get labels for a group tags: - - group - /group/create/{groupName}: + - labels post: - description: Creates a new care group with the provided group name. + description: create a new label for a group parameters: - - description: group name + - description: Group to create label for in: path - name: groupName + name: groupId required: true type: string - responses: - "200": - description: OK - schema: - type: integer - summary: Creates a care group - tags: - - group - /group/member/{uid}: - get: - description: get the group id from the user id - parameters: - - description: user id - in: path - name: uid + - description: Label creation data + in: body + name: _ required: true - type: string + schema: + $ref: '#/definitions/labels.LabelData' responses: "200": description: OK schema: - type: string + $ref: '#/definitions/models.Label' "400": description: Bad Request schema: type: string - summary: Retrieve a group id given a user id + summary: Create A New Label tags: - - group - /labels/delete/{gid}/{lname}: + - labels + /group/{groupId}/labels/{lname}: delete: description: delete a label parameters: - description: Group to delete label from in: path - name: gid + name: groupId required: true type: string - description: Name of label to delete @@ -298,13 +304,12 @@ paths: summary: Delete A Label tags: - labels - /labels/edit/{gid}/{lname}: patch: description: edit a label parameters: - description: Group of label to edit in: path - name: gid + name: groupId required: true type: string - description: Name of label to edit @@ -330,28 +335,63 @@ paths: summary: Edit A Label tags: - labels - /labels/new: + /group/{groupId}/roles: + get: + description: get all group members for a group given group id from the db + parameters: + - description: group id + in: path + name: groupId + required: true + type: integer + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.GroupRole' + type: array + summary: Get all members of a group + tags: + - group + /group/create/{groupName}: post: - description: create a new label for a group + description: Creates a new care group with the provided group name. parameters: - - description: Label creation data - in: body - name: _ + - description: group name + in: path + name: groupName required: true - schema: - $ref: '#/definitions/labels.LabelData' + type: string responses: "200": description: OK schema: - $ref: '#/definitions/models.Label' + type: integer + summary: Creates a care group + tags: + - group + /group/member/{uid}: + get: + description: get the group id from the user id + parameters: + - description: user id + in: path + name: uid + required: true + type: string + responses: + "200": + description: OK + schema: + type: string "400": description: Bad Request schema: type: string - summary: Create A New Label + summary: Retrieve a group id given a user id tags: - - labels + - group /medications: get: description: get all user medications @@ -415,6 +455,82 @@ paths: summary: Assign Users To Task tags: - tasks + /tasks/{tid}/labels: + delete: + description: remove a label from a task given the task id, group id, and label + name + parameters: + - description: the task id to get labels for + in: path + name: tid + required: true + type: integer + - description: The label data to remove from the task + in: body + name: requestBody + required: true + schema: + $ref: '#/definitions/task_labels.LabelData' + responses: + "200": + description: OK + schema: + type: string + "400": + description: Bad Request + schema: + type: string + summary: remove a label from a task + tags: + - task labels + get: + description: get a tasks labels given the task id + parameters: + - description: the task id to get labels for + in: path + name: tid + required: true + type: string + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Task_Label' + type: array + "400": + description: Bad Request + schema: + type: string + summary: get a tasks labels + tags: + - task labels + post: + description: add a label to a task given the task id, group id, and label name + parameters: + - description: the task id to add the label to + in: path + name: tid + required: true + type: integer + - description: The label data to add to the task + in: body + name: requestBody + required: true + schema: + $ref: '#/definitions/task_labels.LabelData' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Task_Label' + "400": + description: Bad Request + schema: + type: string + summary: add a label to a task + tags: + - task labels /tasks/{tid}/remove: delete: description: remove users from task diff --git a/backend/main.go b/backend/main.go index 5b7581b..4b25ed5 100644 --- a/backend/main.go +++ b/backend/main.go @@ -9,6 +9,7 @@ import ( "carewallet/schema/groups" "carewallet/schema/labels" "carewallet/schema/medication" + "carewallet/schema/task_labels" "carewallet/schema/tasks" "fmt" "os" @@ -42,14 +43,21 @@ func main() { v1 := r.Group("/") { medication.GetMedicationGroup(v1, &medication.PgModel{Conn: conn}) + files.FileGroup(v1, &files.PgModel{Conn: conn}) + group := v1.Group("group") { - groups.GetCareGroups(group, &groups.PgModel{Conn: conn}) - groupRoles.GetGroupRolesGroup(group, &groupRoles.PgModel{Conn: conn}) + groups.CareGroups(group, &groups.PgModel{Conn: conn}) + groupRoles.GroupRolesGroup(group, &groupRoles.PgModel{Conn: conn}) + labels.LabelGroup(group, &labels.PgModel{Conn: conn}) + } + + task := v1.Group("tasks") + { + tasks.TaskGroup(task, &tasks.PgModel{Conn: conn}) + task_labels.TaskGroup(task, &task_labels.PgModel{Conn: conn}) } - tasks.TaskGroup(v1, &tasks.PgModel{Conn: conn}) - labels.LabelGroup(v1, &labels.PgModel{Conn: conn}) } if enviroment == configuration.EnvironmentLocal { diff --git a/backend/models/task_label.go b/backend/models/task_label.go new file mode 100644 index 0000000..9d40f18 --- /dev/null +++ b/backend/models/task_label.go @@ -0,0 +1,7 @@ +package models + +type Task_Label struct { + TaskId int `json:"task_id"` + GroupId int `json:"group_id"` + LabelName string `json:"label_name"` +} diff --git a/backend/schema/group-roles/group_roles_test.go b/backend/schema/group-roles/group_roles_test.go index f3c8bbe..b23783d 100644 --- a/backend/schema/group-roles/group_roles_test.go +++ b/backend/schema/group-roles/group_roles_test.go @@ -37,12 +37,12 @@ func TestGetGroupRoles(t *testing.T) { v1 := router.Group("/group") { - GetGroupRolesGroup(v1, &controller) + GroupRolesGroup(v1, &controller) } t.Run("TestGetGroupRoles", func(t *testing.T) { w := httptest.NewRecorder() - req, _ := http.NewRequest("GET", "/group/member/user123", nil) + req, _ := http.NewRequest("GET", "/group/member/user1", nil) router.ServeHTTP(w, req) // Check for HTTP Status OK (200) @@ -58,7 +58,7 @@ func TestGetGroupRoles(t *testing.T) { } // Define the expected group - expectedGroup := models.GroupRole{GroupID: 1, Role: "PATIENT", UserID: "user123"} + expectedGroup := models.GroupRole{GroupID: 1, Role: "PATIENT", UserID: "user1"} if expectedGroup != responseGroup { t.Errorf("Expected group ID: %+v, Actual group ID: %+v", expectedGroup, responseGroup) diff --git a/backend/schema/group-roles/routes.go b/backend/schema/group-roles/routes.go index 3b2178a..3138d22 100644 --- a/backend/schema/group-roles/routes.go +++ b/backend/schema/group-roles/routes.go @@ -13,7 +13,7 @@ type PgModel struct { } // groupRoles.go file -func GetGroupRolesGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { +func GroupRolesGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { groupRoles := v1.Group("") { groupRoles.GET("/:groupId/roles", c.GetGroupRoles) diff --git a/backend/schema/groups/groups_test.go b/backend/schema/groups/groups_test.go index bbce5c9..75bc565 100644 --- a/backend/schema/groups/groups_test.go +++ b/backend/schema/groups/groups_test.go @@ -39,7 +39,7 @@ func TestGroupRoutes(t *testing.T) { v1 := router.Group("/group") { - GetCareGroups(v1, &controller) + CareGroups(v1, &controller) } // test to get group members @@ -89,7 +89,7 @@ func TestGroupRoutes(t *testing.T) { } // Define the expected users - expectedGroupID := 3 + expectedGroupID := 6 if expectedGroupID != responseGroupID { t.Error("Result was not correct") @@ -100,7 +100,7 @@ func TestGroupRoutes(t *testing.T) { // test to add a user to a group t.Run("TestAddUser", func(t *testing.T) { postRequest := GroupMember{ - UserId: "user123", + UserId: "user3", Role: "PATIENT", } diff --git a/backend/schema/groups/routes.go b/backend/schema/groups/routes.go index 63303b4..0659c15 100644 --- a/backend/schema/groups/routes.go +++ b/backend/schema/groups/routes.go @@ -13,7 +13,7 @@ type PgModel struct { Conn *pgx.Conn } -func GetCareGroups(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { +func CareGroups(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { careGroups := v1.Group("") { careGroups.POST("/create/:groupName", c.CreateCareGroups) diff --git a/backend/schema/labels/label_test.go b/backend/schema/labels/label_test.go index c504ec5..72cf287 100644 --- a/backend/schema/labels/label_test.go +++ b/backend/schema/labels/label_test.go @@ -32,14 +32,49 @@ func TestLabelGroup(t *testing.T) { router := gin.Default() router.Use(cors.Default()) - v1 := router.Group("/") + v1 := router.Group("/group") { LabelGroup(v1, &controller) } + t.Run("TestGetLabelsByGroup", func(t *testing.T) { + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/group/1/labels", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to get labels by group.") + } + + var getResponse []models.Label + err = json.Unmarshal(w.Body.Bytes(), &getResponse) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + expectedResponse := []models.Label{ + { + GroupID: 1, + LabelName: "Medication", + LabelColor: "blue", + }, + { + GroupID: 1, + LabelName: "Household", + LabelColor: "purple", + }, + } + + if !reflect.DeepEqual(expectedResponse, getResponse) { + t.Error("Result was not correct") + } + + }) + t.Run("TestCreateNewLabel", func(t *testing.T) { postRequest := LabelData{ - GroupID: 1, LabelName: "Office", LabelColor: "Orange", } @@ -50,7 +85,7 @@ func TestLabelGroup(t *testing.T) { } w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/labels/new", bytes.NewBuffer(requestJSON)) + req, _ := http.NewRequest("POST", "/group/2/labels", bytes.NewBuffer(requestJSON)) router.ServeHTTP(w, req) if http.StatusOK != w.Code { @@ -65,7 +100,7 @@ func TestLabelGroup(t *testing.T) { } expectedResponse := models.Label{ - GroupID: 1, + GroupID: 2, LabelName: "Office", LabelColor: "Orange", } @@ -77,7 +112,7 @@ func TestLabelGroup(t *testing.T) { t.Run("TestDeleteLabel", func(t *testing.T) { w := httptest.NewRecorder() - req, _ := http.NewRequest("DELETE", "/labels/delete/2/Appointment", nil) + req, _ := http.NewRequest("DELETE", "/group/2/labels/Appointment", nil) router.ServeHTTP(w, req) if http.StatusOK != w.Code { @@ -87,7 +122,6 @@ func TestLabelGroup(t *testing.T) { t.Run("TestEditLabel", func(t *testing.T) { postRequest := LabelData{ - GroupID: 4, LabelName: "Family", LabelColor: "Yellow", } @@ -98,7 +132,7 @@ func TestLabelGroup(t *testing.T) { } w := httptest.NewRecorder() - req, _ := http.NewRequest("PATCH", "/labels/edit/4/Household", bytes.NewBuffer(requestJSON)) + req, _ := http.NewRequest("PATCH", "/group/4/labels/Household", bytes.NewBuffer(requestJSON)) router.ServeHTTP(w, req) if http.StatusOK != w.Code { diff --git a/backend/schema/labels/routes.go b/backend/schema/labels/routes.go index 8a53e44..6e608ad 100644 --- a/backend/schema/labels/routes.go +++ b/backend/schema/labels/routes.go @@ -3,6 +3,7 @@ package labels import ( "fmt" "net/http" + "strconv" "github.com/gin-gonic/gin" "github.com/jackc/pgx" @@ -14,18 +15,41 @@ type PgModel struct { func LabelGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { - labels := v1.Group("labels") + labels := v1.Group(":groupId/labels") { - labels.POST("/new", c.CreateNewLabel) - labels.DELETE("/delete/:gid/:lname", c.DeleteLabel) - labels.PATCH("/edit/:gid/:lname", c.EditLabel) + labels.POST("", c.CreateNewLabel) + labels.GET("", c.GetLabelsByGroup) + labels.DELETE(":lname", c.DeleteLabel) + labels.PATCH(":lname", c.EditLabel) } return labels } +// GetLabelsByGroup godoc +// +// @summary get labels for a group +// @description get all labels for a group given their group id +// @tags labels +// +// @param groupId path int true "the group id to get labels for" +// +// @success 200 {array} models.Label +// @failure 400 {object} string +// @router /group/{groupId}/labels [GET] +func (pg *PgModel) GetLabelsByGroup(c *gin.Context) { + group_id := c.Param("groupId") + + labels, err := GetLabelsByGroupFromDB(pg.Conn, group_id) + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, labels) +} + type LabelData struct { - GroupID int `json:"group_id"` LabelName string `json:"label_name"` LabelColor string `json:"label_color"` } @@ -36,13 +60,15 @@ type LabelData struct { // @description create a new label for a group // @tags labels // -// @param _ body LabelData true "Label creation data" +// @param groupId path string true "Group to create label for" +// @param _ body LabelData true "Label creation data" // -// @success 200 {object} models.Label -// @failure 400 {object} string -// @router /labels/new [post] +// @success 200 {object} models.Label +// @failure 400 {object} string +// @router /group/{groupId}/labels [POST] func (pg *PgModel) CreateNewLabel(c *gin.Context) { var requestBody LabelData + group_id := c.Param("groupId") if err := c.BindJSON(&requestBody); err != nil { fmt.Println("Error binding JSON: ", err.Error()) @@ -50,7 +76,9 @@ func (pg *PgModel) CreateNewLabel(c *gin.Context) { return } - label, err := CreateNewLabelInDB(pg.Conn, requestBody) + id, _ := strconv.Atoi(group_id) + + label, err := CreateNewLabelInDB(pg.Conn, id, requestBody) if err != nil { c.JSON(http.StatusBadRequest, err.Error()) return @@ -65,14 +93,14 @@ func (pg *PgModel) CreateNewLabel(c *gin.Context) { // @description delete a label // @tags labels // -// @param gid path string true "Group to delete label from" +// @param groupId path string true "Group to delete label from" // @param lname path string true "Name of label to delete" // // @success 200 {object} models.Label // @failure 400 {object} string -// @router /labels/delete/{gid}/{lname} [DELETE] +// @router /group/{groupId}/labels/{lname} [DELETE] func (pg *PgModel) DeleteLabel(c *gin.Context) { - group_id := c.Param("gid") + group_id := c.Param("groupId") label_name := c.Param("lname") err := DeleteLabelFromDB(pg.Conn, group_id, label_name) @@ -90,15 +118,15 @@ func (pg *PgModel) DeleteLabel(c *gin.Context) { // @description edit a label // @tags labels // -// @param gid path string true "Group of label to edit" +// @param groupId path string true "Group of label to edit" // @param lname path string true "Name of label to edit" // @param _ body LabelData true "Label edit data" // // @success 200 {object} models.Label // @failure 400 {object} string -// @router /labels/edit/{gid}/{lname} [PATCH] +// @router /group/{groupId}/labels/{lname} [PATCH] func (pg *PgModel) EditLabel(c *gin.Context) { - group_id := c.Param("gid") + group_id := c.Param("groupId") label_name := c.Param("lname") var requestBody LabelData diff --git a/backend/schema/labels/transactions.go b/backend/schema/labels/transactions.go index 7068842..7648c0c 100644 --- a/backend/schema/labels/transactions.go +++ b/backend/schema/labels/transactions.go @@ -7,8 +7,36 @@ import ( "github.com/jackc/pgx" ) -func CreateNewLabelInDB(pool *pgx.Conn, requestBody LabelData) (models.Label, error) { - groupID := requestBody.GroupID +func GetLabelsByGroupFromDB(pool *pgx.Conn, groupID string) ([]models.Label, error) { + groupIDInt, err := strconv.Atoi(groupID) + if err != nil { + return nil, err + } + + rows, err := pool.Query("SELECT label_name, label_color FROM label WHERE group_id = $1", groupIDInt) + if err != nil { + return nil, err + } + + defer rows.Close() + + var results []models.Label + + for rows.Next() { + label := models.Label{} + err := rows.Scan(&label.LabelName, &label.LabelColor) + if err != nil { + return nil, err + } + label.GroupID = groupIDInt + results = append(results, label) + } + + return results, nil + +} + +func CreateNewLabelInDB(pool *pgx.Conn, groupID int, requestBody LabelData) (models.Label, error) { labelName := requestBody.LabelName labelColor := requestBody.LabelColor diff --git a/backend/schema/task_labels/routes.go b/backend/schema/task_labels/routes.go new file mode 100644 index 0000000..79e7b17 --- /dev/null +++ b/backend/schema/task_labels/routes.go @@ -0,0 +1,111 @@ +package task_labels + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/jackc/pgx" +) + +type PgModel struct { + Conn *pgx.Conn +} + +func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { + + tasks := v1.Group(":tid/labels") + { + tasks.POST("", c.AddLabelToTask) + tasks.DELETE("", c.RemoveLabelFromTask) + tasks.GET("", c.GetLabelsByTask) + } + + return tasks +} + +// GetLabelsByTask godoc +// +// @summary get a tasks labels +// @description get a tasks labels given the task id +// @tags task labels +// +// @param tid path string true "the task id to get labels for" +// +// @success 200 {array} models.Task_Label +// @failure 400 {object} string +// @router /tasks/{tid}/labels [GET] +func (pg *PgModel) GetLabelsByTask(c *gin.Context) { + taskLabels, err := GetLabelsByTaskInDB(pg.Conn, c.Param("tid")) + + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, taskLabels) +} + +type LabelData struct { + GroupID int `json:"group_id"` + LabelName string `json:"label_name"` +} + +// AddLabelToTask godoc +// +// @summary add a label to a task +// @description add a label to a task given the task id, group id, and label name +// @tags task labels +// +// @param tid path int true "the task id to add the label to" +// @param requestBody body LabelData true "The label data to add to the task" +// +// @success 200 {object} models.Task_Label +// @failure 400 {object} string +// @router /tasks/{tid}/labels [POST] +func (pg *PgModel) AddLabelToTask(c *gin.Context) { + var requestBody LabelData + + if err := c.BindJSON(&requestBody); err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + updatedTaskLabel, err := AddLabelToTaskInDB(pg.Conn, requestBody, c.Param("tid")) + + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, updatedTaskLabel) +} + +// RemoveLabelFromTask godoc +// +// @summary remove a label from a task +// @description remove a label from a task given the task id, group id, and label name +// @tags task labels +// +// @param tid path int true "the task id to get labels for" +// @param requestBody body LabelData true "The label data to remove from the task" +// +// @success 200 {object} string +// @failure 400 {object} string +// @router /tasks/{tid}/labels [DELETE] +func (pg *PgModel) RemoveLabelFromTask(c *gin.Context) { + var requestBody LabelData + + if err := c.BindJSON(&requestBody); err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + err := RemoveLabelFromTaskInDB(pg.Conn, requestBody, c.Param("tid")) + + if err != nil { + c.JSON(http.StatusBadRequest, err.Error()) + return + } + + c.JSON(http.StatusOK, "") +} diff --git a/backend/schema/task_labels/task_labels_test.go b/backend/schema/task_labels/task_labels_test.go new file mode 100644 index 0000000..27bcdae --- /dev/null +++ b/backend/schema/task_labels/task_labels_test.go @@ -0,0 +1,124 @@ +package task_labels + +import ( + "bytes" + "carewallet/configuration" + "carewallet/db" + "carewallet/models" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "reflect" + "testing" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +func TestTaskLabelsGroup(t *testing.T) { + config, err := configuration.GetConfiguration() + + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to retreive configuration file: %v\n", err) + os.Exit(1) + } + + conn := db.ConnectPosgresDatabase(config) + defer conn.Close() + + controller := PgModel{Conn: conn} + router := gin.Default() + router.Use(cors.Default()) + + v1 := router.Group("/tasks") + { + TaskGroup(v1, &controller) + } + + t.Run("TestGetTaskLabels", func(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/tasks/1/labels", nil) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to get labels by task.") + } + + var getResponse []LabelData + err = json.Unmarshal(w.Body.Bytes(), &getResponse) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + expectedResponse := []LabelData{ + { + GroupID: 1, + LabelName: "Medication", + }, + } + + if !reflect.DeepEqual(expectedResponse, getResponse) { + t.Error("Failed to get the expected response") + } + }) + + t.Run("TestAddTaskLabels", func(t *testing.T) { + postRequest := LabelData{ + GroupID: 1, + LabelName: "Household", + } + + requestJSON, err := json.Marshal(postRequest) + if err != nil { + t.Error("Failed to marshal remove request to JSON") + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/tasks/1/labels", bytes.NewBuffer(requestJSON)) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to assign new label.") + } + + var postResponse models.Task_Label + err = json.Unmarshal(w.Body.Bytes(), &postResponse) + + if err != nil { + t.Error("Failed to unmarshal json") + } + + expectedResponse := models.Task_Label{ + GroupId: 1, + TaskId: 1, + LabelName: "Household", + } + + if !reflect.DeepEqual(expectedResponse, postResponse) { + t.Error("Result was not correct") + } + }) + + t.Run("TestRemoveTaskLabels", func(t *testing.T) { + postRequest := LabelData{ + GroupID: 1, + LabelName: "Medication", + } + + requestJSON, err := json.Marshal(postRequest) + if err != nil { + t.Error("Failed to marshal remove request to JSON") + } + + w := httptest.NewRecorder() + req, _ := http.NewRequest("DELETE", "/tasks/1/labels", bytes.NewBuffer(requestJSON)) + router.ServeHTTP(w, req) + + if http.StatusOK != w.Code { + t.Error("Failed to remove label.") + } + }) +} diff --git a/backend/schema/task_labels/transactions.go b/backend/schema/task_labels/transactions.go new file mode 100644 index 0000000..0864ed4 --- /dev/null +++ b/backend/schema/task_labels/transactions.go @@ -0,0 +1,58 @@ +package task_labels + +import ( + "carewallet/models" + + "github.com/jackc/pgx" +) + +func GetLabelsByTaskInDB(conn *pgx.Conn, taskId string) ([]models.Task_Label, error) { + rows, err := conn.Query("SELECT * FROM task_labels WHERE task_id = $1", taskId) + + if err != nil { + print(err, "error selecting tasks by query") + return nil, err + } + + defer rows.Close() + + var results []models.Task_Label + + for rows.Next() { + task := models.Task_Label{} + err := rows.Scan(&task.GroupId, &task.TaskId, &task.LabelName) + + if err != nil { + print(err, "error scanning tasks by query") + return nil, err + } + + results = append(results, task) + } + + return results, nil +} + +func AddLabelToTaskInDB(conn *pgx.Conn, requestBody LabelData, taskid string) (models.Task_Label, error) { + var task_label models.Task_Label + err := conn.QueryRow("INSERT INTO task_labels (task_id, group_id, label_name) VALUES ($1, $2, $3) RETURNING *;", + taskid, requestBody.GroupID, requestBody.LabelName).Scan(&task_label.TaskId, &task_label.GroupId, &task_label.LabelName) + + if err != nil { + print(err.Error()) + return models.Task_Label{}, err + } + + return task_label, nil +} + +func RemoveLabelFromTaskInDB(conn *pgx.Conn, requestBody LabelData, taskId string) error { + _, err := conn.Exec("DELETE FROM task_labels WHERE task_id = $1 AND group_id = $2 AND label_name = $3", taskId, requestBody.GroupID, requestBody.LabelName) + + if err != nil { + print(err.Error()) + return err + } + + return nil +} diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go index f24d619..6fef37a 100644 --- a/backend/schema/tasks/routes.go +++ b/backend/schema/tasks/routes.go @@ -14,7 +14,7 @@ type PgModel struct { func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { - tasks := v1.Group("tasks") + tasks := v1.Group("") { tasks.GET("/filtered", c.GetFilteredTasks) tasks.POST("/:tid/assign", c.AssignUsersToTask) diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go index 56e1459..c8785fe 100644 --- a/backend/schema/tasks/task_test.go +++ b/backend/schema/tasks/task_test.go @@ -34,7 +34,7 @@ func TestTaskGroup(t *testing.T) { router := gin.Default() router.Use(cors.Default()) - v1 := router.Group("/") + v1 := router.Group("/tasks") { TaskGroup(v1, &controller) } From af44908e4af5a0df873675b10ffd046800116b2c Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Fri, 16 Feb 2024 18:18:42 -0500 Subject: [PATCH 29/29] docs: fix a swagger doc return type --- backend/docs/docs.go | 2 +- backend/docs/swagger.json | 2 +- backend/docs/swagger.yaml | 2 +- backend/schema/labels/routes.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index bd3c031..6da6f32 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -293,7 +293,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.Label" + "type": "string" } }, "400": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index c1a750e..40f2229 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -286,7 +286,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.Label" + "type": "string" } }, "400": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index fb72110..2617a4b 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -296,7 +296,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/models.Label' + type: string "400": description: Bad Request schema: diff --git a/backend/schema/labels/routes.go b/backend/schema/labels/routes.go index 6e608ad..1ff93df 100644 --- a/backend/schema/labels/routes.go +++ b/backend/schema/labels/routes.go @@ -96,7 +96,7 @@ func (pg *PgModel) CreateNewLabel(c *gin.Context) { // @param groupId path string true "Group to delete label from" // @param lname path string true "Name of label to delete" // -// @success 200 {object} models.Label +// @success 200 {object} string // @failure 400 {object} string // @router /group/{groupId}/labels/{lname} [DELETE] func (pg *PgModel) DeleteLabel(c *gin.Context) {