diff --git a/src/app/api/google-health/route.ts b/src/app/api/google-health/route.ts new file mode 100644 index 0000000..5bb83d5 --- /dev/null +++ b/src/app/api/google-health/route.ts @@ -0,0 +1,23 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getGoogleHealthData, saveHealthData } from "@/lib/google-health/data"; +import { authenticateGoogleHealth } from "@/lib/google-health/auth"; + +export async function GET(req: NextRequest) { + try { + const authToken = await authenticateGoogleHealth(req); + const healthData = await getGoogleHealthData(authToken); + return NextResponse.json({ healthData }); + } catch (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function POST(req: NextRequest) { + try { + const data = await req.json(); + const savedData = await saveHealthData(data); + return NextResponse.json({ savedData }); + } catch (error) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/src/components/source/source-add-screen.tsx b/src/components/source/source-add-screen.tsx index 49e02ef..e1e0725 100644 --- a/src/components/source/source-add-screen.tsx +++ b/src/components/source/source-add-screen.tsx @@ -1,5 +1,3 @@ -// TODO typesafe the form data -/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unused-vars */ 'use client'; import {Document, Page, pdfjs} from 'react-pdf'; @@ -21,6 +19,8 @@ import dynamic from "next/dynamic"; import {HealthDataParserVisionListResponse} from "@/app/api/health-data-parser/visions/route"; import {HealthDataGetResponse} from "@/app/api/health-data/[id]/route"; import {HealthDataParserDocumentListResponse} from "@/app/api/health-data-parser/documents/route"; +import { getAuthUrl, getToken } from '@/lib/google-health/auth'; +import { getGoogleHealthData } from '@/lib/google-health/data'; const Select = dynamic(() => import('react-select'), {ssr: false}); @@ -55,6 +55,7 @@ interface Field { interface AddSourceDialogProps { onFileUpload: (e: ChangeEvent) => void; onAddSymptoms: (date: string) => void; + onImportGoogleHealthData: () => void; isSetUpVisionParser: boolean; isSetUpDocumentParser: boolean; } @@ -105,6 +106,10 @@ const HealthDataType = { SYMPTOMS: { id: 'SYMPTOMS', name: 'Symptoms' + }, + GOOGLE_HEALTH: { + id: 'GOOGLE_HEALTH', + name: 'Google Health' } }; @@ -173,7 +178,8 @@ const AddSourceDialog: React.FC = ({ isSetUpVisionParser, isSetUpDocumentParser, onFileUpload, - onAddSymptoms + onAddSymptoms, + onImportGoogleHealthData }) => { const [open, setOpen] = useState(false); const [showSettingsAlert, setShowSettingsAlert] = useState(false); @@ -205,6 +211,11 @@ const AddSourceDialog: React.FC = ({ setOpen(false); }; + const handleImportGoogleHealthData = async () => { + onImportGoogleHealthData(); + setOpen(false); + }; + return ( <> @@ -256,6 +267,17 @@ const AddSourceDialog: React.FC = ({

Record today's symptoms

+ +
@@ -297,6 +319,8 @@ const HealthDataItem: React.FC = ({healthData, isSelected, return ; case HealthDataType.SYMPTOMS.id: return ; + case HealthDataType.GOOGLE_HEALTH.id: + return ; default: return ; } @@ -1112,6 +1136,39 @@ export default function SourceAddScreen() { } }; + const handleImportGoogleHealthData = async () => { + try { + const authUrl = getAuthUrl(); + const authCode = prompt(`Please visit the following URL to authorize the app:\n\n${authUrl}\n\nThen enter the authorization code here:`); + if (!authCode) { + alert('Authorization code is required to import Google Health data.'); + return; + } + + const tokens = await getToken(authCode); + const healthData = await getGoogleHealthData(tokens.access_token); + + const now = new Date(); + const body = { + id: cuid(), + type: HealthDataType.GOOGLE_HEALTH.id, + data: healthData, + status: 'COMPLETED', + filePath: null, + fileType: null, + createdAt: now, + updatedAt: now + } as HealthData; + + setSelectedId(body.id); + setFormData(body.data as Record); + await mutate({healthDataList: [...healthDataList?.healthDataList || [], body]}); + } catch (error) { + console.error('Failed to import Google Health data:', error); + alert('Failed to import Google Health data. Please try again.'); + } + }; + useEffect(() => { if (visionDataList?.visions && visionParser === undefined) { const {name, models} = visionDataList.visions[0]; @@ -1140,7 +1197,8 @@ export default function SourceAddScreen() { isSetUpVisionParser={visionParser !== undefined && visionParserModel !== undefined && visionParserApiKey.length > 0} isSetUpDocumentParser={documentParser !== undefined && documentParserModel !== undefined && documentParserApiKey.length > 0} onFileUpload={handleFileUpload} - onAddSymptoms={handleAddSymptoms}/> + onAddSymptoms={handleAddSymptoms} + onImportGoogleHealthData={handleImportGoogleHealthData}/>
{healthDataList?.healthDataList?.map((item) => ( { + return { + source: data.dataSource, + values: data.data.map((point: any) => ({ + startTime: point.startTimeNanos, + endTime: point.endTimeNanos, + value: point.value, + })), + }; + }); +} + +export async function getGoogleHealthData(authToken: string) { + const rawData = await fetchGoogleHealthData(authToken); + return parseGoogleHealthData(rawData); +} + +export async function saveHealthData(data: any) { + // Implement the logic to save health data to the database + // This is a placeholder function and should be replaced with actual implementation + return data; +}