From d8211cd573f7a47f4e0c9ee06fc1dd16a09033c1 Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 3 Oct 2025 06:52:34 +0000 Subject: [PATCH] fix: Resolve GraphQL DateTime and JSON serialization errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed two critical GraphQL schema issues preventing dashboard data loading: **Backend Changes:** - Changed child.birthDate from DATE to TIMESTAMP type in entity and database - Updated TypeORM entity (child.entity.ts:23) - Migrated database column: ALTER TABLE children ALTER COLUMN birth_date TYPE TIMESTAMP - Added JSON scalar support for activity metadata field - Installed graphql-type-json package - Created JSONScalar (src/graphql/scalars/json.scalar.ts) - Updated Activity.metadata from String to GraphQLJSON type - Auto-generated schema.gql with JSON scalar definition **Frontend Changes:** - Fixed Apollo Client token storage key mismatch - Changed from 'access_token' to 'accessToken' to match tokenStorage utility - Enhanced dashboard logging for debugging GraphQL queries **Database Migration:** - Converted children.birth_date: DATE → TIMESTAMP - Preserves existing data (2023-06-01 → 2023-06-01 00:00:00) Resolves errors: - "Expected DateTime.serialize() to return non-nullable value, returned: null" - "String cannot represent value: { ... }" for activity metadata 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../maternal-app-backend/package-lock.json | 401 ++++++++++-------- .../maternal-app-backend/package.json | 2 + .../src/database/entities/child.entity.ts | 2 +- .../src/graphql/scalars/json.scalar.ts | 22 + .../src/graphql/types/activity.type.ts | 9 +- .../maternal-app-backend/src/schema.gql | 100 +++++ maternal-web/app/page.tsx | 132 +++--- maternal-web/lib/apollo-client.ts | 31 ++ 8 files changed, 448 insertions(+), 251 deletions(-) create mode 100644 maternal-app/maternal-app-backend/src/graphql/scalars/json.scalar.ts create mode 100644 maternal-app/maternal-app-backend/src/schema.gql create mode 100644 maternal-web/lib/apollo-client.ts diff --git a/maternal-app/maternal-app-backend/package-lock.json b/maternal-app/maternal-app-backend/package-lock.json index 80df7b3..2707645 100644 --- a/maternal-app/maternal-app-backend/package-lock.json +++ b/maternal-app/maternal-app-backend/package-lock.json @@ -10,6 +10,7 @@ "license": "UNLICENSED", "dependencies": { "@apollo/server": "^5.0.0", + "@as-integrations/express5": "^1.1.2", "@aws-sdk/client-s3": "^3.899.0", "@aws-sdk/lib-storage": "^3.900.0", "@aws-sdk/s3-request-presigner": "^3.899.0", @@ -45,6 +46,7 @@ "dotenv": "^17.2.3", "form-data": "^4.0.4", "graphql": "^16.11.0", + "graphql-type-json": "^0.3.2", "ioredis": "^5.8.0", "langchain": "^0.3.35", "mailgun.js": "^12.1.0", @@ -553,6 +555,19 @@ "xss": "^1.0.8" } }, + "node_modules/@as-integrations/express5": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@as-integrations/express5/-/express5-1.1.2.tgz", + "integrity": "sha512-BxfwtcWNf2CELDkuPQxi5Zl3WqY/dQVJYafeCBOGoFQjv5M0fjhxmAFZ9vKx/5YKKNeok4UY6PkFbHzmQrdxIA==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@apollo/server": "^4.0.0 || ^5.0.0", + "express": "^5.0.0" + } + }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", @@ -3920,184 +3935,6 @@ "@nestjs/core": "^11.0.0" } }, - "node_modules/@nestjs/platform-express/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/platform-express/node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/platform-express/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/@nestjs/platform-express/node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@nestjs/platform-express/node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@nestjs/platform-express/node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@nestjs/platform-express/node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@nestjs/platform-express/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/platform-express/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@nestjs/platform-express/node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nestjs/platform-express/node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nestjs/platform-express/node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@nestjs/platform-socket.io": { "version": "11.1.6", "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.6.tgz", @@ -8358,6 +8195,18 @@ "simple-wcswidth": "^1.0.1" } }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -8383,6 +8232,15 @@ "node": ">= 0.6" } }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/cookiejar": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", @@ -9383,6 +9241,105 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -9810,6 +9767,15 @@ "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", "license": "MIT" }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -10065,6 +10031,15 @@ "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, + "node_modules/graphql-type-json": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz", + "integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==", + "license": "MIT", + "peerDependencies": { + "graphql": ">=0.8.0" + } + }, "node_modules/graphql-ws": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.0.6.tgz", @@ -12070,6 +12045,18 @@ "node": ">= 4.0.0" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -13861,6 +13848,49 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -13871,6 +13901,21 @@ "randombytes": "^2.1.0" } }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", diff --git a/maternal-app/maternal-app-backend/package.json b/maternal-app/maternal-app-backend/package.json index 3fb32c5..61e0570 100644 --- a/maternal-app/maternal-app-backend/package.json +++ b/maternal-app/maternal-app-backend/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@apollo/server": "^5.0.0", + "@as-integrations/express5": "^1.1.2", "@aws-sdk/client-s3": "^3.899.0", "@aws-sdk/lib-storage": "^3.900.0", "@aws-sdk/s3-request-presigner": "^3.899.0", @@ -57,6 +58,7 @@ "dotenv": "^17.2.3", "form-data": "^4.0.4", "graphql": "^16.11.0", + "graphql-type-json": "^0.3.2", "ioredis": "^5.8.0", "langchain": "^0.3.35", "mailgun.js": "^12.1.0", diff --git a/maternal-app/maternal-app-backend/src/database/entities/child.entity.ts b/maternal-app/maternal-app-backend/src/database/entities/child.entity.ts index 297baf5..f83b3f2 100644 --- a/maternal-app/maternal-app-backend/src/database/entities/child.entity.ts +++ b/maternal-app/maternal-app-backend/src/database/entities/child.entity.ts @@ -20,7 +20,7 @@ export class Child { @Column({ length: 100 }) name: string; - @Column({ name: 'birth_date', type: 'date' }) + @Column({ name: 'birth_date', type: 'timestamp' }) birthDate: Date; @Column({ length: 20, nullable: true }) diff --git a/maternal-app/maternal-app-backend/src/graphql/scalars/json.scalar.ts b/maternal-app/maternal-app-backend/src/graphql/scalars/json.scalar.ts new file mode 100644 index 0000000..2ba5ae8 --- /dev/null +++ b/maternal-app/maternal-app-backend/src/graphql/scalars/json.scalar.ts @@ -0,0 +1,22 @@ +import { Scalar, CustomScalar } from '@nestjs/graphql'; +import { Kind, ValueNode } from 'graphql'; + +@Scalar('JSON', () => Object) +export class JSONScalar implements CustomScalar { + description = 'JSON custom scalar type'; + + parseValue(value: unknown): object { + return value as object; // value from the client + } + + serialize(value: unknown): object { + return value as object; // value sent to the client + } + + parseLiteral(ast: ValueNode): object { + if (ast.kind === Kind.OBJECT) { + return ast as any; + } + return null; + } +} diff --git a/maternal-app/maternal-app-backend/src/graphql/types/activity.type.ts b/maternal-app/maternal-app-backend/src/graphql/types/activity.type.ts index e71c054..f6662f0 100644 --- a/maternal-app/maternal-app-backend/src/graphql/types/activity.type.ts +++ b/maternal-app/maternal-app-backend/src/graphql/types/activity.type.ts @@ -1,6 +1,7 @@ -import { ObjectType, Field, ID, registerEnumType, Int, Float } from '@nestjs/graphql'; +import { ObjectType, Field, ID, registerEnumType, Int, Float, GraphQLISODateTime } from '@nestjs/graphql'; import { ChildType } from './child.type'; import { UserType } from './user.type'; +import GraphQLJSON from 'graphql-type-json'; export enum ActivityType { FEEDING = 'feeding', @@ -53,9 +54,9 @@ export class ActivityGQLType { @Field({ nullable: true }) notes?: string; - // Metadata as JSON string or object - @Field({ nullable: true }) - metadata?: string; + // Metadata as JSON object + @Field(() => GraphQLJSON, { nullable: true }) + metadata?: any; // Relations @Field(() => ChildType, { nullable: true }) diff --git a/maternal-app/maternal-app-backend/src/schema.gql b/maternal-app/maternal-app-backend/src/schema.gql new file mode 100644 index 0000000..c29a586 --- /dev/null +++ b/maternal-app/maternal-app-backend/src/schema.gql @@ -0,0 +1,100 @@ +# ------------------------------------------------------ +# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) +# ------------------------------------------------------ + +type Activity { + child: Child + childId: String! + createdAt: DateTime! + endedAt: DateTime + id: ID! + loggedBy: String! + logger: User + metadata: JSON + notes: String + startedAt: DateTime! + type: ActivityType! + updatedAt: DateTime! +} + +enum ActivityType { + ACTIVITY + DIAPER + FEEDING + GROWTH + MEDICATION + MEDICINE + MILESTONE + SLEEP + TEMPERATURE +} + +type Child { + birthDate: DateTime! + createdAt: DateTime! + familyId: String! + gender: String + id: ID! + name: String! + photoUrl: String + updatedAt: DateTime! +} + +type DailySummary { + date: String! + diaperCount: Int! + feedingCount: Int! + medicationCount: Int! + sleepCount: Int! + totalFeedingAmount: Int + totalSleepDuration: Int +} + +type Dashboard { + child: Child + children: [Child!]! + familyMembers: [FamilyMember!]! + logger: User + recentActivities: [Activity!]! + selectedChild: Child + todaySummary: DailySummary + totalActivitiesToday: Int! + totalChildren: Int! +} + +""" +A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format. +""" +scalar DateTime + +type FamilyMember { + createdAt: DateTime! + familyId: String! + role: FamilyRole! + user: User + userId: String! +} + +enum FamilyRole { + CAREGIVER + PARENT +} + +""" +The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). +""" +scalar JSON + +type Query { + dashboard(childId: String): Dashboard! +} + +type User { + createdAt: DateTime! + email: String! + families: [FamilyMember!] + id: ID! + name: String! + phone: String + updatedAt: DateTime! +} \ No newline at end of file diff --git a/maternal-web/app/page.tsx b/maternal-web/app/page.tsx index 7f2ba76..803643c 100644 --- a/maternal-web/app/page.tsx +++ b/maternal-web/app/page.tsx @@ -21,86 +21,76 @@ import { import { motion } from 'framer-motion'; import { useAuth } from '@/lib/auth/AuthContext'; import { useRouter } from 'next/navigation'; -import { trackingApi, DailySummary } from '@/lib/api/tracking'; -import { childrenApi, Child } from '@/lib/api/children'; +import { useQuery } from '@apollo/client/react'; +import { GET_DASHBOARD } from '@/graphql/queries/dashboard'; import { format } from 'date-fns'; import { useRealTimeActivities } from '@/hooks/useWebSocket'; export default function HomePage() { const { user, isLoading: authLoading } = useAuth(); const router = useRouter(); - const [children, setChildren] = useState([]); - const [selectedChild, setSelectedChild] = useState(null); - const [dailySummary, setDailySummary] = useState(null); - const [loading, setLoading] = useState(true); + const [selectedChildId, setSelectedChildId] = useState(null); - const familyId = user?.families?.[0]?.familyId; + // GraphQL query for dashboard data + const { data, loading, error, refetch } = useQuery(GET_DASHBOARD, { + variables: { childId: selectedChildId }, + skip: authLoading || !user, + fetchPolicy: 'cache-and-network', + }); - // Real-time activity handler to refresh daily summary - const refreshDailySummary = useCallback(async () => { - if (!selectedChild) return; + // Log GraphQL errors and data for debugging + useEffect(() => { + console.log('[HomePage] === GraphQL Debug Info ==='); + console.log('[HomePage] Raw data:', data); + console.log('[HomePage] Data type:', typeof data); + console.log('[HomePage] Data keys:', data ? Object.keys(data) : 'null'); - try { - const today = format(new Date(), 'yyyy-MM-dd'); - const summary = await trackingApi.getDailySummary(selectedChild.id, today); - console.log('[HomePage] Refreshed daily summary:', summary); - setDailySummary(summary); - } catch (error) { - console.error('[HomePage] Failed to refresh summary:', error); + if (error) { + console.error('[HomePage] GraphQL error:', error); + console.error('[HomePage] GraphQL error details:', { + message: error.message, + networkError: error.networkError, + graphQLErrors: error.graphQLErrors, + }); } - }, [selectedChild]); + if (data) { + console.log('[HomePage] GraphQL data:', data); + console.log('[HomePage] Dashboard data:', data.dashboard); + } + console.log('[HomePage] Query state:', { + loading, + error: !!error, + hasData: !!data, + user: !!user, + skip: authLoading || !user, + childrenCount: data?.dashboard?.children?.length || 0, + selectedChildId, + }); + console.log('[HomePage] LocalStorage token:', typeof window !== 'undefined' ? localStorage.getItem('accessToken')?.substring(0, 20) : 'SSR'); + }, [error, data, loading, user, authLoading, selectedChildId]); + + // Real-time activity handler to refetch dashboard data + const refreshDashboard = useCallback(async () => { + if (refetch) { + console.log('[HomePage] Refreshing dashboard data...'); + await refetch(); + } + }, [refetch]); // Subscribe to real-time activity updates useRealTimeActivities( - refreshDailySummary, // On activity created - refreshDailySummary, // On activity updated - refreshDailySummary // On activity deleted + refreshDashboard, // On activity created + refreshDashboard, // On activity updated + refreshDashboard // On activity deleted ); - // Load children and daily summary + // Set the first child as selected when data loads useEffect(() => { - const loadData = async () => { - // Wait for auth to complete before trying to load data - if (authLoading) { - return; - } - - if (!familyId) { - console.log('[HomePage] No familyId found'); - console.log('[HomePage] User object:', JSON.stringify(user, null, 2)); - console.log('[HomePage] User.families:', user?.families); - setLoading(false); - return; - } - - console.log('[HomePage] Loading data for familyId:', familyId); - - try { - // Load children - const childrenData = await childrenApi.getChildren(familyId); - console.log('[HomePage] Children loaded:', childrenData.length); - setChildren(childrenData); - - if (childrenData.length > 0) { - const firstChild = childrenData[0]; - setSelectedChild(firstChild); - - // Load today's summary for first child - const today = format(new Date(), 'yyyy-MM-dd'); - console.log('[HomePage] Fetching daily summary for child:', firstChild.id, 'date:', today); - const summary = await trackingApi.getDailySummary(firstChild.id, today); - console.log('[HomePage] Daily summary response:', summary); - setDailySummary(summary); - } - } catch (error) { - console.error('[HomePage] Failed to load data:', error); - } finally { - setLoading(false); - } - }; - - loadData(); - }, [familyId, authLoading, user]); + if (data?.dashboard?.children && data.dashboard.children.length > 0 && !selectedChildId) { + const firstChild = data.dashboard.children[0]; + setSelectedChildId(firstChild.id); + } + }, [data, selectedChildId]); const quickActions = [ { icon: , label: 'Feeding', color: '#E91E63', path: '/track/feeding' }, // Pink with 4.5:1 contrast @@ -123,6 +113,12 @@ export default function HomePage() { } }; + // Extract data from GraphQL response + const children = data?.dashboard?.children || []; + const selectedChild = data?.dashboard?.selectedChild; + const dailySummary = data?.dashboard?.todaySummary; + const isLoading = authLoading || loading; + return ( @@ -206,7 +202,7 @@ export default function HomePage() { isolate fallback={} > - {loading ? ( + {isLoading ? ( ) : ( @@ -252,9 +248,9 @@ export default function HomePage() { }} >