diff --git a/.github/workflows/BackendCI.yml b/.github/workflows/BackendCI.yml
index 66c3ee5..716a60a 100644
--- a/.github/workflows/BackendCI.yml
+++ b/.github/workflows/BackendCI.yml
@@ -78,7 +78,10 @@ jobs:
uses: actions/checkout@v4
- name: Import DB seed data
- run: psql -d postgresql://user:pwd@172.17.0.1:5432/carewallet -f init.sql
+ run: |
+ for file in $(ls -1 ./ | sort); do
+ psql -d postgresql://user:pwd@172.17.0.1:5432/carewallet -f "$file"
+ done
working-directory: ./backend/db/migrations
- name: Set up Go
diff --git a/backend/db/migrations/1.user.sql b/backend/db/migrations/1.user.sql
new file mode 100644
index 0000000..51cab11
--- /dev/null
+++ b/backend/db/migrations/1.user.sql
@@ -0,0 +1,33 @@
+DROP TABLE IF EXISTS users;
+
+CREATE TABLE IF NOT EXISTS users (
+ user_id varchar NOT NULL UNIQUE,
+ first_name varchar NOT NULL,
+ last_name varchar NOT NULL,
+ email varchar NOT NULL,
+ phone varchar, --potentially make phone/address required (NOT NULL)
+ address varchar,
+ pfp_s3_url varchar, --for profile picture if we implement that
+ device_id varchar, --expoPushToken for push notifications
+ push_notification_enabled BOOLEAN DEFAULT FALSE,
+ PRIMARY KEY (user_id)
+);
+
+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'),
+
+ -- Care-Wallet Team
+ ('fIoFY26mJnYWH8sNdfuVoxpnVnr1', 'Matt', 'McCoy', 'mattcmccoy01@gmail.com', '', ''),
+ ('JamnX6TZf0dt6juozMRzNG5LMQd2', 'Andy', 'Cap', 'caplan.and@northeastern.edu', '', ''),
+ ('BLq3MXk4rVg4RKuYiMd7aEmMhsz1', 'Ansh', 'Patel', 'anshrpatel22@gmail.com', '', ''),
+ ('mPeo3d3MiXfnpPJADWgFD9ZcB2M2', 'Olivia', 'Sedarski', 'olivia@gmail.com', '', ''),
+ ('onrQs8HVGBVMPNz4Fk1uE94bSxg1', 'Danny', 'Rollo', 'dannyrollo4@gmail.com', '', ''),
+ ('8Sy7xBkGiGQv4ZKphcQfY8PxAqw1', 'Narayan', 'Sharma', 'sharma.na@northeastern.edu', '', ''),
+ ('iL7PnjS4axQffmlPceobjUUZ9DF2', 'Caitlin', 'Flynn', 'flynn.ca@northeastern.edu', '', ''),
+ ('5JgN2PQxCRM9VoCiiFPlQPNqkL32', 'Linwood', 'Blaisdell', 'blaisdell.l@northeastern.edu', '', '')
+ -- End Care-Wallet Team
+;
diff --git a/backend/db/migrations/2.group.sql b/backend/db/migrations/2.group.sql
new file mode 100644
index 0000000..a85d363
--- /dev/null
+++ b/backend/db/migrations/2.group.sql
@@ -0,0 +1,54 @@
+DROP TABLE IF EXISTS care_group;
+DROP TABLE IF EXISTS group_roles;
+
+CREATE TYPE role AS ENUM ('PATIENT', 'PRIMARY', 'SECONDARY');
+
+CREATE TABLE IF NOT EXISTS care_group (
+ group_id serial NOT NULL UNIQUE,
+ group_name varchar NOT NULL,
+ date_created timestamp NOT NULL, --do we default current time?
+ PRIMARY KEY (group_id)
+);
+
+CREATE TABLE IF NOT EXISTS group_roles (
+ group_id integer NOT NULL,
+ user_id varchar NOT NULL,
+ role role NOT NULL,
+ PRIMARY KEY (group_id, user_id),
+ FOREIGN KEY (group_id) REFERENCES care_group (group_id),
+ FOREIGN KEY (user_id) REFERENCES users (user_id)
+);
+
+INSERT INTO care_group (group_name, date_created)
+VALUES
+ ('Smith Family', NOW()),
+ ('Johnson Support Network', NOW()),
+ ('Williams Care Team', NOW()),
+ ('Brown Medical Group', NOW()),
+
+ -- Care-Wallet Team
+ ('We <3 Old People', NOW())
+ -- End Care-Wallet Team
+;
+
+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'),
+
+ -- Care-Wallet Team
+ (5, 'fIoFY26mJnYWH8sNdfuVoxpnVnr1', 'PRIMARY'),
+ (5, 'JamnX6TZf0dt6juozMRzNG5LMQd2', 'PRIMARY'),
+ (5, '5JgN2PQxCRM9VoCiiFPlQPNqkL32', 'PATIENT'),
+ (5, 'BLq3MXk4rVg4RKuYiMd7aEmMhsz1', 'SECONDARY'),
+ (5, 'mPeo3d3MiXfnpPJADWgFD9ZcB2M2', 'SECONDARY'),
+ (5, 'onrQs8HVGBVMPNz4Fk1uE94bSxg1', 'SECONDARY'),
+ (5, '8Sy7xBkGiGQv4ZKphcQfY8PxAqw1', 'SECONDARY'),
+ (5, 'iL7PnjS4axQffmlPceobjUUZ9DF2', 'SECONDARY')
+ -- End Care-Wallet Team
+;
diff --git a/backend/db/migrations/3.task.sql b/backend/db/migrations/3.task.sql
new file mode 100644
index 0000000..7da671b
--- /dev/null
+++ b/backend/db/migrations/3.task.sql
@@ -0,0 +1,55 @@
+DROP TABLE IF EXISTS task;
+DROP TABLE IF EXISTS task_assignees;
+DROP TABLE IF EXISTS task_labels;
+
+CREATE TYPE task_assignment_status AS ENUM ('ACCEPTED', 'DECLINED', 'NOTIFIED');
+CREATE TYPE task_status AS ENUM ('INCOMPLETE', 'COMPLETE', 'PARTIAL');
+CREATE TYPE task_type AS ENUM ('med_mgmt', 'dr_appt', 'financial', 'other');
+
+CREATE TABLE IF NOT EXISTS task (
+ task_id serial NOT NULL,
+ group_id integer NOT NULL,
+ created_by varchar NOT NULL,
+ created_date timestamp NOT NULL, -- add default val with current timestamp?
+ start_date timestamp,
+ end_date timestamp,
+ notes varchar,
+ repeating BOOLEAN DEFAULT FALSE,
+ repeating_interval varchar,
+ repeating_end_date timestamp,
+ task_status task_status NOT NULL,
+ task_type task_type NOT NULL, -- (eg. medication management, dr appointment, etc.)
+ task_info json,
+ PRIMARY KEY (task_id),
+ FOREIGN KEY (group_id) REFERENCES care_group (group_id),
+ FOREIGN KEY (created_by) REFERENCES users (user_id)
+);
+
+CREATE TABLE IF NOT EXISTS task_assignees (
+ task_id integer NOT NULL,
+ user_id varchar NOT NULL,
+ assignment_status task_assignment_status NOT NULL,
+ assigned_by varchar NOT NULL,
+ assigned_date timestamp NOT NULL, -- add default val with current timestamp?
+ last_notified timestamp,
+ PRIMARY KEY (task_id, user_id),
+ FOREIGN KEY (task_id) REFERENCES task (task_id),
+ FOREIGN KEY (user_id) REFERENCES users (user_id),
+ FOREIGN KEY (assigned_by) REFERENCES users (user_id)
+);
+
+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', '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')
+;
+
+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', 'DECLINED', 'user1', NOW())
+;
diff --git a/backend/db/migrations/4.label.sql b/backend/db/migrations/4.label.sql
new file mode 100644
index 0000000..944719b
--- /dev/null
+++ b/backend/db/migrations/4.label.sql
@@ -0,0 +1,35 @@
+ DROP TABLE IF EXISTS label;
+
+ CREATE TABLE If NOT EXISTS label (
+ group_id integer NOT NULL,
+ label_name varchar NOT NULL,
+ label_color varchar NOT NULL,
+ PRIMARY KEY (group_id, label_name),
+ FOREIGN KEY (group_id) REFERENCES care_group (group_id)
+);
+
+ CREATE TABLE If NOT EXISTS task_labels (
+ task_id integer NOT NULL,
+ group_id integer NOT NULL,
+ label_name varchar NOT NULL,
+ PRIMARY KEY (task_id, label_name),
+ 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
+);
+
+INSERT INTO label (group_id, label_name, label_color)
+VALUES
+ (1, 'Medication', 'blue'),
+ (2, 'Appointments', 'green'),
+ (3, 'Financial', 'orange'),
+ (4, 'Household', 'purple'),
+ (1, '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')
+;
diff --git a/backend/db/migrations/5.files.sql b/backend/db/migrations/5.files.sql
new file mode 100644
index 0000000..82b38cf
--- /dev/null
+++ b/backend/db/migrations/5.files.sql
@@ -0,0 +1,15 @@
+DROP TABLE IF EXISTS files;
+
+CREATE TABLE IF NOT EXISTS files (
+ file_id serial NOT NULL UNIQUE,
+ file_name varchar NOT NULL,
+ group_id integer NOT NULL,
+ upload_by varchar NOT NULL,
+ upload_date timestamp,
+ file_size integer NOT NULL,
+ task_id integer,
+ PRIMARY KEY (file_id),
+ FOREIGN KEY (group_id) REFERENCES care_group (group_id),
+ FOREIGN KEY (upload_by) REFERENCES users (user_id),
+ FOREIGN KEY (task_id) REFERENCES task (task_id)
+);
diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql
index b7d4f46..f664cfc 100644
--- a/backend/db/migrations/init.sql
+++ b/backend/db/migrations/init.sql
@@ -1,17 +1,4 @@
DROP TABLE IF EXISTS medication;
-DROP TABLE IF EXISTS care_group;
-DROP TABLE IF EXISTS users;
-DROP TABLE IF EXISTS group_roles;
-DROP TABLE IF EXISTS task;
-DROP TABLE IF EXISTS task_assignees;
-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');
-CREATE TYPE task_type AS ENUM ('med_mgmt', 'dr_appt', 'financial', 'other');
CREATE TABLE IF NOT EXISTS medication (
medication_id integer NOT NULL UNIQUE,
@@ -19,102 +6,6 @@ CREATE TABLE IF NOT EXISTS medication (
PRIMARY KEY (medication_id)
);
-
-CREATE TABLE IF NOT EXISTS care_group (
- group_id serial NOT NULL UNIQUE,
- group_name varchar NOT NULL,
- date_created timestamp NOT NULL, --do we default current time?
- PRIMARY KEY (group_id)
-);
-
-CREATE TABLE IF NOT EXISTS users (
- user_id varchar NOT NULL UNIQUE,
- first_name varchar NOT NULL,
- last_name varchar NOT NULL,
- email varchar NOT NULL,
- phone varchar, --potentially make phone/address required (NOT NULL)
- address varchar,
- pfp_s3_url varchar, --for profile picture if we implement that
- device_id varchar, --expoPushToken for push notifications
- push_notification_enabled BOOLEAN DEFAULT FALSE,
- PRIMARY KEY (user_id)
-);
-
-CREATE TABLE IF NOT EXISTS group_roles (
- group_id integer NOT NULL,
- user_id varchar NOT NULL,
- role role NOT NULL,
- PRIMARY KEY (group_id, user_id),
- FOREIGN KEY (group_id) REFERENCES care_group (group_id),
- FOREIGN KEY (user_id) REFERENCES users (user_id)
-);
-
-CREATE TABLE IF NOT EXISTS task (
- task_id serial NOT NULL,
- group_id integer NOT NULL,
- created_by varchar NOT NULL,
- created_date timestamp NOT NULL, -- add default val with current timestamp?
- start_date timestamp,
- end_date timestamp,
- notes varchar,
- repeating BOOLEAN DEFAULT FALSE,
- repeating_interval varchar,
- repeating_end_date timestamp,
- task_status task_status NOT NULL,
- task_type task_type NOT NULL, -- (eg. medication management, dr appointment, etc.)
- task_info json,
- PRIMARY KEY (task_id),
- FOREIGN KEY (group_id) REFERENCES care_group (group_id),
- FOREIGN KEY (created_by) REFERENCES users (user_id)
-);
-
-CREATE TABLE IF NOT EXISTS task_assignees (
- task_id integer NOT NULL,
- user_id varchar NOT NULL,
- assignment_status task_assignment_status NOT NULL,
- assigned_by varchar NOT NULL,
- assigned_date timestamp NOT NULL, -- add default val with current timestamp?
- last_notified timestamp,
- PRIMARY KEY (task_id, user_id),
- FOREIGN KEY (task_id) REFERENCES task (task_id),
- FOREIGN KEY (user_id) REFERENCES users (user_id),
- FOREIGN KEY (assigned_by) REFERENCES users (user_id)
-);
-
- CREATE TABLE If NOT EXISTS label (
- group_id integer NOT NULL,
- label_name varchar NOT NULL,
- label_color varchar NOT NULL, -- TODO:figure out what form color should be ("rgba(12,2,1,0)", "#21292F", "red" etc.) or just
- PRIMARY KEY (group_id, label_name),
- FOREIGN KEY (group_id) REFERENCES care_group (group_id)
-);
-
- CREATE TABLE If NOT EXISTS task_labels (
- task_id integer NOT NULL,
- group_id integer NOT NULL,
- label_name varchar NOT NULL,
- PRIMARY KEY (task_id, label_name),
- 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 (
- file_id serial NOT NULL UNIQUE,
- file_name varchar NOT NULL,
- group_id integer NOT NULL,
- upload_by varchar NOT NULL,
- upload_date timestamp,
- file_size integer NOT NULL,
- task_id integer,
- PRIMARY KEY (file_id),
- FOREIGN KEY (group_id) REFERENCES care_group (group_id),
- FOREIGN KEY (upload_by) REFERENCES users (user_id),
- FOREIGN KEY (task_id) REFERENCES task (task_id)
-);
-
------------------ SAMPLE DATA :) -----------------------
-
--- Insert sample data into "medication" table
INSERT INTO medication (medication_id, medication_name)
VALUES
(1, 'Medication A'),
@@ -123,73 +14,3 @@ VALUES
(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()),
- ('Care-Wallet 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'),
- ('fIoFY26mJnYWH8sNdfuVoxpnVnr1', 'Matt', 'McCoy', '', '', '')
-;
-
-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'),
- (5, 'fIoFY26mJnYWH8sNdfuVoxpnVnr1', 'PRIMARY')
-;
-
-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', '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')
-;
-
-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', 'DECLINED', 'user1', NOW())
-;
-
-INSERT INTO label (group_id, label_name, label_color)
-VALUES
- (1, 'Medication', 'blue'),
- (2, 'Appointments', 'green'),
- (3, 'Financial', 'orange'),
- (4, 'Household', 'purple'),
- (1, '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)
-;
diff --git a/backend/docs/docs.go b/backend/docs/docs.go
index 6da6f32..751b7a3 100644
--- a/backend/docs/docs.go
+++ b/backend/docs/docs.go
@@ -432,6 +432,34 @@ const docTemplate = `{
}
}
},
+ "/tasks": {
+ "post": {
+ "description": "Create a new task",
+ "tags": [
+ "tasks"
+ ],
+ "summary": "Create a New Task",
+ "parameters": [
+ {
+ "description": "Create Task Request",
+ "name": "request_body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/tasks.TaskBody"
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Created Task",
+ "schema": {
+ "$ref": "#/definitions/models.Task"
+ }
+ }
+ }
+ }
+ },
"/tasks/assigned": {
"get": {
"description": "get tasks assigned to given users",
@@ -532,6 +560,92 @@ const docTemplate = `{
}
}
},
+ "/tasks/{tid}": {
+ "get": {
+ "description": "get a task given its id",
+ "tags": [
+ "tasks"
+ ],
+ "summary": "get task by id",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "the id of the task",
+ "name": "tid",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/models.Task"
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "put": {
+ "description": "Update the task_info field of a task by ID",
+ "tags": [
+ "tasks"
+ ],
+ "summary": "Update Task Info",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Task ID",
+ "name": "tid",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Update Task Info Request",
+ "name": "request_body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/tasks.TaskBody"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Updated Task",
+ "schema": {
+ "$ref": "#/definitions/models.Task"
+ }
+ }
+ }
+ },
+ "delete": {
+ "description": "Delete a task by ID",
+ "tags": [
+ "tasks"
+ ],
+ "summary": "Delete a Task",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Task ID",
+ "name": "tid",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "No Content"
+ }
+ }
+ }
+ },
"/tasks/{tid}/assign": {
"post": {
"description": "assign users to task",
@@ -576,6 +690,41 @@ const docTemplate = `{
}
}
},
+ "/tasks/{tid}/assigned": {
+ "get": {
+ "description": "Get list of users assigned to a task by task ID",
+ "tags": [
+ "tasks"
+ ],
+ "summary": "Get list of users assigned to a task",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Task ID",
+ "name": "tid",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List of user IDs assigned to the task",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
"/tasks/{tid}/labels": {
"get": {
"description": "get a tasks labels given the task id",
@@ -732,6 +881,155 @@ const docTemplate = `{
}
}
}
+ },
+ "/user": {
+ "get": {
+ "description": "gets the information about multiple users given their user id",
+ "tags": [
+ "user"
+ ],
+ "summary": "gets the information about multiple users",
+ "parameters": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "collectionFormat": "csv",
+ "description": "User IDs",
+ "name": "userIDs",
+ "in": "query",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/models.User"
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "/user/{uid}": {
+ "get": {
+ "description": "gets the information about a user given their user id",
+ "tags": [
+ "user"
+ ],
+ "summary": "gets the information about a user",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "User ID",
+ "name": "uid",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/models.User"
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "put": {
+ "description": "Updates a user with the provided userId given the updated user.",
+ "tags": [
+ "user"
+ ],
+ "summary": "Updates a user",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "User ID",
+ "name": "uid",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "User Information",
+ "name": "UserInfo",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/user.UserInfoBody"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/models.User"
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "post": {
+ "description": "Creates a new user with the provided userId.",
+ "tags": [
+ "user"
+ ],
+ "summary": "Creates a user",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "User ID",
+ "name": "uid",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "User Information",
+ "name": "UserInfo",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/user.UserInfoBody"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/models.User"
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
}
},
"definitions": {
@@ -919,6 +1217,38 @@ const docTemplate = `{
}
}
},
+ "models.User": {
+ "type": "object",
+ "properties": {
+ "address": {
+ "type": "string"
+ },
+ "device_id": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "first_name": {
+ "type": "string"
+ },
+ "last_name": {
+ "type": "string"
+ },
+ "pfp_s3_url": {
+ "type": "string"
+ },
+ "phone": {
+ "type": "string"
+ },
+ "push_notification_enabled": {
+ "type": "boolean"
+ },
+ "user_id": {
+ "type": "string"
+ }
+ }
+ },
"task_labels.LabelData": {
"type": "object",
"properties": {
@@ -954,6 +1284,68 @@ const docTemplate = `{
}
}
}
+ },
+ "tasks.TaskBody": {
+ "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_info": {
+ "type": "string"
+ },
+ "task_status": {
+ "type": "string"
+ },
+ "task_type": {
+ "type": "string"
+ }
+ }
+ },
+ "user.UserInfoBody": {
+ "type": "object",
+ "properties": {
+ "address": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "first_name": {
+ "type": "string"
+ },
+ "last_name": {
+ "type": "string"
+ },
+ "phone": {
+ "type": "string"
+ }
+ }
}
}
}`
diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json
index 40f2229..1336662 100644
--- a/backend/docs/swagger.json
+++ b/backend/docs/swagger.json
@@ -425,6 +425,34 @@
}
}
},
+ "/tasks": {
+ "post": {
+ "description": "Create a new task",
+ "tags": [
+ "tasks"
+ ],
+ "summary": "Create a New Task",
+ "parameters": [
+ {
+ "description": "Create Task Request",
+ "name": "request_body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/tasks.TaskBody"
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Created Task",
+ "schema": {
+ "$ref": "#/definitions/models.Task"
+ }
+ }
+ }
+ }
+ },
"/tasks/assigned": {
"get": {
"description": "get tasks assigned to given users",
@@ -525,6 +553,92 @@
}
}
},
+ "/tasks/{tid}": {
+ "get": {
+ "description": "get a task given its id",
+ "tags": [
+ "tasks"
+ ],
+ "summary": "get task by id",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "the id of the task",
+ "name": "tid",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/models.Task"
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "put": {
+ "description": "Update the task_info field of a task by ID",
+ "tags": [
+ "tasks"
+ ],
+ "summary": "Update Task Info",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Task ID",
+ "name": "tid",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Update Task Info Request",
+ "name": "request_body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/tasks.TaskBody"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Updated Task",
+ "schema": {
+ "$ref": "#/definitions/models.Task"
+ }
+ }
+ }
+ },
+ "delete": {
+ "description": "Delete a task by ID",
+ "tags": [
+ "tasks"
+ ],
+ "summary": "Delete a Task",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Task ID",
+ "name": "tid",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "No Content"
+ }
+ }
+ }
+ },
"/tasks/{tid}/assign": {
"post": {
"description": "assign users to task",
@@ -569,6 +683,41 @@
}
}
},
+ "/tasks/{tid}/assigned": {
+ "get": {
+ "description": "Get list of users assigned to a task by task ID",
+ "tags": [
+ "tasks"
+ ],
+ "summary": "Get list of users assigned to a task",
+ "parameters": [
+ {
+ "type": "integer",
+ "description": "Task ID",
+ "name": "tid",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "List of user IDs assigned to the task",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
"/tasks/{tid}/labels": {
"get": {
"description": "get a tasks labels given the task id",
@@ -725,6 +874,155 @@
}
}
}
+ },
+ "/user": {
+ "get": {
+ "description": "gets the information about multiple users given their user id",
+ "tags": [
+ "user"
+ ],
+ "summary": "gets the information about multiple users",
+ "parameters": [
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "collectionFormat": "csv",
+ "description": "User IDs",
+ "name": "userIDs",
+ "in": "query",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/models.User"
+ }
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "/user/{uid}": {
+ "get": {
+ "description": "gets the information about a user given their user id",
+ "tags": [
+ "user"
+ ],
+ "summary": "gets the information about a user",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "User ID",
+ "name": "uid",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/models.User"
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "put": {
+ "description": "Updates a user with the provided userId given the updated user.",
+ "tags": [
+ "user"
+ ],
+ "summary": "Updates a user",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "User ID",
+ "name": "uid",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "User Information",
+ "name": "UserInfo",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/user.UserInfoBody"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/models.User"
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "post": {
+ "description": "Creates a new user with the provided userId.",
+ "tags": [
+ "user"
+ ],
+ "summary": "Creates a user",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "User ID",
+ "name": "uid",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "User Information",
+ "name": "UserInfo",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/user.UserInfoBody"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/models.User"
+ }
+ },
+ "400": {
+ "description": "Bad Request",
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ }
}
},
"definitions": {
@@ -912,6 +1210,38 @@
}
}
},
+ "models.User": {
+ "type": "object",
+ "properties": {
+ "address": {
+ "type": "string"
+ },
+ "device_id": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "first_name": {
+ "type": "string"
+ },
+ "last_name": {
+ "type": "string"
+ },
+ "pfp_s3_url": {
+ "type": "string"
+ },
+ "phone": {
+ "type": "string"
+ },
+ "push_notification_enabled": {
+ "type": "boolean"
+ },
+ "user_id": {
+ "type": "string"
+ }
+ }
+ },
"task_labels.LabelData": {
"type": "object",
"properties": {
@@ -947,6 +1277,68 @@
}
}
}
+ },
+ "tasks.TaskBody": {
+ "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_info": {
+ "type": "string"
+ },
+ "task_status": {
+ "type": "string"
+ },
+ "task_type": {
+ "type": "string"
+ }
+ }
+ },
+ "user.UserInfoBody": {
+ "type": "object",
+ "properties": {
+ "address": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "first_name": {
+ "type": "string"
+ },
+ "last_name": {
+ "type": "string"
+ },
+ "phone": {
+ "type": "string"
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml
index 2617a4b..91b65c9 100644
--- a/backend/docs/swagger.yaml
+++ b/backend/docs/swagger.yaml
@@ -121,6 +121,27 @@ definitions:
userID:
type: string
type: object
+ models.User:
+ properties:
+ address:
+ type: string
+ device_id:
+ type: string
+ email:
+ type: string
+ first_name:
+ type: string
+ last_name:
+ type: string
+ pfp_s3_url:
+ type: string
+ phone:
+ type: string
+ push_notification_enabled:
+ type: boolean
+ user_id:
+ type: string
+ type: object
task_labels.LabelData:
properties:
group_id:
@@ -144,6 +165,47 @@ definitions:
type: string
type: array
type: object
+ tasks.TaskBody:
+ 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_info:
+ type: string
+ task_status:
+ type: string
+ task_type:
+ type: string
+ type: object
+ user.UserInfoBody:
+ properties:
+ address:
+ type: string
+ email:
+ type: string
+ first_name:
+ type: string
+ last_name:
+ type: string
+ phone:
+ type: string
+ type: object
info:
contact: {}
description: This is an API for the Care-Wallet App.
@@ -426,6 +488,81 @@ paths:
summary: add a medication
tags:
- medications
+ /tasks:
+ post:
+ description: Create a new task
+ parameters:
+ - description: Create Task Request
+ in: body
+ name: request_body
+ required: true
+ schema:
+ $ref: '#/definitions/tasks.TaskBody'
+ responses:
+ "201":
+ description: Created Task
+ schema:
+ $ref: '#/definitions/models.Task'
+ summary: Create a New Task
+ tags:
+ - tasks
+ /tasks/{tid}:
+ delete:
+ description: Delete a task by ID
+ parameters:
+ - description: Task ID
+ in: path
+ name: tid
+ required: true
+ type: integer
+ responses:
+ "204":
+ description: No Content
+ summary: Delete a Task
+ tags:
+ - tasks
+ get:
+ description: get a task given its id
+ parameters:
+ - description: the id of the task
+ in: path
+ name: tid
+ required: true
+ type: integer
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.Task'
+ "400":
+ description: Bad Request
+ schema:
+ type: string
+ summary: get task by id
+ tags:
+ - tasks
+ put:
+ description: Update the task_info field of a task by ID
+ parameters:
+ - description: Task ID
+ in: path
+ name: tid
+ required: true
+ type: integer
+ - description: Update Task Info Request
+ in: body
+ name: request_body
+ required: true
+ schema:
+ $ref: '#/definitions/tasks.TaskBody'
+ responses:
+ "200":
+ description: Updated Task
+ schema:
+ $ref: '#/definitions/models.Task'
+ summary: Update Task Info
+ tags:
+ - tasks
/tasks/{tid}/assign:
post:
description: assign users to task
@@ -455,6 +592,29 @@ paths:
summary: Assign Users To Task
tags:
- tasks
+ /tasks/{tid}/assigned:
+ get:
+ description: Get list of users assigned to a task by task ID
+ parameters:
+ - description: Task ID
+ in: path
+ name: tid
+ required: true
+ type: integer
+ responses:
+ "200":
+ description: List of user IDs assigned to the task
+ schema:
+ items:
+ type: string
+ type: array
+ "400":
+ description: Bad Request
+ schema:
+ type: string
+ summary: Get list of users assigned to a task
+ tags:
+ - tasks
/tasks/{tid}/labels:
delete:
description: remove a label from a task given the task id, group id, and label
@@ -623,4 +783,103 @@ paths:
summary: Get Filtered Tasks
tags:
- tasks
+ /user:
+ get:
+ description: gets the information about multiple users given their user id
+ parameters:
+ - collectionFormat: csv
+ description: User IDs
+ in: query
+ items:
+ type: string
+ name: userIDs
+ required: true
+ type: array
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/models.User'
+ type: array
+ "400":
+ description: Bad Request
+ schema:
+ type: string
+ summary: gets the information about multiple users
+ tags:
+ - user
+ /user/{uid}:
+ get:
+ description: gets the information about a user given their user id
+ parameters:
+ - description: User ID
+ in: path
+ name: uid
+ required: true
+ type: string
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.User'
+ "400":
+ description: Bad Request
+ schema:
+ type: string
+ summary: gets the information about a user
+ tags:
+ - user
+ post:
+ description: Creates a new user with the provided userId.
+ parameters:
+ - description: User ID
+ in: path
+ name: uid
+ required: true
+ type: string
+ - description: User Information
+ in: body
+ name: UserInfo
+ required: true
+ schema:
+ $ref: '#/definitions/user.UserInfoBody'
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.User'
+ "400":
+ description: Bad Request
+ schema:
+ type: string
+ summary: Creates a user
+ tags:
+ - user
+ put:
+ description: Updates a user with the provided userId given the updated user.
+ parameters:
+ - description: User ID
+ in: path
+ name: uid
+ required: true
+ type: string
+ - description: User Information
+ in: body
+ name: UserInfo
+ required: true
+ schema:
+ $ref: '#/definitions/user.UserInfoBody'
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/models.User'
+ "400":
+ description: Bad Request
+ schema:
+ type: string
+ summary: Updates a user
+ tags:
+ - user
swagger: "2.0"
diff --git a/backend/main.go b/backend/main.go
index 4b25ed5..e5f1ee6 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -11,6 +11,7 @@ import (
"carewallet/schema/medication"
"carewallet/schema/task_labels"
"carewallet/schema/tasks"
+ "carewallet/schema/user"
"fmt"
"os"
@@ -46,6 +47,8 @@ func main() {
files.FileGroup(v1, &files.PgModel{Conn: conn})
+ user.UserGroup(v1, &user.PgModel{Conn: conn})
+
group := v1.Group("group")
{
groups.CareGroups(group, &groups.PgModel{Conn: conn})
diff --git a/backend/schema/group-roles/routes.go b/backend/schema/group-roles/routes.go
index 3138d22..93b7c80 100644
--- a/backend/schema/group-roles/routes.go
+++ b/backend/schema/group-roles/routes.go
@@ -58,12 +58,19 @@ func (pg *PgModel) GetGroupByUID(c *gin.Context) {
// @router /group/{groupId}/roles [get]
func (pg *PgModel) GetGroupRoles(c *gin.Context) {
gid := c.Param("groupId")
- if gidInt, err := strconv.Atoi(gid); err == nil {
- if careGroups, err := GetAllGroupRolesFromDB(pg.Conn, gidInt); err == nil {
- c.JSON(http.StatusOK, careGroups)
- return
- }
+ gidInt, err := strconv.Atoi(gid)
+ if err != nil {
c.JSON(http.StatusBadRequest, err.Error())
+ return
}
+
+ careGroups, err := GetAllGroupRolesFromDB(pg.Conn, gidInt)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, careGroups)
}
diff --git a/backend/schema/group-roles/transactions.go b/backend/schema/group-roles/transactions.go
index b27582c..b38a363 100644
--- a/backend/schema/group-roles/transactions.go
+++ b/backend/schema/group-roles/transactions.go
@@ -39,7 +39,7 @@ func GetAllGroupRolesFromDB(pool *pgx.Conn, gid int) ([]models.GroupRole, error)
err := rows.Scan(&gr.GroupID, &gr.UserID, &gr.Role)
if err != nil {
- print(err, "from transactions err2 ")
+ print(err.Error(), "from transactions err2 ")
return nil, err
}
diff --git a/backend/schema/medication/routes.go b/backend/schema/medication/routes.go
index 885274e..b61e7f4 100644
--- a/backend/schema/medication/routes.go
+++ b/backend/schema/medication/routes.go
@@ -34,7 +34,8 @@ func (pg *PgModel) GetMedications(c *gin.Context) {
med, err := GetAllMedsFromDB(pg.Conn)
if err != nil {
- panic(err)
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
}
c.JSON(http.StatusOK, med)
diff --git a/backend/schema/medication/transactions.go b/backend/schema/medication/transactions.go
index 1416f0e..34d323b 100644
--- a/backend/schema/medication/transactions.go
+++ b/backend/schema/medication/transactions.go
@@ -25,7 +25,6 @@ func GetAllMedsFromDB(pool *pgx.Conn) ([]models.Medication, error) {
if err != nil {
print(err, "from transactions err2 ")
-
return nil, err
}
diff --git a/backend/schema/tasks/routes.go b/backend/schema/tasks/routes.go
index 6fef37a..9452a46 100644
--- a/backend/schema/tasks/routes.go
+++ b/backend/schema/tasks/routes.go
@@ -1,8 +1,13 @@
package tasks
import (
+ "carewallet/models"
+ "encoding/json"
+ "fmt"
"net/http"
+ "strconv"
"strings"
+ "time"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx"
@@ -13,18 +18,50 @@ type PgModel struct {
}
func TaskGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup {
-
tasks := v1.Group("")
{
tasks.GET("/filtered", c.GetFilteredTasks)
- tasks.POST("/:tid/assign", c.AssignUsersToTask)
- tasks.DELETE("/:tid/remove", c.RemoveUsersFromTask)
tasks.GET("/assigned", c.GetTasksByAssignedUsers)
- }
+ tasks.POST("", c.CreateTask)
+ byId := tasks.Group("/:tid")
+ {
+ byId.GET("", c.GetTaskByID)
+ byId.DELETE("", c.DeleteTask)
+ byId.PUT("", c.UpdateTaskInfo)
+ byId.GET("/assigned", c.GetUsersAssignedToTask)
+ byId.POST("/assign", c.AssignUsersToTask)
+ byId.DELETE("/remove", c.RemoveUsersFromTask)
+ }
+ }
return tasks
}
+// GetTaskByID godoc
+//
+// @summary get task by id
+// @description get a task given its id
+// @tags tasks
+//
+// @param tid path int true "the id of the task"
+//
+// @success 200 {object} models.Task
+// @failure 400 {object} string
+// @router /tasks/{tid} [GET]
+func (pg *PgModel) GetTaskByID(c *gin.Context) {
+ taskID, err := strconv.Atoi(c.Param("tid"))
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+ task, err := GetTaskByID(pg.Conn, taskID)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+ c.JSON(http.StatusOK, task)
+}
+
type TaskQuery struct {
TaskID string `form:"taskID"`
GroupID string `form:"groupID"`
@@ -52,14 +89,11 @@ func (pg *PgModel) GetFilteredTasks(c *gin.Context) {
c.JSON(http.StatusBadRequest, err.Error())
return
}
-
tasks, err := GetTasksByQueryFromDB(pg.Conn, filterQuery)
-
if err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
-
c.JSON(http.StatusOK, tasks)
}
@@ -82,19 +116,15 @@ type Assignment struct {
// @router /tasks/{tid}/assign [post]
func (pg *PgModel) AssignUsersToTask(c *gin.Context) {
var requestBody Assignment
-
if err := c.BindJSON(&requestBody); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
-
assignedUsers, err := AssignUsersToTaskInDB(pg.Conn, requestBody.UserIDs, c.Param("tid"), requestBody.Assigner)
-
if err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
-
c.JSON(http.StatusOK, assignedUsers)
}
@@ -116,19 +146,15 @@ type Removal struct {
// @router /tasks/{tid}/remove [delete]
func (pg *PgModel) RemoveUsersFromTask(c *gin.Context) {
var requestBody Removal
-
if err := c.BindJSON(&requestBody); err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
-
removedUsers, err := RemoveUsersFromTaskInDB(pg.Conn, requestBody.UserIDs, c.Param("tid"))
-
if err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
-
c.JSON(http.StatusOK, removedUsers)
}
@@ -152,13 +178,180 @@ func (pg *PgModel) GetTasksByAssignedUsers(c *gin.Context) {
assignedQuery := AssignedQuery{
UserIDs: strings.Split(userIDs, ","),
}
-
tasks, err := GetTasksByAssignedFromDB(pg.Conn, assignedQuery.UserIDs)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+ c.JSON(http.StatusOK, tasks)
+}
+type TaskBody struct {
+ 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"`
+}
+
+// CreateTask godoc
+//
+// @summary Create a New Task
+// @description Create a new task
+// @tags tasks
+//
+// @param request_body body TaskBody true "Create Task Request"
+//
+// @success 201 {object} models.Task "Created Task"
+// @router /tasks [post]
+func (pg *PgModel) CreateTask(c *gin.Context) {
+ // Bind the request body to the CreateTaskRequest struct
+ var requestBody models.Task
+ if err := c.BindJSON(&requestBody); err != nil {
+ fmt.Println("error binding to request body: ", err)
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ // Create the new task in the database
+ newTaskID, err := CreateTaskInDB(pg.Conn, requestBody)
if err != nil {
+ fmt.Println("error creating task in the database:", err)
c.JSON(http.StatusBadRequest, err.Error())
return
}
- c.JSON(http.StatusOK, tasks)
+ // Fetch the created task from the database
+ createdTask, err := GetTaskByID(pg.Conn, newTaskID)
+ if err != nil {
+ fmt.Println("error fetching created task from the database:", err)
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusCreated, createdTask)
+}
+
+// DeleteTask godoc
+//
+// @summary Delete a Task
+// @description Delete a task by ID
+// @tags tasks
+// @param tid path int true "Task ID"
+// @success 204 "No Content"
+// @router /tasks/{tid} [delete]
+func (pg *PgModel) DeleteTask(c *gin.Context) {
+ // Extract task ID from the path parameter
+ taskID, err := strconv.Atoi(c.Param("tid"))
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ // Check if the task exists before attempting to delete
+ if _, err := GetTaskByID(pg.Conn, taskID); err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ // Delete the task from the database
+ if err := DeleteTaskInDB(pg.Conn, taskID); err != nil {
+ fmt.Println("error deleting task from the database:", err)
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ c.Status(http.StatusNoContent)
+}
+
+// UpdateTaskInfo godoc
+//
+// @summary Update Task Info
+// @description Update the task_info field of a task by ID
+// @tags tasks
+// @param tid path int true "Task ID"
+// @param request_body body TaskBody true "Update Task Info Request"
+// @success 200 {object} models.Task "Updated Task"
+// @router /tasks/{tid} [put]
+func (pg *PgModel) UpdateTaskInfo(c *gin.Context) {
+ // Extract task ID from the path parameter
+ taskID, err := strconv.Atoi(c.Param("tid"))
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ // Bind the request body to the UpdateTaskInfoRequest struct
+ var requestBody models.Task
+
+ if err := c.BindJSON(&requestBody); err != nil {
+ fmt.Println("error binding to request body: ", err)
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ // Convert TaskInfo to json.RawMessage
+ taskInfoRaw, err := json.Marshal(requestBody.TaskInfo)
+ if err != nil {
+ fmt.Println("error converting TaskInfo to json.RawMessage:", err)
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ // Update the task_info field in the database
+ if err := UpdateTaskInfoInDB(pg.Conn, taskID, taskInfoRaw); err != nil {
+ fmt.Println("error updating task info in the database:", err)
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ // Fetch the updated task from the database
+ updatedTask, err := GetTaskByID(pg.Conn, taskID)
+ if err != nil {
+ fmt.Println("error fetching updated task from the database:", err)
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, updatedTask)
+}
+
+// TaskUser represents the user assigned to a task.
+type TaskUser struct {
+ UserID string `json:"userID"`
+}
+
+// GetUsersAssignedToTask godoc
+//
+// @summary Get list of users assigned to a task
+// @description Get list of users assigned to a task by task ID
+// @tags tasks
+// @param tid path int true "Task ID"
+// @success 200 {array} string "List of user IDs assigned to the task"
+// @failure 400 {object} string
+// @router /tasks/{tid}/assigned [get]
+func (pg *PgModel) GetUsersAssignedToTask(c *gin.Context) {
+ // Extract task ID from the path parameter
+ taskIDStr := c.Param("tid")
+ taskID, err := strconv.Atoi(taskIDStr)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ // Get list of users assigned to the task
+ userIDs, err := GetUsersAssignedToTaskFromDB(pg.Conn, taskID)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, userIDs)
}
diff --git a/backend/schema/tasks/task_test.go b/backend/schema/tasks/task_test.go
index 929d481..9ec9d8d 100644
--- a/backend/schema/tasks/task_test.go
+++ b/backend/schema/tasks/task_test.go
@@ -12,6 +12,7 @@ import (
"net/url"
"os"
"reflect"
+ "strconv"
"testing"
"time"
@@ -51,12 +52,12 @@ func TestTaskGroup(t *testing.T) {
w := httptest.NewRecorder()
query := url.Values{}
- 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)
+ 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)
@@ -205,4 +206,242 @@ func TestTaskGroup(t *testing.T) {
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",
+ },
+ }
+
+ if !reflect.DeepEqual(expectedTasks, responseTasks) {
+ t.Error("Result was not correct")
+ }
+ })
+
+ t.Run("TestCreateTask_Success", func(t *testing.T) {
+ // Creating a Task instance
+ startDate := time.Now().UTC()
+ endDate := time.Now().Add(24 * time.Hour).UTC()
+ notes := "This is a sample task"
+ repeating := true
+ repeatingInterval := "Weekly"
+ repeatingEndDate := time.Now().Add(7 * 24 * time.Hour).UTC()
+ taskInfo := `{"info": "Additional information about the task"}`
+
+ taskData := models.Task{
+ TaskID: 1,
+ GroupID: 1,
+ CreatedBy: "user1",
+ CreatedDate: time.Now().UTC(),
+ StartDate: &startDate,
+ EndDate: &endDate,
+ Notes: ¬es,
+ Repeating: repeating,
+ RepeatingInterval: &repeatingInterval,
+ RepeatingEndDate: &repeatingEndDate,
+ TaskStatus: "INCOMPLETE",
+ TaskType: "med_mgmt",
+ TaskInfo: &taskInfo,
+ }
+
+ taskJSON, err := json.Marshal(taskData)
+ if err != nil {
+ t.Error("Failed to marshal task data to JSON:", err)
+ }
+
+ // Create a new request with the task data
+ req, err := http.NewRequest("POST", "/tasks", bytes.NewBuffer(taskJSON))
+ if err != nil {
+ t.Error("Failed to create HTTP request:", err)
+ }
+
+ // Create a recorder to capture the response
+ w := httptest.NewRecorder()
+
+ // Serve the request using the router
+ router.ServeHTTP(w, req)
+
+ // Assertions
+ if http.StatusCreated != w.Code {
+ t.Error("Expected status 201, got", w.Code)
+ }
+
+ // Validate the response body
+ var createdTask models.Task
+ err = json.Unmarshal(w.Body.Bytes(), &createdTask)
+ if err != nil {
+ t.Error("Failed to unmarshal JSON:", err)
+ }
+
+ // Add more specific assertions based on the expected response
+ if createdTask.TaskID == -1 {
+ t.Error("Expected task ID to be present")
+ }
+ if createdTask.CreatedBy != "user1" {
+ t.Error("Unexpected created_by value")
+ }
+ // Add more assertions as needed
+ })
+
+ t.Run("TestDeleteTask", func(t *testing.T) {
+ getTaskByIDFunc := func(taskID int) (models.Task, error) {
+ return models.Task{
+ TaskID: taskID,
+ // Add other necessary fields
+ }, nil
+ }
+
+ // Mock the successful deletion of the task in the database
+ deleteTaskInDBFunc := func(taskID int) error {
+ return nil
+ }
+
+ // Create a new Gin router
+ router := gin.Default()
+
+ // Attach the DeleteTask route to the router
+ router.DELETE("/tasks/:tid", func(c *gin.Context) {
+ // Extract task ID from the path parameter
+ taskID, err := strconv.Atoi(c.Param("tid"))
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ // Check if the task exists before attempting to delete
+ if _, err := getTaskByIDFunc(taskID); err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ // Delete the task from the database
+ if err := deleteTaskInDBFunc(taskID); err != nil {
+ fmt.Println("error deleting task from the database:", err)
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ c.Status(http.StatusNoContent)
+ })
+
+ // Perform a DELETE request to the /tasks/:tid endpoint
+ req, err := http.NewRequest("DELETE", "/tasks/1", nil)
+ if err != nil {
+ t.Fatal("Failed to create HTTP request:", err)
+ }
+
+ w := httptest.NewRecorder()
+ router.ServeHTTP(w, req)
+
+ // Assertions
+ if http.StatusNoContent != w.Code {
+ t.Fatal("Expected status 204, got", w.Code)
+ }
+
+ if body := w.Body.String(); body != "" {
+ t.Fatal("Expected empty response body, got", body)
+ }
+ })
+
+ t.Run("TestUpdateTaskInfo", func(t *testing.T) {
+ // Creating a Task instance
+ startDate := time.Now().UTC()
+ endDate := time.Now().Add(24 * time.Hour).UTC()
+ notes := "This is a sample task"
+ repeating := true
+ repeatingInterval := "Weekly"
+ repeatingEndDate := time.Now().Add(7 * 24 * time.Hour).UTC()
+ taskInfo := `{"info": "Additional information about the task"}`
+
+ taskData := models.Task{
+ TaskID: 1,
+ GroupID: 1,
+ CreatedBy: "user1",
+ CreatedDate: time.Now().UTC(),
+ StartDate: &startDate,
+ EndDate: &endDate,
+ Notes: ¬es,
+ Repeating: repeating,
+ RepeatingInterval: &repeatingInterval,
+ RepeatingEndDate: &repeatingEndDate,
+ TaskStatus: "INCOMPLETE",
+ TaskType: "med_mgmt",
+ TaskInfo: &taskInfo,
+ }
+ requestBodyJSON, err := json.Marshal(taskData)
+ if err != nil {
+ t.Fatal("Failed to marshal task data to JSON:", err)
+ return
+ }
+
+ // Perform a PUT request to the /tasks/:tid/info endpoint
+ req, err := http.NewRequest("PUT", "/tasks/1", bytes.NewBuffer(requestBodyJSON))
+ if err != nil {
+ t.Fatal("Failed to create HTTP request:", err)
+ return
+ }
+
+ // Create a recorder to capture the response
+ w := httptest.NewRecorder()
+
+ // Serve the request using the router
+ router.ServeHTTP(w, req)
+
+ // Assertions
+ if http.StatusOK != w.Code {
+ t.Fatal("Expected status 200, got", w.Code)
+ return
+ }
+ })
+
+ t.Run("TestGetUsersAssignedToTask", func(t *testing.T) {
+ // Create a new recorder and request
+ w := httptest.NewRecorder()
+ req, _ := http.NewRequest("GET", "/tasks/2/assigned", nil)
+
+ // Serve the request using the router
+ router.ServeHTTP(w, req)
+
+ // Print the response body for debugging
+ fmt.Println("Response Body:", w.Body.String())
+
+ // Assertions
+ if http.StatusOK != w.Code {
+ t.Error("Failed to retrieve users assigned to task.")
+ }
+
+ var responseUsers []string
+ err := json.Unmarshal(w.Body.Bytes(), &responseUsers)
+ if err != nil {
+ t.Error("Failed to unmarshal JSON")
+ }
+
+ expectedUsers := []string{"user3", "user4"}
+ if !reflect.DeepEqual(expectedUsers, responseUsers) {
+ t.Error("Result was not correct")
+ }
+ })
}
diff --git a/backend/schema/tasks/transactions.go b/backend/schema/tasks/transactions.go
index a69a1c9..ba60f4c 100644
--- a/backend/schema/tasks/transactions.go
+++ b/backend/schema/tasks/transactions.go
@@ -2,6 +2,7 @@ package tasks
import (
"carewallet/models"
+ "encoding/json"
"fmt"
"strconv"
"time"
@@ -153,3 +154,97 @@ func GetTasksByAssignedFromDB(pool *pgx.Conn, userIDs []string) ([]models.Task,
return tasks, nil
}
+
+// CreateTaskInDB creates a new task in the database and returns its ID
+func CreateTaskInDB(pool *pgx.Conn, newTask models.Task) (int, error) {
+ query := `
+ INSERT INTO task (group_id, created_by, created_date, start_date, end_date, notes, repeating, repeating_interval, repeating_end_date, task_status, task_type, task_info) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING task_id`
+
+ var newTaskID int
+ err := pool.QueryRow(
+ query,
+ newTask.GroupID,
+ newTask.CreatedBy,
+ time.Now(), // Assuming created_date should be the current timestamp
+ newTask.StartDate,
+ newTask.EndDate,
+ newTask.Notes,
+ newTask.Repeating,
+ newTask.RepeatingInterval,
+ newTask.RepeatingEndDate,
+ newTask.TaskStatus,
+ newTask.TaskType,
+ newTask.TaskInfo,
+ ).Scan(&newTaskID)
+
+ return newTaskID, err
+}
+
+// DeleteTaskInDB deletes a task from the database by ID
+func DeleteTaskInDB(pool *pgx.Conn, taskID int) error {
+ // Assuming "task" table structure, adjust the query based on your schema
+ query := "DELETE FROM task WHERE task_id = $1"
+
+ _, err := pool.Exec(query, taskID)
+ return err
+}
+
+// UpdateTaskInfoInDB updates the task_info field in the database
+func UpdateTaskInfoInDB(pool *pgx.Conn, taskID int, taskInfo json.RawMessage) error {
+ // Assuming "task" table structure, adjust the query based on your schema
+ query := "UPDATE task SET task_info = $1 WHERE task_id = $2"
+
+ _, err := pool.Exec(query, taskInfo, taskID)
+ return err
+}
+
+// GetTaskByID fetches a task from the database by its ID
+func GetTaskByID(pool *pgx.Conn, taskID int) (models.Task, error) {
+ query := `
+ SELECT task_id, group_id, created_by, created_date, start_date, end_date, notes, repeating, repeating_interval, repeating_end_date, task_status, task_type, task_info FROM task WHERE task_id = $1`
+
+ var task models.Task
+ err := pool.QueryRow(query, taskID).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,
+ )
+ return task, err
+}
+
+func GetUsersAssignedToTaskFromDB(pool *pgx.Conn, taskID int) ([]string, error) {
+ var userIDs []string
+
+ // Get all user IDs assigned to the task
+ rows, err := pool.Query("SELECT user_id FROM task_assignees WHERE task_id = $1;", taskID)
+ if err != nil {
+ fmt.Println(err, "error selecting user assignees")
+ return nil, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var userID string
+
+ err := rows.Scan(&userID)
+ if err != nil {
+ fmt.Println(err, "error scanning user ID")
+ return nil, err
+ }
+
+ fmt.Println(userID)
+ userIDs = append(userIDs, userID)
+ }
+
+ return userIDs, nil
+}
diff --git a/backend/schema/user/routes.go b/backend/schema/user/routes.go
new file mode 100644
index 0000000..37d13d4
--- /dev/null
+++ b/backend/schema/user/routes.go
@@ -0,0 +1,152 @@
+package user
+
+import (
+ "carewallet/models"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+ "github.com/jackc/pgx"
+)
+
+type PgModel struct {
+ Conn *pgx.Conn
+}
+
+func UserGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup {
+ userGroup := v1.Group("user")
+ {
+ userGroup.GET("/:uid", c.GetUser)
+ userGroup.GET("", c.GetUsers)
+ userGroup.POST("/:uid", c.CreateUser)
+ userGroup.PUT("/:uid", c.UpdateUser)
+ }
+
+ return userGroup
+}
+
+type UserInfoBody struct {
+ FirstName string `json:"first_name"`
+ LastName string `json:"last_name"`
+ Email string `json:"email"`
+ Phone string `json:"phone"`
+ Address string `json:"address"`
+}
+
+// CreateUser godoc
+//
+// @summary Creates a user
+// @description Creates a new user with the provided userId.
+// @tags user
+//
+// @param uid path string true "User ID"
+// @param UserInfo body UserInfoBody true "User Information"
+//
+// @success 200 {object} models.User
+// @failure 400 {object} string
+// @router /user/{uid} [POST]
+func (pg *PgModel) CreateUser(c *gin.Context) {
+ var requestBody UserInfoBody
+
+ if err := c.BindJSON(&requestBody); err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ user, err := CreateUserInDB(pg.Conn, c.Param("uid"), requestBody)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, user)
+}
+
+// GetUser godoc
+//
+// @summary gets the information about a user
+// @description gets the information about a user given their user id
+// @tags user
+//
+// @param uid path string true "User ID"
+//
+// @success 200 {object} models.User
+// @failure 400 {object} string
+// @router /user/{uid} [GET]
+func (pg *PgModel) GetUser(c *gin.Context) {
+ user, err := GetUserInDB(pg.Conn, c.Param("uid"))
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, user)
+}
+
+type UsersQuery struct {
+ UserIDs []string `form:"userIDs"`
+}
+
+// GetUsers godoc
+//
+// @summary gets the information about multiple users
+// @description gets the information about multiple users given their user id
+// @tags user
+//
+// @param userIDs query []string true "User IDs"
+//
+// @success 200 {array} models.User
+// @failure 400 {object} string
+// @router /user [GET]
+func (pg *PgModel) GetUsers(c *gin.Context) {
+
+ userIDs := c.Query("userIDs")
+ userQuery := UsersQuery{
+ UserIDs: strings.Split(userIDs, ","),
+ }
+
+ var users []models.User
+
+ for _, element := range userQuery.UserIDs {
+ user, err := GetUserInDB(pg.Conn, element)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+ users = append(users, user)
+ }
+
+ c.JSON(http.StatusOK, users)
+}
+
+// UpdateUser godoc
+//
+// @summary Updates a user
+// @description Updates a user with the provided userId given the updated user.
+// @tags user
+//
+// @param uid path string true "User ID"
+// @param UserInfo body UserInfoBody true "User Information"
+//
+// @success 200 {object} models.User
+// @failure 400 {object} string
+// @router /user/{uid} [PUT]
+func (pg *PgModel) UpdateUser(c *gin.Context) {
+ var requestBody UserInfoBody
+
+ if err := c.BindJSON(&requestBody); err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ user, err := UpdateUserInDB(pg.Conn, c.Param("uid"), requestBody)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, err.Error())
+ return
+ }
+
+ c.JSON(http.StatusOK, user)
+}
diff --git a/backend/schema/user/transactions.go b/backend/schema/user/transactions.go
new file mode 100644
index 0000000..b3f62ef
--- /dev/null
+++ b/backend/schema/user/transactions.go
@@ -0,0 +1,44 @@
+package user
+
+import (
+ "carewallet/models"
+
+ "github.com/jackc/pgx"
+)
+
+func CreateUserInDB(conn *pgx.Conn, uid string, requestBody UserInfoBody) (models.User, error) {
+ var user models.User
+
+ err := conn.QueryRow("INSERT INTO users (user_id, first_name, last_name, phone, email, address) VALUES ($1, $2, $3, $4, $5, $6) RETURNING user_id, first_name, last_name, phone, email, address", uid, requestBody.FirstName, requestBody.LastName, requestBody.Phone, requestBody.Email, requestBody.Address).Scan(&user.UserID, &user.FirstName, &user.LastName, &user.Phone, &user.Email, &user.Address)
+
+ if err != nil {
+ print(err.Error(), "from transactions err ")
+ return models.User{}, err
+ }
+
+ return user, nil
+}
+
+func UpdateUserInDB(conn *pgx.Conn, uid string, requestBody UserInfoBody) (models.User, error) {
+ var user models.User
+ err := conn.QueryRow("UPDATE users SET first_name = $1, last_name = $2, phone = $3, email = $4, address = $5 RETURNING user_id, first_name, last_name, phone, email, address", requestBody.FirstName, requestBody.LastName, requestBody.Phone, requestBody.Email, requestBody.Address).Scan(&user.UserID, &user.FirstName, &user.LastName, &user.Phone, &user.Email, &user.Address)
+
+ if err != nil {
+ print(err.Error(), "from transactions err ")
+ return models.User{}, err
+ }
+
+ return user, nil
+}
+
+func GetUserInDB(conn *pgx.Conn, uid string) (models.User, error) {
+ var user models.User
+ err := conn.QueryRow("SELECT user_id, first_name, last_name, phone, email, address FROM users WHERE user_id = $1", uid).Scan(&user.UserID, &user.FirstName, &user.LastName, &user.Phone, &user.Email, &user.Address)
+
+ if err != nil {
+ print(err.Error(), "from transactions err ")
+ return models.User{}, err
+ }
+
+ return user, nil
+}
diff --git a/backend/schema/user/user_test.go b/backend/schema/user/user_test.go
new file mode 100644
index 0000000..a00006b
--- /dev/null
+++ b/backend/schema/user/user_test.go
@@ -0,0 +1 @@
+package user
diff --git a/client/assets/arrow-left.svg b/client/assets/arrow-left.svg
new file mode 100644
index 0000000..99dedf4
--- /dev/null
+++ b/client/assets/arrow-left.svg
@@ -0,0 +1,4 @@
+
diff --git a/client/assets/bottom-nav/bell.svg b/client/assets/bottom-nav/bell.svg
new file mode 100644
index 0000000..c3e3954
--- /dev/null
+++ b/client/assets/bottom-nav/bell.svg
@@ -0,0 +1,6 @@
+
diff --git a/client/assets/bottom-nav/calendar.svg b/client/assets/bottom-nav/calendar.svg
new file mode 100644
index 0000000..f6135fc
--- /dev/null
+++ b/client/assets/bottom-nav/calendar.svg
@@ -0,0 +1,8 @@
+
diff --git a/client/assets/bottom-nav/home.svg b/client/assets/bottom-nav/home.svg
new file mode 100644
index 0000000..917ea06
--- /dev/null
+++ b/client/assets/bottom-nav/home.svg
@@ -0,0 +1,6 @@
+
diff --git a/client/assets/bottom-nav/user.svg b/client/assets/bottom-nav/user.svg
new file mode 100644
index 0000000..e0879e6
--- /dev/null
+++ b/client/assets/bottom-nav/user.svg
@@ -0,0 +1,7 @@
+
diff --git a/client/assets/home.svg b/client/assets/home.svg
deleted file mode 100644
index 1449ac4..0000000
--- a/client/assets/home.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/client/assets/profile/ellipse.svg b/client/assets/profile/ellipse.svg
new file mode 100644
index 0000000..bff7bc4
--- /dev/null
+++ b/client/assets/profile/ellipse.svg
@@ -0,0 +1,3 @@
+
diff --git a/client/components/profile/CircleCard.tsx b/client/components/profile/CircleCard.tsx
new file mode 100644
index 0000000..ba2f8c9
--- /dev/null
+++ b/client/components/profile/CircleCard.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Pressable, Text, View } from 'react-native';
+
+interface CircleCardProps {
+ onTouchEnd?: () => void;
+ ButtonText: string;
+}
+export function CircleCard({ onTouchEnd, ButtonText }: CircleCardProps) {
+ return (
+
+
+
+ {ButtonText}
+
+
+ );
+}
diff --git a/client/components/profile/Group.tsx b/client/components/profile/Group.tsx
new file mode 100644
index 0000000..18bcb07
--- /dev/null
+++ b/client/components/profile/Group.tsx
@@ -0,0 +1,78 @@
+import React, { useState } from 'react';
+import {
+ ActivityIndicator,
+ FlatList,
+ Pressable,
+ Text,
+ View
+} from 'react-native';
+
+import { GroupRole, Role } from '../../types/group';
+import { User } from '../../types/user';
+
+interface GroupProps {
+ roles: GroupRole[];
+ rolesAreLoading: boolean;
+ setActiveUser: (userID: string) => void;
+ activeUser: string;
+ users: User[];
+ usersAreLoading: boolean;
+}
+
+export function Group({
+ roles,
+ rolesAreLoading,
+ usersAreLoading,
+ users,
+ setActiveUser,
+ activeUser
+}: GroupProps) {
+ const [canPress, setCanPress] = useState(true);
+
+ if (rolesAreLoading || usersAreLoading) {
+ return (
+
+
+ Loading...
+
+ );
+ }
+
+ if (!roles || !users) {
+ return (
+
+ Could Not Load Group...
+
+ );
+ }
+
+ return (
+ setCanPress(false)}
+ onScrollEndDrag={() => setCanPress(true)}
+ horizontal
+ showsHorizontalScrollIndicator={false}
+ data={users.filter(
+ (user) =>
+ user.user_id !== activeUser &&
+ user.user_id !==
+ (roles.find((role) => role.role === Role.PATIENT)?.user_id ?? '')
+ )}
+ renderItem={({ item, index }) => (
+ {
+ if (canPress) setActiveUser(item.user_id);
+ }}
+ >
+
+
+ {item.first_name}
+
+
+ )}
+ />
+ );
+}
diff --git a/client/components/profile/Header.tsx b/client/components/profile/Header.tsx
new file mode 100644
index 0000000..b386ec4
--- /dev/null
+++ b/client/components/profile/Header.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { Text, View } from 'react-native';
+
+import { useNavigation } from '@react-navigation/native';
+import { styled } from 'nativewind';
+
+import ArrowLeft from '../../assets/arrow-left.svg';
+import Ellipse from '../../assets/profile/ellipse.svg';
+import { AppStackNavigation } from '../../navigation/AppNavigation';
+import { GroupRole } from '../../types/group';
+import { User } from '../../types/user';
+import { ProfileTopHeader } from './ProfileTopHeader';
+
+const StyledEllipse = styled(Ellipse);
+
+interface HeaderProps {
+ user: User | undefined;
+ role: GroupRole | undefined;
+}
+
+export function Header({ user, role }: HeaderProps) {
+ const navigate = useNavigation();
+
+ if (!user) return null;
+
+ return (
+ <>
+
+
+ }
+ rightButtonText="Edit"
+ />
+
+ {`${role?.role.charAt(0)}${role?.role.slice(1).toLowerCase()} Caregiver`}
+
+
+ {user.phone ? user.phone : user.email}
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/client/components/profile/ProfileTopHeader.tsx b/client/components/profile/ProfileTopHeader.tsx
new file mode 100644
index 0000000..ae2acd7
--- /dev/null
+++ b/client/components/profile/ProfileTopHeader.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { Pressable, Text, View } from 'react-native';
+
+import { User } from '../../types/user';
+
+interface ProfileTopHeaderProps {
+ user: User;
+ onTouchEndLeft?: () => void;
+ leftButtonText?: JSX.Element | string;
+ onTouchEndRight?: () => void;
+ rightButtonText?: string;
+}
+
+export function ProfileTopHeader({
+ user,
+ onTouchEndLeft,
+ leftButtonText,
+ onTouchEndRight,
+ rightButtonText
+}: ProfileTopHeaderProps) {
+ return (
+
+
+
+ {leftButtonText}
+
+
+
+ {user.first_name} {user.last_name}
+
+
+
+ {rightButtonText}
+
+
+
+ );
+}
diff --git a/client/contexts/CareWalletContext.tsx b/client/contexts/CareWalletContext.tsx
index 9170200..448a71c 100644
--- a/client/contexts/CareWalletContext.tsx
+++ b/client/contexts/CareWalletContext.tsx
@@ -2,6 +2,7 @@ import React, { createContext, useContext, useEffect, useState } from 'react';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
+import { getUserGroup } from './api';
import { Group, User } from './types';
type CareWalletContextData = {
@@ -29,10 +30,12 @@ export function CareWalletProvider({
setUser(signedInUser);
- setGroup({
- groupID: 999,
- role: 'TEMP'
- });
+ // TODO: What should happen if a user isnt apart of a group?
+ if (user) {
+ getUserGroup(user.uid).then((grouprole) => {
+ setGroup({ role: grouprole.role, groupID: grouprole.group_id });
+ });
+ }
});
}, []);
diff --git a/client/contexts/api.ts b/client/contexts/api.ts
new file mode 100644
index 0000000..a1fcb55
--- /dev/null
+++ b/client/contexts/api.ts
@@ -0,0 +1,9 @@
+import axios from 'axios';
+
+import { api_url } from '../services/api-links';
+import { GroupRole } from '../types/group';
+
+export const getUserGroup = async (userId: string): Promise => {
+ const { data } = await axios.get(`${api_url}/group/member/${userId}`);
+ return data;
+};
diff --git a/client/navigation/AppNavigation.tsx b/client/navigation/AppNavigation.tsx
index bce16db..5e47e2d 100644
--- a/client/navigation/AppNavigation.tsx
+++ b/client/navigation/AppNavigation.tsx
@@ -12,6 +12,7 @@ export type AppStackParamList = {
Main: undefined;
Home: undefined;
Login: undefined;
+ Profile: undefined;
};
export type AppStackNavigation = NavigationProp;
diff --git a/client/navigation/AppStackBottomTabNavigator.tsx b/client/navigation/AppStackBottomTabNavigator.tsx
index ae71d1a..6793920 100644
--- a/client/navigation/AppStackBottomTabNavigator.tsx
+++ b/client/navigation/AppStackBottomTabNavigator.tsx
@@ -3,32 +3,58 @@ import { Text } from 'react-native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
-import Home from '../assets/home.svg';
-import GroupScreen from '../screens/Groups';
+import Bell from '../assets/bottom-nav/bell.svg';
+import Calendar from '../assets/bottom-nav/calendar.svg';
+import Home from '../assets/bottom-nav/home.svg';
+import User from '../assets/bottom-nav/user.svg';
import MedicationList from '../screens/MedicationList';
+import Profile from '../screens/Profile';
const AppStackBottomTab = createBottomTabNavigator();
export function AppStackBottomTabNavigator() {
return (
-
+
,
- tabBarLabel: () => Landing
+ tabBarIcon: ({ color }) => ,
+ tabBarLabel: () =>
}}
component={MedicationList}
/>
,
- tabBarLabel: () => Group
+ tabBarIcon: ({ color }) => ,
+ tabBarLabel: () =>
}}
- component={GroupScreen}
+ component={MedicationList}
+ />
+ ,
+ tabBarLabel: () =>
+ }}
+ component={MedicationList}
+ />
+ ,
+ tabBarLabel: () =>
+ }}
+ component={Profile}
/>
);
diff --git a/client/screens/Groups.tsx b/client/screens/Groups.tsx
deleted file mode 100644
index 51a002a..0000000
--- a/client/screens/Groups.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { Button, StyleSheet, Text, TextInput, View } from 'react-native';
-
-import {
- addUserToCareGroup,
- createCareGroup,
- getGroupMembers
-} from '../services/group';
-
-export default function GroupScreen() {
- const [groupName, setGroupName] = useState('');
- const [userId, setUserId] = useState('');
- const [role, setRole] = useState('');
- // Setting this default 1 for now (to test get members),
- // but will automically be set to the new group id when a group is created
- const [groupId, setGroupId] = useState('1');
- const [members, setMembers] = useState([]);
-
- // Fetch group members when groupId changes
- useEffect(() => {
- if (groupId) {
- getGroupMembers(groupId)
- .then((data) => setMembers(data))
- .catch((error) => console.error('Error fetching members:', error));
- }
- }, [groupId]);
-
- // Create a new care group
- const handleCreateGroup = async () => {
- try {
- const newGroupId = await createCareGroup(groupName);
- setGroupId(newGroupId.toString());
- } catch (error) {
- console.error('Error creating group:', error);
- }
- };
-
- // Add a user to the care group
- const handleAddUser = async () => {
- try {
- await addUserToCareGroup(userId, groupId, role);
- getGroupMembers(groupId)
- .then((data) => setMembers(data))
- .catch((error) => console.error('Error fetching members:', error));
- } catch (error) {
- console.error('Error adding user to group:', error);
- }
- };
-
- // Rendering (scuffed but it works for now)
- return (
-
-
-
-
-
-
-
-
-
- Group Members
- {members ? (
- members.map((member) => {member})
- ) : (
- No members
- )}
-
-
- );
-}
-
-// uhhhhh
-const styles = StyleSheet.create({
- input: {
- width: '100%',
- marginVertical: 10,
- paddingVertical: 10,
- paddingHorizontal: 20,
- borderWidth: 1,
- borderColor: '#ccc',
- borderRadius: 5
- },
- container: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center'
- }
-});
diff --git a/client/screens/Profile.tsx b/client/screens/Profile.tsx
new file mode 100644
index 0000000..8cc764b
--- /dev/null
+++ b/client/screens/Profile.tsx
@@ -0,0 +1,74 @@
+import React, { useState } from 'react';
+import { ActivityIndicator, Text, View } from 'react-native';
+
+import { CircleCard } from '../components/profile/CircleCard';
+import { Group } from '../components/profile/Group';
+import { Header } from '../components/profile/Header';
+import { useCareWalletContext } from '../contexts/CareWalletContext';
+import { useAuth } from '../services/auth';
+import { useGroup } from '../services/group';
+import { useUsers } from '../services/user';
+
+export default function Profile() {
+ const { user: signedInUser, group } = useCareWalletContext();
+ const [activeUser, setActiveUser] = useState(signedInUser.userID);
+ const { roles, rolesAreLoading } = useGroup(group.groupID);
+ const { users, usersAreLoading } = useUsers(
+ roles?.map((role) => role.user_id) ?? []
+ );
+
+ const { signOutMutation } = useAuth();
+
+ if (rolesAreLoading || usersAreLoading) {
+ return (
+
+
+ Loading...
+
+ );
+ }
+
+ if (!roles || !users) {
+ return (
+
+ Could Not Load Profile...
+
+ );
+ }
+
+ // TODO: Connext with task screen
+ // TODO: Get task number from backend
+ // TODO: Connext with patient information screen
+ // TODO: Add ability to change user view if I click on another user?
+ return (
+
+ user.user_id === activeUser)}
+ role={roles.find((role) => role.user_id === activeUser)}
+ />
+
+
+
+ Your Tasks
+ 4
+
+
+
+
+
+
+
+
+
+ signOutMutation()} />
+
+
+ );
+}
diff --git a/client/services/group.ts b/client/services/group.ts
index 71f2bd2..56bc6a8 100644
--- a/client/services/group.ts
+++ b/client/services/group.ts
@@ -1,45 +1,33 @@
+import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
+import { GroupRole } from '../types/group';
import { api_url } from './api-links';
-// Create a care group
-export const createCareGroup = async (groupName: string): Promise => {
- try {
- const response = await axios.post(
- `${api_url}/care-groups/${groupName}`,
- {}
- );
- console.log('response:', response);
- return response.data;
- } catch (error) {
- console.error('Error creating care group:', error);
- throw error;
- }
+const getUserGroup = async (userId: string): Promise => {
+ const { data } = await axios.get(`${api_url}/group/member/${userId}`);
+ return data;
};
-// Add a user to a care group
-export const addUserToCareGroup = async (
- userId: string,
- groupId: string,
- role: string
-): Promise => {
- try {
- const response = await axios.post(
- `${api_url}/care-groups/addUser/${userId}/${groupId}/${role}`,
- {}
- );
- console.log('response:', response);
- return response.data;
- } catch (error) {
- console.error('Error creating care group:', error);
- throw error;
- }
+const getGroupRoles = async (groupId: number): Promise => {
+ const { data } = await axios.get(`${api_url}/group/${groupId}/roles`);
+ return data;
};
-// Get all members of a care group
-export const getGroupMembers = async (groupId: string): Promise => {
- const response = await axios.get(
- `${api_url}/care-groups/get-members/${groupId}`
- );
- return response.data;
+export const useUserGroup = (userId: string) => {
+ const { data: groupId, isLoading: groupIdIsLoading } = useQuery({
+ queryKey: ['groupId', userId],
+ queryFn: () => getUserGroup(userId)
+ });
+
+ return { groupId, groupIdIsLoading };
+};
+
+export const useGroup = (groupId: number) => {
+ const { data: roles, isLoading: rolesAreLoading } = useQuery({
+ queryKey: ['roles', groupId],
+ queryFn: () => getGroupRoles(groupId)
+ });
+
+ return { roles, rolesAreLoading };
};
diff --git a/client/services/medication.ts b/client/services/medication.ts
index 1b7669a..fbfb334 100644
--- a/client/services/medication.ts
+++ b/client/services/medication.ts
@@ -26,8 +26,7 @@ export const useMedication = () => {
Medication[]
>({
queryKey: ['medList'], // if querying with a value add values here ex. ['medList', {id}]
- queryFn: getAllMedications,
- refetchInterval: 20000 // Will refetch the data every 20 seconds
+ queryFn: getAllMedications
});
const { mutate: addMedicationMutation } = useMutation({
diff --git a/client/services/user.ts b/client/services/user.ts
new file mode 100644
index 0000000..c03490d
--- /dev/null
+++ b/client/services/user.ts
@@ -0,0 +1,63 @@
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import axios from 'axios';
+
+import { User } from '../types/user';
+import { api_url } from './api-links';
+
+const getUser = async (userId: string): Promise => {
+ const { data } = await axios.get(`${api_url}/user/${userId}`);
+ return data;
+};
+
+const getUsers = async (userIds: string[]): Promise => {
+ const { data } = await axios.get(`${api_url}/user`, {
+ params: { userIDs: userIds.join(',') }
+ });
+
+ return data;
+};
+
+const updateUser = async (user: User): Promise => {
+ const { data } = await axios.put(`${api_url}/user/${user.user_id}`, user);
+ return data;
+};
+
+const addUser = async (user: User): Promise => {
+ const { data } = await axios.post(`${api_url}/user/${user.user_id}`, user);
+ return data;
+};
+
+export const useUser = (userId: string) => {
+ const queryClient = useQueryClient();
+
+ const { data: user, isLoading: userIsLoading } = useQuery({
+ queryKey: ['user', userId],
+ queryFn: () => getUser(userId)
+ });
+
+ const { mutate: updateUserMutation } = useMutation({
+ mutationFn: (user: User) => updateUser(user),
+ onSuccess: (data) => {
+ queryClient.setQueryData(['user', userId], data);
+ }
+ });
+
+ const { mutate: addUserMutation } = useMutation({
+ mutationFn: (user: User) => addUser(user),
+ onSuccess: (data) => {
+ queryClient.setQueryData(['user', userId], data);
+ }
+ });
+
+ return { user, userIsLoading, updateUserMutation, addUserMutation };
+};
+
+export const useUsers = (userIds: string[]) => {
+ const { data: users, isLoading: usersAreLoading } = useQuery({
+ queryKey: ['users', userIds],
+ queryFn: () => getUsers(userIds),
+ enabled: userIds.length > 0
+ });
+
+ return { users, usersAreLoading };
+};
diff --git a/client/types/group.ts b/client/types/group.ts
index f240227..7b07b9e 100644
--- a/client/types/group.ts
+++ b/client/types/group.ts
@@ -3,3 +3,15 @@ export interface CareGroup {
group_name: string;
date_created: Date;
}
+
+export enum Role {
+ PATIENT = 'PATIENT',
+ PRIMARY = 'PRIMARY',
+ SECONDARY = 'SECONDARY'
+}
+
+export interface GroupRole {
+ group_id: number;
+ user_id: string;
+ role: Role;
+}
diff --git a/client/types/user.ts b/client/types/user.ts
new file mode 100644
index 0000000..ed6e396
--- /dev/null
+++ b/client/types/user.ts
@@ -0,0 +1,11 @@
+export interface User {
+ user_id: string;
+ first_name: string;
+ last_name: string;
+ email: string;
+ phone: string;
+ address: string;
+ pfp_s3_url: string;
+ device_id: string;
+ push_notification_enabled: boolean;
+}
diff --git a/docker-compose.yaml b/docker-compose.yaml
index cb181ad..dae2306 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,9 +1,9 @@
services:
db:
image: postgres:14.1-alpine
- restart: always
+ restart: unless-stopped
healthcheck:
- test: ['CMD', 'pg_isready', '-q', '-d', 'postgres', '-U', 'user']
+ test: ["CMD", "pg_isready", "-q", "-d", "postgres", "-U", "user"]
timeout: 45s
interval: 10s
retries: 10
@@ -12,7 +12,7 @@ services:
- POSTGRES_DB=carewallet
- POSTGRES_PASSWORD=pwd
ports:
- - '5434:5432'
+ - "5434:5432"
volumes:
- ./backend/db/migrations:/docker-entrypoint-initdb.d/
volumes: