From a30617ff5a1faed40721a8417b8462e441258177 Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Tue, 6 Feb 2024 11:49:23 -0500 Subject: [PATCH 01/22] fix(file-upload): refactor fileupload to use params instead of header Just found the params field today, this change is to go with general SWE practices that a header is a field of an HTTP request or response that passes additional context and metadata about the request or response. For example, a request message can use headers to indicate it's preferred media formats, while a response can use header to indicate the media format of the returned body. --- backend/docs/docs.go | 21 +++++++++++++++++-- backend/docs/swagger.json | 21 +++++++++++++++++-- backend/docs/swagger.yaml | 14 ++++++++++++- backend/models/file.go | 2 +- backend/schema/files/routes.go | 29 ++++++++++++++------------- client/components/DocPickerButton.tsx | 6 ++++-- client/contexts/CareWalletContext.tsx | 2 +- client/contexts/types.ts | 2 +- client/package.json | 2 +- client/screens/Medication.tsx | 2 ++ client/services/file.ts | 6 +++--- 11 files changed, 79 insertions(+), 28 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index d976f43..464192a 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -29,6 +29,20 @@ const docTemplate = `{ "name": "file_data", "in": "formData", "required": true + }, + { + "type": "string", + "description": "The userId of the uploader", + "name": "upload_by", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "The groupId of the uploader", + "name": "group_id", + "in": "formData", + "required": true } ], "responses": { @@ -39,7 +53,10 @@ const docTemplate = `{ } }, "400": { - "description": "Bad Request" + "description": "Bad Request", + "schema": { + "type": "string" + } } } } @@ -85,7 +102,7 @@ const docTemplate = `{ "type": "integer" }, "upload_by": { - "type": "integer" + "type": "string" }, "upload_date": { "type": "string" diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index c924157..2d08f15 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -22,6 +22,20 @@ "name": "file_data", "in": "formData", "required": true + }, + { + "type": "string", + "description": "The userId of the uploader", + "name": "upload_by", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "The groupId of the uploader", + "name": "group_id", + "in": "formData", + "required": true } ], "responses": { @@ -32,7 +46,10 @@ } }, "400": { - "description": "Bad Request" + "description": "Bad Request", + "schema": { + "type": "string" + } } } } @@ -78,7 +95,7 @@ "type": "integer" }, "upload_by": { - "type": "integer" + "type": "string" }, "upload_date": { "type": "string" diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 25aaeba..382cc9d 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -13,7 +13,7 @@ definitions: task_id: type: integer upload_by: - type: integer + type: string upload_date: type: string type: object @@ -39,6 +39,16 @@ paths: name: file_data required: true type: file + - description: The userId of the uploader + in: formData + name: upload_by + required: true + type: string + - description: The groupId of the uploader + in: formData + name: group_id + required: true + type: integer responses: "200": description: OK @@ -46,6 +56,8 @@ paths: $ref: '#/definitions/models.File' "400": description: Bad Request + schema: + type: string summary: Upload a file tags: - file diff --git a/backend/models/file.go b/backend/models/file.go index 5f66fab..b0878e4 100644 --- a/backend/models/file.go +++ b/backend/models/file.go @@ -4,7 +4,7 @@ type File struct { FileID int `json:"file_id"` FileName string `json:"file_name"` GroupID int `json:"group_id"` - UploadBy int `json:"upload_by"` + UploadBy string `json:"upload_by"` UploadDate string `json:"upload_date"` FileSize int64 `json:"file_size"` TaskID int `json:"task_id"` diff --git a/backend/schema/files/routes.go b/backend/schema/files/routes.go index f32a874..0aaafb4 100644 --- a/backend/schema/files/routes.go +++ b/backend/schema/files/routes.go @@ -17,35 +17,28 @@ func GetFileGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { files := v1.Group("files") { - files.POST("/upload", c.UploadFileRoute) + files.POST("/upload", c.UploadFile) } return files } -// GetFiles godoc +// UploadFile godoc // // @summary Upload a file // @description Upload a file to database and S3 bucket // @tags file +// // @param file_data formData file true "Body with file zip" +// @param upload_by formData string true "The userId of the uploader" +// @param group_id formData int true "The groupId of the uploader" // // @success 200 {object} models.File -// @failure 400 +// @failure 400 {object} string // @router /files/upload [post] -func (pg *PgModel) UploadFileRoute(c *gin.Context) { - // TODO: Ensure Swagger Knows about the bad request returns!!! +func (pg *PgModel) UploadFile(c *gin.Context) { var file models.File - if err := c.Bind(&file); err != nil { - c.JSON(http.StatusBadRequest, "Failed to process the request") - return - } - userID := c.GetHeader("user_id") - groupID := c.GetHeader("group_id") - file.UploadBy, _ = strconv.Atoi(userID) - file.GroupID, _ = strconv.Atoi(groupID) - form, err := c.MultipartForm() if err != nil { c.JSON(http.StatusBadRequest, "Failed to get form") @@ -53,6 +46,14 @@ func (pg *PgModel) UploadFileRoute(c *gin.Context) { } fileResponse := form.File["file_data"][0] + file.UploadBy = form.Value["upload_by"][0] + file.GroupID, err = strconv.Atoi(form.Value["group_id"][0]) + + if err != nil { + c.JSON(http.StatusBadRequest, "Failed to parse groupid") + return + } + fileData, err := fileResponse.Open() if err != nil { c.JSON(http.StatusBadRequest, "Failed to open file") diff --git a/client/components/DocPickerButton.tsx b/client/components/DocPickerButton.tsx index bc9295b..6dd71e3 100644 --- a/client/components/DocPickerButton.tsx +++ b/client/components/DocPickerButton.tsx @@ -2,9 +2,11 @@ import React, { useState } from 'react'; import { View, Button, Text } from 'react-native'; import * as DocumentPicker from 'expo-document-picker'; import { uploadFile } from '../services/file'; +import { useCareWalletContext } from '../contexts/CareWalletContext'; export default function DocPickerButton() { const [pickedDocument, setPickedDocument] = useState(null); + const { user, group } = useCareWalletContext(); const pickDocument = async () => { try { @@ -16,8 +18,8 @@ export default function DocPickerButton() { if (result.canceled === false) { // TODO get userID and groupID - const userID = 0; - const groupID = 0; + const userID = user.userID; + const groupID = group.groupID; await uploadFile(result.assets[0], userID, groupID); } } catch (err) { diff --git a/client/contexts/CareWalletContext.tsx b/client/contexts/CareWalletContext.tsx index 55347ca..b26ecd4 100644 --- a/client/contexts/CareWalletContext.tsx +++ b/client/contexts/CareWalletContext.tsx @@ -23,7 +23,7 @@ export default function CareWalletProvider({ children }: { children: any }) { setUser(signedInUser); }); setGroup({ - groupID: 'TEMP - REPLACE WITH ACTUAL', + groupID: -1, role: 'TEMP - REPLACE WITH ACTUAL' }); }, []); diff --git a/client/contexts/types.ts b/client/contexts/types.ts index 82969df..81c0d92 100644 --- a/client/contexts/types.ts +++ b/client/contexts/types.ts @@ -4,6 +4,6 @@ export interface User { } export interface Group { - groupID: string; + groupID: number; role: string; // TODO: update to enum } diff --git a/client/package.json b/client/package.json index 33dd1c9..56d991e 100644 --- a/client/package.json +++ b/client/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@firebase/auth": "^1.5.1", - "@react-native-async-storage/async-storage": "^1.21.0", + "@react-native-async-storage/async-storage": "1.18.2", "@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/free-brands-svg-icons": "^6.5.1", "@fortawesome/free-regular-svg-icons": "^6.5.1", diff --git a/client/screens/Medication.tsx b/client/screens/Medication.tsx index 0ddfbc0..00244ca 100644 --- a/client/screens/Medication.tsx +++ b/client/screens/Medication.tsx @@ -5,6 +5,7 @@ import { Medication } from '../types/medication'; import { useCareWalletContext } from '../contexts/CareWalletContext'; import ClickableCard from '../components/Card'; import PopupModal from '../components/PopupModal'; +import DocPickerButton from '../components/DocPickerButton'; export default function MedList() { const [medications, setMedications] = React.useState(); @@ -23,6 +24,7 @@ export default function MedList() { ID: {selectedMed?.medication_id} + {medications && medications.map((med, index) => ( diff --git a/client/services/file.ts b/client/services/file.ts index 05cba91..dd971fc 100644 --- a/client/services/file.ts +++ b/client/services/file.ts @@ -5,7 +5,7 @@ import * as FileSystem from 'expo-file-system'; export const uploadFile = async ( file: DocumentPicker.DocumentPickerAsset, - userId: number, + userId: string, groupId: number ) => { const uploadResumable = FileSystem.createUploadTask( @@ -15,8 +15,8 @@ export const uploadFile = async ( httpMethod: 'POST', uploadType: FileSystem.FileSystemUploadType.MULTIPART, fieldName: 'file_data', - headers: { - user_id: userId.toString(), + parameters: { + upload_by: userId, group_id: groupId.toString() } } From 29d6371752cd609ba57dd0041f9fc4f191b92c79 Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Tue, 6 Feb 2024 17:51:55 -0500 Subject: [PATCH 02/22] feat: I NO LONGER NEED TO LOGIN --- client/contexts/CareWalletContext.tsx | 10 ++++++---- client/screens/Login.tsx | 15 +++++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/client/contexts/CareWalletContext.tsx b/client/contexts/CareWalletContext.tsx index b26ecd4..6c325b2 100644 --- a/client/contexts/CareWalletContext.tsx +++ b/client/contexts/CareWalletContext.tsx @@ -20,11 +20,13 @@ export default function CareWalletProvider({ children }: { children: any }) { userID: user?.uid ?? '', userEmail: user?.email ?? '' }; + setUser(signedInUser); - }); - setGroup({ - groupID: -1, - role: 'TEMP - REPLACE WITH ACTUAL' + + setGroup({ + groupID: -1, + role: 'TEMP - REPLACE WITH ACTUAL' + }); }); }, []); diff --git a/client/screens/Login.tsx b/client/screens/Login.tsx index 71d8cf5..e8ec520 100644 --- a/client/screens/Login.tsx +++ b/client/screens/Login.tsx @@ -4,6 +4,8 @@ import { logIn } from '../services/auth/login'; import { signUp } from '../services/auth/signup'; import { useNavigation } from '@react-navigation/native'; import { AppStackNavigation } from '../navigation/AppNavigation'; +import { onAuthStateChanged } from '@firebase/auth'; +import { auth } from '../firebase.config'; const LoginPage: React.FC = () => { const [email, setEmail] = useState(''); @@ -11,6 +13,15 @@ const LoginPage: React.FC = () => { const navigation = useNavigation(); + onAuthStateChanged(auth, (user) => { + if (user) { + navigation.navigate('MainNavScreens'); + return; + } + + navigation.navigate('Login'); + }); + const handleLogin = async () => { if (!email || !password) { Alert.alert('Error', 'Email and password are required'); @@ -21,8 +32,6 @@ const LoginPage: React.FC = () => { Alert.alert('Login Failed', result.substring(5).replaceAll('-', ' ')); } else { Alert.alert('Login Success', 'Welcome back!'); - // console.log('result: ', result); - navigation.navigate('MainNavScreens'); } }; @@ -36,8 +45,6 @@ const LoginPage: React.FC = () => { Alert.alert('Signup Failed', result.substring(5).replaceAll('-', ' ')); } else { Alert.alert('Signup Success', 'Welcome to the app!'); - // console.log('result: ', result); - navigation.navigate('MainNavScreens'); } }; From 6eac1ce79e3a1fc899cd5c4861dd0f89987c66b0 Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Wed, 7 Feb 2024 13:03:53 -0500 Subject: [PATCH 03/22] feat: fe requests using transquery --- backend/docs/docs.go | 32 +++++ backend/docs/swagger.json | 32 +++++ backend/docs/swagger.yaml | 21 ++++ backend/schema/medication/routes.go | 31 +++++ backend/schema/medication/transactions.go | 12 ++ client/App.tsx | 19 +-- client/components/Card.tsx | 6 - client/package.json | 7 +- client/screens/Medication.tsx | 135 ++++++++++++++++++---- client/services/file.ts | 2 +- client/services/medication.ts | 42 +++++++ 11 files changed, 298 insertions(+), 41 deletions(-) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index 464192a..1a72f2e 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -79,6 +79,38 @@ const docTemplate = `{ } } } + }, + "post": { + "description": "add a medication to a users medlist", + "tags": [ + "medications" + ], + "summary": "add a medication", + "parameters": [ + { + "description": "a medication", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Medication" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Medication" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } } } }, diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index 2d08f15..4131d4c 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -72,6 +72,38 @@ } } } + }, + "post": { + "description": "add a medication to a users medlist", + "tags": [ + "medications" + ], + "summary": "add a medication", + "parameters": [ + { + "description": "a medication", + "name": "_", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Medication" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Medication" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + } + } } } }, diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 382cc9d..149a228 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -74,4 +74,25 @@ paths: summary: Get All Meds tags: - medications + post: + description: add a medication to a users medlist + parameters: + - description: a medication + in: body + name: _ + required: true + schema: + $ref: '#/definitions/models.Medication' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Medication' + "400": + description: Bad Request + schema: + type: string + summary: add a medication + tags: + - medications swagger: "2.0" diff --git a/backend/schema/medication/routes.go b/backend/schema/medication/routes.go index 5f7f3f0..ce835e6 100644 --- a/backend/schema/medication/routes.go +++ b/backend/schema/medication/routes.go @@ -1,6 +1,8 @@ package medication import ( + "carewallet/models" + "fmt" "net/http" "github.com/gin-gonic/gin" @@ -16,6 +18,7 @@ func GetMedicationGroup(v1 *gin.RouterGroup, c *PgModel) *gin.RouterGroup { medications := v1.Group("medications") { medications.GET("", c.GetMedications) + medications.POST("", c.AddMedications) } return medications @@ -37,3 +40,31 @@ func (pg *PgModel) GetMedications(c *gin.Context) { c.JSON(http.StatusOK, med) } + +// AddMedications godoc +// +// @summary add a medication +// @description add a medication to a users medlist +// @tags medications +// +// @param _ body models.Medication true "a medication" +// +// @success 200 {object} models.Medication +// @failure 400 {object} string +// @router /medications [post] +func (pg *PgModel) AddMedications(c *gin.Context) { + var medbody models.Medication + + c.Bind(&medbody) + + fmt.Println(medbody.MedicationID) + fmt.Println(medbody.MedicationName) + med, err := AddMedToDB(pg.Conn, medbody) + + if err != nil { + 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 a90471d..cfcae35 100644 --- a/backend/schema/medication/transactions.go +++ b/backend/schema/medication/transactions.go @@ -34,3 +34,15 @@ func GetAllMedsFromDB(pool *pgx.Conn) ([]models.Medication, error) { return results, nil } + +func AddMedToDB(pool *pgx.Conn, med models.Medication) (models.Medication, error) { + err := pool.QueryRow("INSERT INTO medication (medication_id, medication_name) VALUES ($1, $2) RETURNING medication_id;", + med.MedicationID, med.MedicationName).Scan() + + if err != nil { + print(err.Error()) + return models.Medication{}, err + } + + return med, nil +} diff --git a/client/App.tsx b/client/App.tsx index 23205c7..2813a59 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -3,15 +3,20 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import Router from './navigation/Router'; import CareWalletProvider from './contexts/CareWalletContext'; import { PaperProvider } from 'react-native-paper'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; export default function App() { + const queryClient = new QueryClient(); + return ( - - - - - - - + + + + + + + + + ); } diff --git a/client/components/Card.tsx b/client/components/Card.tsx index 527f00d..9388210 100644 --- a/client/components/Card.tsx +++ b/client/components/Card.tsx @@ -8,12 +8,6 @@ interface ClickableCardProps { children?: JSX.Element[] | JSX.Element; } -const StyledModal = styled(Card.Title, { - props: { - titleStyle: true - } -}); - export const ClickableCard: React.FC = ({ title, onPress, diff --git a/client/package.json b/client/package.json index 56d991e..7320f05 100644 --- a/client/package.json +++ b/client/package.json @@ -13,15 +13,16 @@ }, "dependencies": { "@firebase/auth": "^1.5.1", - "@react-native-async-storage/async-storage": "1.18.2", "@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/free-brands-svg-icons": "^6.5.1", "@fortawesome/free-regular-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-native-fontawesome": "^0.3.0", + "@react-native-async-storage/async-storage": "1.18.2", "@react-navigation/bottom-tabs": "^6.5.11", "@react-navigation/native": "^6.1.9", "@react-navigation/native-stack": "^6.9.17", + "@tanstack/react-query": "^5.18.1", "axios": "^1.6.4", "expo": "~49.0.13", "expo-document-picker": "~11.5.4", @@ -33,8 +34,8 @@ "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" + "react-native-screens": "~3.22.0", + "react-native-svg-transformer": "^1.3.0" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/client/screens/Medication.tsx b/client/screens/Medication.tsx index 00244ca..b2e319e 100644 --- a/client/screens/Medication.tsx +++ b/client/screens/Medication.tsx @@ -1,20 +1,74 @@ import * as React from 'react'; -import { View, Text, ScrollView } from 'react-native'; -import { getAllMedications } from '../services/medication'; +import { + View, + Text, + FlatList, + ListRenderItem, + Pressable, + TextInput +} from 'react-native'; import { Medication } from '../types/medication'; import { useCareWalletContext } from '../contexts/CareWalletContext'; import ClickableCard from '../components/Card'; import PopupModal from '../components/PopupModal'; import DocPickerButton from '../components/DocPickerButton'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { Divider } from 'react-native-paper'; +import { useCallback, useState } from 'react'; +import { + addMedication, + getAllMedications, + useMedicationQuery +} from '../services/medication'; export default function MedList() { - const [medications, setMedications] = React.useState(); - const [selectedMed, setSelectedMed] = React.useState(); + const [selectedMed, setSelectedMed] = useState(); + const [name, setName] = useState(''); + const [id, setId] = useState(''); + const [visible, setVisible] = useState(false); + const [newMedVisible, setNewMedVisible] = useState(false); + const { user, group } = useCareWalletContext(); - const [visible, setVisible] = React.useState(false); - React.useEffect(() => { - getAllMedications().then((med) => setMedications(med)); - }, []); + const { + medications, + medicationsIsError, + medicationsIsLoading, + addMedicationMutation + } = useMedicationQuery(); + + const onListItemPress = (med: Medication) => { + console.log(med.medication_id); + setSelectedMed(med); + setVisible(true); + }; + + const renderMedicationList = useCallback>( + ({ item }) => { + return ( + onListItemPress(item)} + > + ID: {item.medication_id} + + ); + }, + [onListItemPress] + ); + + if (medicationsIsLoading) + return ( + + Loading... + + ); + + if (medicationsIsError) + return ( + + Could Not Load Medications List + + ); return ( @@ -24,22 +78,55 @@ export default function MedList() { ID: {selectedMed?.medication_id} - - - {medications && - medications.map((med, index) => ( - { - setSelectedMed(med); - setVisible(true); - }} - > - ID: {med.medication_id} - - ))} - + + + ID: + setId(val)} + value={`${id}`} + /> + + + Name: + setName(val)} + value={`${name}`} + inputMode="text" + /> + + + { + addMedicationMutation({ + medication_id: parseInt(id), + medication_name: name + }); + setNewMedVisible(false); + }} + > + Add Medication + + + + + setNewMedVisible(true)} + > + Add Medication + + + + `id: ${item.medication_id}`} + ItemSeparatorComponent={() => } + /> {user && group && ( The user id is: {user.userID} diff --git a/client/services/file.ts b/client/services/file.ts index dd971fc..70d464c 100644 --- a/client/services/file.ts +++ b/client/services/file.ts @@ -1,4 +1,4 @@ -import axios, { HttpStatusCode } from 'axios'; +import { HttpStatusCode } from 'axios'; import { api_url } from './api-links'; import * as DocumentPicker from 'expo-document-picker'; import * as FileSystem from 'expo-file-system'; diff --git a/client/services/medication.ts b/client/services/medication.ts index d70b0ab..ef799d9 100644 --- a/client/services/medication.ts +++ b/client/services/medication.ts @@ -1,8 +1,50 @@ import axios from 'axios'; import { api_url } from './api-links'; import { Medication } from '../types/medication'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; export const getAllMedications = async (): Promise => { + await new Promise((r) => setTimeout(r, 2000)); // this is just to show is loading (DONT ADD ANYWHERE ELSE) const response = await axios.get(`${api_url}/medications`); return response.data; }; + +export const addMedication = async (med: Medication) => { + await axios.post(`${api_url}/medications`, { + medication_id: med.medication_id, + medication_name: med.medication_name + }); +}; + +export function useMedicationQuery() { + const queryClient = useQueryClient(); + + const { + data: medications, + isLoading: medicationsIsLoading, + isError: medicationsIsError + } = useQuery({ + queryKey: ['medList'], // if querying with a value add values here ex. ['medList', {search}] + queryFn: getAllMedications, + staleTime: 5000, // marks the data is obsolete or stale + refetchInterval: 5000 // Will refetch the data every 5 seconds + }); + + const { mutate: addMedicationMutation } = useMutation({ + mutationFn: (med: Medication) => addMedication(med), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ['medList'], + exact: true, + refetchType: 'active' + }); + } + }); + + return { + medications, + medicationsIsLoading, + medicationsIsError, + addMedicationMutation + }; +} From a630f9be93ef55d0bf389b4f1404e71f6aed5386 Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Wed, 7 Feb 2024 13:47:17 -0500 Subject: [PATCH 04/22] feat: install clsx bc I like it w/ tailwind --- backend/schema/medication/routes.go | 4 -- backend/schema/medication/transactions.go | 2 +- client/package.json | 1 + client/screens/Medication.tsx | 54 +++++++++++++---------- client/services/medication.ts | 31 ++++++++----- 5 files changed, 52 insertions(+), 40 deletions(-) diff --git a/backend/schema/medication/routes.go b/backend/schema/medication/routes.go index ce835e6..885274e 100644 --- a/backend/schema/medication/routes.go +++ b/backend/schema/medication/routes.go @@ -2,7 +2,6 @@ package medication import ( "carewallet/models" - "fmt" "net/http" "github.com/gin-gonic/gin" @@ -54,11 +53,8 @@ func (pg *PgModel) GetMedications(c *gin.Context) { // @router /medications [post] func (pg *PgModel) AddMedications(c *gin.Context) { var medbody models.Medication - c.Bind(&medbody) - fmt.Println(medbody.MedicationID) - fmt.Println(medbody.MedicationName) med, err := AddMedToDB(pg.Conn, medbody) if err != nil { diff --git a/backend/schema/medication/transactions.go b/backend/schema/medication/transactions.go index cfcae35..1416f0e 100644 --- a/backend/schema/medication/transactions.go +++ b/backend/schema/medication/transactions.go @@ -37,7 +37,7 @@ func GetAllMedsFromDB(pool *pgx.Conn) ([]models.Medication, error) { func AddMedToDB(pool *pgx.Conn, med models.Medication) (models.Medication, error) { err := pool.QueryRow("INSERT INTO medication (medication_id, medication_name) VALUES ($1, $2) RETURNING medication_id;", - med.MedicationID, med.MedicationName).Scan() + med.MedicationID, med.MedicationName).Scan(&med.MedicationID) if err != nil { print(err.Error()) diff --git a/client/package.json b/client/package.json index 7320f05..a2646e0 100644 --- a/client/package.json +++ b/client/package.json @@ -24,6 +24,7 @@ "@react-navigation/native-stack": "^6.9.17", "@tanstack/react-query": "^5.18.1", "axios": "^1.6.4", + "clsx": "^2.1.0", "expo": "~49.0.13", "expo-document-picker": "~11.5.4", "expo-file-system": "~15.4.5", diff --git a/client/screens/Medication.tsx b/client/screens/Medication.tsx index b2e319e..2fa0d8e 100644 --- a/client/screens/Medication.tsx +++ b/client/screens/Medication.tsx @@ -12,14 +12,10 @@ import { useCareWalletContext } from '../contexts/CareWalletContext'; import ClickableCard from '../components/Card'; import PopupModal from '../components/PopupModal'; import DocPickerButton from '../components/DocPickerButton'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Divider } from 'react-native-paper'; import { useCallback, useState } from 'react'; -import { - addMedication, - getAllMedications, - useMedicationQuery -} from '../services/medication'; +import useMedication from '../services/medication'; +import clsx from 'clsx'; export default function MedList() { const [selectedMed, setSelectedMed] = useState(); @@ -27,33 +23,39 @@ export default function MedList() { const [id, setId] = useState(''); const [visible, setVisible] = useState(false); const [newMedVisible, setNewMedVisible] = useState(false); + const [userGroupVisible, setUserGroupVisible] = useState(false); const { user, group } = useCareWalletContext(); + const { medications, medicationsIsError, medicationsIsLoading, addMedicationMutation - } = useMedicationQuery(); - - const onListItemPress = (med: Medication) => { - console.log(med.medication_id); - setSelectedMed(med); - setVisible(true); - }; + } = useMedication(); const renderMedicationList = useCallback>( - ({ item }) => { + ({ item, index }) => { return ( onListItemPress(item)} + onPress={() => { + setSelectedMed(item); + setVisible(true); + }} > - ID: {item.medication_id} + + ID: {item.medication_id} + ); }, - [onListItemPress] + [] ); if (medicationsIsLoading) @@ -97,7 +99,6 @@ export default function MedList() { inputMode="text" /> - { @@ -127,14 +128,19 @@ export default function MedList() { keyExtractor={(item) => `id: ${item.medication_id}`} ItemSeparatorComponent={() => } /> - {user && group && ( + setUserGroupVisible(true)}> + Show User and Group Info + + - The user id is: {user.userID} - The user email is: {user.userEmail} - The group id is: {group.groupID} - The group role is: {group.role} + User ID: {user.userID} + + User Email: {user.userEmail} + + Group ID: {group.groupID} + Group Role: {group.role} - )} + ); } diff --git a/client/services/medication.ts b/client/services/medication.ts index ef799d9..67f2e12 100644 --- a/client/services/medication.ts +++ b/client/services/medication.ts @@ -1,30 +1,39 @@ import axios from 'axios'; -import { api_url } from './api-links'; import { Medication } from '../types/medication'; +import { api_url } from './api-links'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -export const getAllMedications = async (): Promise => { +/** + * Get all of the medications from the DB + * @returns All Medications from the DB + */ +const getAllMedications = async (): Promise => { await new Promise((r) => setTimeout(r, 2000)); // this is just to show is loading (DONT ADD ANYWHERE ELSE) const response = await axios.get(`${api_url}/medications`); return response.data; }; -export const addMedication = async (med: Medication) => { +/** + * Add a medication to the medication db + * @param med the medication to add + */ +const addMedication = async (med: Medication) => { await axios.post(`${api_url}/medications`, { medication_id: med.medication_id, medication_name: med.medication_name }); }; -export function useMedicationQuery() { +export default function useMedication() { const queryClient = useQueryClient(); const { data: medications, isLoading: medicationsIsLoading, - isError: medicationsIsError + isError: medicationsIsError, + refetch: medicationsRefetch } = useQuery({ - queryKey: ['medList'], // if querying with a value add values here ex. ['medList', {search}] + queryKey: ['medList'], // if querying with a value add values here ex. ['medList', {id}] queryFn: getAllMedications, staleTime: 5000, // marks the data is obsolete or stale refetchInterval: 5000 // Will refetch the data every 5 seconds @@ -33,11 +42,11 @@ export function useMedicationQuery() { const { mutate: addMedicationMutation } = useMutation({ mutationFn: (med: Medication) => addMedication(med), onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['medList'], - exact: true, - refetchType: 'active' - }); + queryClient + .invalidateQueries({ + queryKey: ['medList'] + }) + .then(() => medicationsRefetch()); } }); From dfba554ad26d5da1a28cfee5777d7dc2e2d13cf6 Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Wed, 7 Feb 2024 19:48:06 -0500 Subject: [PATCH 05/22] refactor: general cleanup for fe demo --- client/screens/Medication.tsx | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/client/screens/Medication.tsx b/client/screens/Medication.tsx index 2fa0d8e..b9c42c2 100644 --- a/client/screens/Medication.tsx +++ b/client/screens/Medication.tsx @@ -19,14 +19,14 @@ import clsx from 'clsx'; export default function MedList() { const [selectedMed, setSelectedMed] = useState(); - const [name, setName] = useState(''); - const [id, setId] = useState(''); - const [visible, setVisible] = useState(false); + + const [newMedState, setNewMedState] = useState({ id: '', name: '' }); + + const [medVisible, setMedVisible] = useState(false); const [newMedVisible, setNewMedVisible] = useState(false); const [userGroupVisible, setUserGroupVisible] = useState(false); const { user, group } = useCareWalletContext(); - const { medications, medicationsIsError, @@ -41,7 +41,7 @@ export default function MedList() { title={item.medication_name} onPress={() => { setSelectedMed(item); - setVisible(true); + setMedVisible(true); }} > - + {selectedMed?.medication_name} @@ -82,29 +82,29 @@ export default function MedList() { - ID: + ID: setId(val)} - value={`${id}`} + onChangeText={(val) => setNewMedState({ ...newMedState, id: val })} + value={newMedState.id} /> - Name: + Name: setName(val)} - value={`${name}`} - inputMode="text" + onChangeText={(val) => + setNewMedState({ ...newMedState, name: val }) + } + value={newMedState.name} /> { addMedicationMutation({ - medication_id: parseInt(id), - medication_name: name + medication_id: parseInt(newMedState.id), + medication_name: newMedState.name }); setNewMedVisible(false); }} From 6d81b3bdf4539de6dfeb74902102338a0e47aff4 Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Thu, 8 Feb 2024 10:54:03 -0500 Subject: [PATCH 06/22] docs: readme changes --- README.md | 140 ++++++++++---------------- client/components/DocPickerButton.tsx | 1 - 2 files changed, 53 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 4be88b8..83ce1b1 100644 --- a/README.md +++ b/README.md @@ -1,119 +1,85 @@

Care-Wallet

-
- A fullstack application for the Care-Wallet project -
-
Workflow Status
+
+
+ A fullstack application for the Care-Wallet project +
-## Set Up Your Development Environment +## Stack + +![Go](https://img.shields.io/badge/go-%2300ADD8.svg?style=for-the-badge&logo=go&logoColor=white) +![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&logo=postgresql&logoColor=white) -First, understand the tech stack: +![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) +![React Native](https://img.shields.io/badge/react_native-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) +![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white) -- The database is [PostGreSQL](https://www.postgresql.org/) and will be - containerized using [Docker](https://www.docker.com/). -- The backend is [Golang](https://go.dev/) -- The frontend is [TypeScript](https://www.typescriptlang.org/) with - [ReactNative](https://reactnative.dev/) and - [NativeWind](https://www.nativewind.dev) and uses [Expo](https://expo.dev/) as - a build tool +## Tools + +![Expo](https://img.shields.io/badge/expo-1C1E24?style=for-the-badge&logo=expo&logoColor=#D04A37) +![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) +![Swagger](https://img.shields.io/badge/-Swagger-%23Clojure?style=for-the-badge&logo=swagger&logoColor=white) + +## Development Enviroment Setup Before compiling and running our application, we need to install/setup several languages, package managers, and various tools. The installation process can vary, so follow the instructions for each item below! -- [Go](https://go.dev/doc/install) - our primary backend language -- [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) - our - package manager in the frontend -- [Docker Desktop](https://www.docker.com/products/docker-desktop/) - useful for - running docker -- [ngrok](https://ngrok.com/docs/getting-started/) - Allows us to easily connect - the frontend to backend code -- [expo-go](https://docs.expo.dev/get-started/expo-go/) - Expo allows mobile - devices to scan a QR code and view the code running on a mobile device +[Go](https://go.dev/doc/install) our primary backend language. -If you wish to simulate a phone from your computer either below will work: +[Node Package Manager](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) +our package manager in the frontend. -- [xcode](https://docs.expo.dev/workflow/ios-simulator/) - A simulator to view - the code on an iphone from a laptop -- [android studio](https://docs.expo.dev/workflow/android-studio-emulator/) - An - emulator to view the code on an android device from a laptop +[Docker](https://www.docker.com/get-started/) and +[Docker Desktop](https://www.docker.com/products/docker-desktop/) our Postgres +Database will be containerized in Docker. -If everything was successful, you can now compile and run the project! +[Ngrok](https://ngrok.com/docs/getting-started/) Allows us to easily connect the +frontend to backend code. -Next, understand the development tools that will make our lives easier: +[Swagger](https://github.com/swaggo/swag) visualizing the api and return types +of requests from the database. -- [Pre-commit](https://pre-commit.com) - standardizing code style and commits -- [Swagger](https://github.com/swaggo/swag) - visualizing the api and return - types of requests from the database -- [Task](https://taskfile.dev) - speeding up development by running long - commands in quick phrases - -Before committing anything, we need to install several tools. The installation -process can vary, so follow the instructions for each item below! - -- [pre-commit](https://pre-commit.com/#installation) - our development tool to - standardize development and ensure every file follows the same styling - guidelines. -- [commitizen](https://commitizen-tools.github.io/commitizen/#installation) - - This allows us to organize commits! By typing `cz c` instead of - `git commit -m ""`, we can organize each of our commits into one of nine - categories: - - **fix**: A bug fix. Correlates with PATCH in SemVer - - **feat**: A new feature. Correlates with MINOR in SemVer - - **docs**: Documentation only changes - - **style**: Changes that do not affect the meaning of the code (white-space, - formatting, missing semi-colons, etc) - - **refactor**: A code change that neither fixes a bug nor adds a feature - - **perf**: A code change that improves performance - - **test**: Adding missing or correcting existing tests - - **build**: Changes that affect the build system or external dependencies - (example scopes: pip, docker, npm) - - **ci**: Changes to our CI configuration files and scripts (example scopes: - GitLabCI) -- [Task](https://taskfile.dev/installation/) - our development tool to quickly - run commands that run, test, and clean files. - -## Extra Dependencies - -Install these into the backend directory - -1. go install github.com/swaggo/swag/cmd/swag@latest +[Task](https://taskfile.dev) speeding up development by running long commands in +quick phrases. -## Before Running +[Nodemon](https://www.npmjs.com/package/nodemon) a tool that watches code and +reloads the build if it sees changes. -1. Create a .env file in the root directory with a single line: - `EXPO_PUBLIC_API_DOMAIN=your-ngrok-static-domain-here` - - this will be used by the frontend services as well as the task file to - launch ngrok! +## Before Running -## Running the project +Create an .env file in the root directory: -1. Launch Docker Desktop -2. In the base of the repo: run `task start-docker` +``` + EXPO_PUBLIC_API_DOMAIN=your-ngrok-static-domain-here + AWS_ACCESS_KEY=your-aws-access-key-here + AWS_SECRET_KEY=your-aws-secret-key-here +``` - - This will run `docker-compose down` then `docker-compose up` +## Before Contributing -3. To build all of the dependencies of the project: run `task build` +Before contributing to the project, we need to install/setup several various +tools. The installation process can vary, so follow the instructions for each +item below! - - This will install both frontend and backend dependencies +- [Pre-commit](https://pre-commit.com) - standardizing code style and commits +- [Commitizen](https://commitizen-tools.github.io/commitizen/) - organizing our + commits into categories -4. Then, open a new tab to run commands in: run `task start-backend` +## Running The Project - - This will generate the swagger docs as well as start the backend +1. Launch Docker Desktop +2. In the base of the repo: run `task start-docker` +3. Then, open a new tab to run commands in: run `task start-backend` or + `task start-dev` - You can now view swagger: http://localhost:8080/swagger/index.html - -5. Next, in a new tab run `task start-ngrok` - -6. Finally, open one last new tab: run `task start-frontend` - - - This will start the frontend - -7. From here follow the prompt in step 6 to launch the frontend on your device - of choice +4. Next, in a new tab run `task start-ngrok` +5. Finally, open one last new tab: run `task start-frontend` diff --git a/client/components/DocPickerButton.tsx b/client/components/DocPickerButton.tsx index 6dd71e3..82b4a14 100644 --- a/client/components/DocPickerButton.tsx +++ b/client/components/DocPickerButton.tsx @@ -14,7 +14,6 @@ export default function DocPickerButton() { type: '*/*', copyToCacheDirectory: false }); - console.log('result', result); if (result.canceled === false) { // TODO get userID and groupID From e59c12306e5dc22bc6588cd4fd182502fef25090 Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Thu, 8 Feb 2024 10:56:17 -0500 Subject: [PATCH 07/22] ci: remove over complicated name --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 50c64a2..7ba5d95 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,4 +1,4 @@ -name: CI (Formatting/Linting, Testing, Building) +name: CI on: push: From 7c39eda3e1390803d8fe8a3fff29db2423ae0f23 Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Thu, 8 Feb 2024 11:18:52 -0500 Subject: [PATCH 08/22] docs: readme update to remove bullets from contributing section --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 83ce1b1..b8abd86 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,10 @@ Before contributing to the project, we need to install/setup several various tools. The installation process can vary, so follow the instructions for each item below! -- [Pre-commit](https://pre-commit.com) - standardizing code style and commits -- [Commitizen](https://commitizen-tools.github.io/commitizen/) - organizing our - commits into categories +[Pre-commit](https://pre-commit.com) standardizing code style and commits + +[Commitizen](https://commitizen-tools.github.io/commitizen/) organizing our +commits into categories ## Running The Project From bb203ce8a4b8726881e1155982543da6caa55ddd Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Thu, 8 Feb 2024 12:01:47 -0500 Subject: [PATCH 09/22] fix: remove extra fetch after post request for med noticed that it marked the data as stale which refetches it automatically then I also manually refetched --- .github/pull_request_template.md | 8 ++++---- client/services/medication.ts | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 00c55e2..f624424 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,7 +13,7 @@ describe how you tested the change. # Checklist -- [ ] I have performed a self-review of my code -- [ ] I have reached out to another developer to review my code -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] New and existing unit tests pass locally with my changes +- [] I have performed a self-review of my code +- [] I have reached out to another developer to review my code +- [] I have commented my code, particularly in hard-to-understand areas +- [] New and existing unit tests pass locally with my changes diff --git a/client/services/medication.ts b/client/services/medication.ts index 67f2e12..cc97733 100644 --- a/client/services/medication.ts +++ b/client/services/medication.ts @@ -35,18 +35,16 @@ export default function useMedication() { } = useQuery({ queryKey: ['medList'], // if querying with a value add values here ex. ['medList', {id}] queryFn: getAllMedications, - staleTime: 5000, // marks the data is obsolete or stale - refetchInterval: 5000 // Will refetch the data every 5 seconds + staleTime: 20000, // marks the data as obsolete or stale + refetchInterval: 20000 // Will refetch the data every 5 seconds }); const { mutate: addMedicationMutation } = useMutation({ mutationFn: (med: Medication) => addMedication(med), onSuccess: () => { - queryClient - .invalidateQueries({ - queryKey: ['medList'] - }) - .then(() => medicationsRefetch()); + queryClient.invalidateQueries({ + queryKey: ['medList'] // mark medlist as stale so it refetches + }); } }); @@ -54,6 +52,7 @@ export default function useMedication() { medications, medicationsIsLoading, medicationsIsError, + medicationsRefetch, addMedicationMutation }; } From f859873119dc71def54afad5b98807ed04c0574e Mon Sep 17 00:00:00 2001 From: Matt McCoy Date: Thu, 8 Feb 2024 19:07:26 -0500 Subject: [PATCH 10/22] refactor: use tan stack in file and login service routes --- backend/db/migrations/init.sql | 30 ++++++++ .../{Card.tsx => ClickableCard.tsx} | 7 +- client/components/DocPickerButton.tsx | 32 +++++---- client/components/PopupModal.tsx | 13 ++-- client/contexts/CareWalletContext.tsx | 2 +- .../navigation/AppStackBottomTabNavigator.tsx | 6 +- client/screens/Landing.tsx | 0 client/screens/Login.tsx | 26 +++---- .../{Medication.tsx => MedicationList.tsx} | 70 ++++++++----------- client/services/auth.ts | 57 +++++++++++++++ client/services/auth/authState.ts | 13 ---- client/services/auth/login.ts | 37 ---------- client/services/auth/signup.ts | 20 ------ client/services/auth/tests/login.test.ts | 30 -------- client/services/file.ts | 41 +++++++---- client/services/medication.ts | 32 ++++----- 16 files changed, 195 insertions(+), 221 deletions(-) rename client/components/{Card.tsx => ClickableCard.tsx} (74%) delete mode 100644 client/screens/Landing.tsx rename client/screens/{Medication.tsx => MedicationList.tsx} (79%) create mode 100644 client/services/auth.ts delete mode 100644 client/services/auth/authState.ts delete mode 100644 client/services/auth/login.ts delete mode 100644 client/services/auth/signup.ts delete mode 100644 client/services/auth/tests/login.test.ts diff --git a/backend/db/migrations/init.sql b/backend/db/migrations/init.sql index e5c0512..4f46a2e 100644 --- a/backend/db/migrations/init.sql +++ b/backend/db/migrations/init.sql @@ -122,3 +122,33 @@ VALUES (3, 'Medication C'), (4, 'Medication D'), (5, 'Medication E') + +INSERT INTO + care_group (group_id, group_name, date_created) +VALUES + (999, 'GROUP DE MATT', '2024-02-08 06:36:00'); + +INSERT INTO + users ( + user_id, + first_name, + last_name, + email, + phone, + address, + pfp_s3_url, + device_id, + push_notification_enabled + ) +VALUES + ( + 'fIoFY26mJnYWH8sNdfuVoxpnVnr1', + 'Matt', + 'McCoy', + '', + '', + '', + '', + '', + FALSE + ); diff --git a/client/components/Card.tsx b/client/components/ClickableCard.tsx similarity index 74% rename from client/components/Card.tsx rename to client/components/ClickableCard.tsx index 9388210..30e26a5 100644 --- a/client/components/Card.tsx +++ b/client/components/ClickableCard.tsx @@ -1,4 +1,3 @@ -import { styled } from 'nativewind'; import React from 'react'; import { Card } from 'react-native-paper'; @@ -8,11 +7,7 @@ interface ClickableCardProps { children?: JSX.Element[] | JSX.Element; } -export const ClickableCard: React.FC = ({ - title, - onPress, - children -}) => { +const ClickableCard = ({ title, onPress, children }: ClickableCardProps) => { return ( diff --git a/client/components/DocPickerButton.tsx b/client/components/DocPickerButton.tsx index 82b4a14..d8b4049 100644 --- a/client/components/DocPickerButton.tsx +++ b/client/components/DocPickerButton.tsx @@ -1,26 +1,27 @@ -import React, { useState } from 'react'; -import { View, Button, Text } from 'react-native'; +import React from 'react'; +import { View, Button } from 'react-native'; import * as DocumentPicker from 'expo-document-picker'; -import { uploadFile } from '../services/file'; import { useCareWalletContext } from '../contexts/CareWalletContext'; +import { useFile } from '../services/file'; -export default function DocPickerButton() { - const [pickedDocument, setPickedDocument] = useState(null); +const DocPickerButton = () => { const { user, group } = useCareWalletContext(); + const { uploadFileMutation } = useFile(); const pickDocument = async () => { try { - const result = await DocumentPicker.getDocumentAsync({ + await DocumentPicker.getDocumentAsync({ type: '*/*', copyToCacheDirectory: false + }).then((res) => { + if (!res.canceled) { + uploadFileMutation({ + file: res.assets[0], + userId: user.userID, + groupId: group.groupID + }); + } }); - - if (result.canceled === false) { - // TODO get userID and groupID - const userID = user.userID; - const groupID = group.groupID; - await uploadFile(result.assets[0], userID, groupID); - } } catch (err) { console.log('err', err); } @@ -29,7 +30,8 @@ export default function DocPickerButton() { return (