feat: full health & fitness app with workout/meal planning
Some checks failed
CI/CD - Build, Push & Deploy / Build & Push Docker Image (push) Has been cancelled
CI/CD - Build, Push & Deploy / Update GitOps Manifest (push) Has been cancelled

- Login/register with JWT auth
- User profile (age, weight, height, goal, country)
- AI trainer agent: 7-day workout programs by goal
- AI dietitian agent: calorie-based meal plans with Turkish cuisine
- Shopping list generator from meal plans
- Modern Turkish UI (SPA)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 10:02:46 +00:00
parent d0d40c2195
commit c17f143a40
13 changed files with 3318 additions and 18 deletions

View File

@@ -1,6 +1,7 @@
# Stage 1: Install dependencies
FROM node:20-alpine AS deps
WORKDIR /app
RUN apk add --no-cache python3 make g++
COPY package.json package-lock.json* ./
RUN npm ci --omit=dev && npm cache clean --force
@@ -10,7 +11,8 @@ ENV NODE_ENV=production
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
adduser -u 1001 -S appuser -G appgroup && \
mkdir -p /tmp/health-app && chown appuser:appgroup /tmp/health-app
COPY --from=deps /app/node_modules ./node_modules
COPY src ./src

544
package-lock.json generated
View File

@@ -1,14 +1,17 @@
{
"name": "@infinicaretech/health-app",
"version": "1.0.0",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@infinicaretech/health-app",
"version": "1.0.0",
"version": "2.0.0",
"dependencies": {
"express": "^4.21.2"
"bcryptjs": "^2.4.3",
"better-sqlite3": "^11.7.0",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2"
},
"engines": {
"node": ">=20.0.0"
@@ -33,6 +36,63 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
"license": "MIT"
},
"node_modules/better-sqlite3": {
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
}
},
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"license": "MIT",
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/body-parser": {
"version": "1.20.4",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
@@ -57,6 +117,36 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -95,6 +185,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -140,6 +236,30 @@
"ms": "2.0.0"
}
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -159,6 +279,15 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -173,6 +302,15 @@
"node": ">= 0.4"
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -188,6 +326,15 @@
"node": ">= 0.8"
}
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -233,6 +380,15 @@
"node": ">= 0.6"
}
},
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"engines": {
"node": ">=6"
}
},
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
@@ -279,6 +435,12 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"license": "MIT"
},
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
@@ -315,6 +477,12 @@
"node": ">= 0.6"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -361,6 +529,12 @@
"node": ">= 0.4"
}
},
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -429,12 +603,38 @@
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -444,6 +644,97 @@
"node": ">= 0.10"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
"license": "MIT",
"dependencies": {
"jws": "^4.0.1",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jsonwebtoken/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -513,12 +804,45 @@
"node": ">= 0.6"
}
},
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -528,6 +852,18 @@
"node": ">= 0.6"
}
},
"node_modules/node-abi": {
"version": "3.89.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz",
"integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -552,6 +888,15 @@
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -567,6 +912,33 @@
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
"license": "MIT"
},
"node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -580,6 +952,16 @@
"node": ">= 0.10"
}
},
"node_modules/pump": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/qs": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
@@ -619,6 +1001,35 @@
"node": ">= 0.8"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"bin": {
"rc": "cli.js"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -645,6 +1056,18 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/send": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
@@ -768,6 +1191,51 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -777,6 +1245,52 @@
"node": ">= 0.8"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -786,6 +1300,18 @@
"node": ">=0.6"
}
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -808,6 +1334,12 @@
"node": ">= 0.8"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -825,6 +1357,12 @@
"engines": {
"node": ">= 0.8"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
}
}
}

View File

@@ -1,15 +1,18 @@
{
"name": "@infinicaretech/health-app",
"version": "1.0.0",
"version": "2.0.0",
"private": true,
"description": "Production-ready health check API for GitOps deployment",
"description": "Comprehensive health & fitness web application with workout and meal planning",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "node --watch src/server.js"
},
"dependencies": {
"express": "^4.21.2"
"bcryptjs": "^2.4.3",
"better-sqlite3": "^11.7.0",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2"
},
"engines": {
"node": ">=20.0.0"

48
src/database.js Normal file
View File

@@ -0,0 +1,48 @@
"use strict";
const Database = require("better-sqlite3");
const path = require("path");
const DB_PATH = process.env.DB_PATH || "/tmp/health-app/health-app.db";
let db;
function getDb() {
if (!db) {
db = new Database(DB_PATH);
db.pragma("journal_mode = WAL");
db.pragma("foreign_keys = ON");
initTables();
}
return db;
}
function initTables() {
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
name TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS profiles (
user_id INTEGER PRIMARY KEY,
age INTEGER,
gender TEXT,
height REAL,
weight REAL,
activity_level TEXT DEFAULT 'moderate',
country TEXT DEFAULT 'Turkey',
city TEXT DEFAULT '',
goal TEXT DEFAULT 'maintain',
allergies TEXT DEFAULT '',
dietary_restrictions TEXT DEFAULT '',
updated_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
`);
}
module.exports = { getDb };

24
src/middleware/auth.js Normal file
View File

@@ -0,0 +1,24 @@
"use strict";
const jwt = require("jsonwebtoken");
const JWT_SECRET = process.env.JWT_SECRET || "health-app-default-secret-change-in-production";
function authenticateToken(req, res, next) {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.startsWith("Bearer ") ? authHeader.slice(7) : null;
if (!token) {
return res.status(401).json({ error: "Yetkilendirme token'ı gerekli" });
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(403).json({ error: "Geçersiz veya süresi dolmuş token" });
}
}
module.exports = { authenticateToken, JWT_SECRET };

1142
src/public/index.html Normal file

File diff suppressed because it is too large Load Diff

92
src/routes/auth.js Normal file
View File

@@ -0,0 +1,92 @@
"use strict";
const express = require("express");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const { getDb } = require("../database");
const { authenticateToken, JWT_SECRET } = require("../middleware/auth");
const router = express.Router();
// POST /api/auth/register
router.post("/register", (req, res) => {
try {
const { email, password, name } = req.body;
if (!email || !password || !name) {
return res.status(400).json({ error: "Email, şifre ve isim gereklidir" });
}
if (password.length < 6) {
return res.status(400).json({ error: "Şifre en az 6 karakter olmalıdır" });
}
const db = getDb();
const existing = db.prepare("SELECT id FROM users WHERE email = ?").get(email);
if (existing) {
return res.status(409).json({ error: "Bu email adresi zaten kayıtlı" });
}
const passwordHash = bcrypt.hashSync(password, 10);
const result = db.prepare("INSERT INTO users (email, password_hash, name) VALUES (?, ?, ?)").run(email, passwordHash, name);
const token = jwt.sign({ id: result.lastInsertRowid, email, name }, JWT_SECRET, { expiresIn: "7d" });
res.status(201).json({
message: "Kayıt başarılı",
token,
user: { id: result.lastInsertRowid, email, name },
});
} catch (err) {
console.error("Register error:", err.message);
res.status(500).json({ error: "Sunucu hatası" });
}
});
// POST /api/auth/login
router.post("/login", (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: "Email ve şifre gereklidir" });
}
const db = getDb();
const user = db.prepare("SELECT * FROM users WHERE email = ?").get(email);
if (!user || !bcrypt.compareSync(password, user.password_hash)) {
return res.status(401).json({ error: "Geçersiz email veya şifre" });
}
const token = jwt.sign({ id: user.id, email: user.email, name: user.name }, JWT_SECRET, { expiresIn: "7d" });
res.json({
message: "Giriş başarılı",
token,
user: { id: user.id, email: user.email, name: user.name },
});
} catch (err) {
console.error("Login error:", err.message);
res.status(500).json({ error: "Sunucu hatası" });
}
});
// GET /api/auth/me
router.get("/me", authenticateToken, (req, res) => {
try {
const db = getDb();
const user = db.prepare("SELECT id, email, name, created_at FROM users WHERE id = ?").get(req.user.id);
if (!user) {
return res.status(404).json({ error: "Kullanıcı bulunamadı" });
}
res.json({ user });
} catch (err) {
console.error("Me error:", err.message);
res.status(500).json({ error: "Sunucu hatası" });
}
});
module.exports = router;

72
src/routes/profile.js Normal file
View File

@@ -0,0 +1,72 @@
"use strict";
const express = require("express");
const { getDb } = require("../database");
const { authenticateToken } = require("../middleware/auth");
const router = express.Router();
// GET /api/profile
router.get("/", authenticateToken, (req, res) => {
try {
const db = getDb();
const profile = db.prepare("SELECT * FROM profiles WHERE user_id = ?").get(req.user.id);
if (!profile) {
return res.json({ profile: null });
}
res.json({ profile });
} catch (err) {
console.error("Get profile error:", err.message);
res.status(500).json({ error: "Sunucu hatası" });
}
});
// POST /api/profile
router.post("/", authenticateToken, (req, res) => {
try {
const { age, gender, height, weight, activity_level, country, city, goal, allergies, dietary_restrictions } = req.body;
if (!age || !gender || !height || !weight) {
return res.status(400).json({ error: "Yaş, cinsiyet, boy ve kilo gereklidir" });
}
const validGenders = ["male", "female"];
const validActivityLevels = ["sedentary", "light", "moderate", "active", "very_active"];
const validGoals = ["lose_weight", "gain_weight", "build_muscle", "maintain"];
if (!validGenders.includes(gender)) {
return res.status(400).json({ error: "Geçersiz cinsiyet değeri" });
}
if (activity_level && !validActivityLevels.includes(activity_level)) {
return res.status(400).json({ error: "Geçersiz aktivite seviyesi" });
}
if (goal && !validGoals.includes(goal)) {
return res.status(400).json({ error: "Geçersiz hedef" });
}
const db = getDb();
const existing = db.prepare("SELECT user_id FROM profiles WHERE user_id = ?").get(req.user.id);
if (existing) {
db.prepare(`
UPDATE profiles SET age=?, gender=?, height=?, weight=?, activity_level=?, country=?, city=?, goal=?, allergies=?, dietary_restrictions=?, updated_at=datetime('now')
WHERE user_id=?
`).run(age, gender, height, weight, activity_level || "moderate", country || "Turkey", city || "", goal || "maintain", allergies || "", dietary_restrictions || "", req.user.id);
} else {
db.prepare(`
INSERT INTO profiles (user_id, age, gender, height, weight, activity_level, country, city, goal, allergies, dietary_restrictions)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(req.user.id, age, gender, height, weight, activity_level || "moderate", country || "Turkey", city || "", goal || "maintain", allergies || "", dietary_restrictions || "");
}
const profile = db.prepare("SELECT * FROM profiles WHERE user_id = ?").get(req.user.id);
res.json({ message: "Profil güncellendi", profile });
} catch (err) {
console.error("Save profile error:", err.message);
res.status(500).json({ error: "Sunucu hatası" });
}
});
module.exports = router;

63
src/routes/program.js Normal file
View File

@@ -0,0 +1,63 @@
"use strict";
const express = require("express");
const { getDb } = require("../database");
const { authenticateToken } = require("../middleware/auth");
const { generateWorkoutProgram } = require("../services/trainer");
const { generateMealPlan } = require("../services/dietitian");
const { generateShoppingList } = require("../services/shopping");
const router = express.Router();
function getProfile(userId) {
const db = getDb();
return db.prepare("SELECT * FROM profiles WHERE user_id = ?").get(userId);
}
// GET /api/program/workout
router.get("/workout", authenticateToken, (req, res) => {
try {
const profile = getProfile(req.user.id);
if (!profile) {
return res.status(400).json({ error: "Lütfen önce profilinizi oluşturun" });
}
const program = generateWorkoutProgram(profile);
res.json(program);
} catch (err) {
console.error("Workout program error:", err.message);
res.status(500).json({ error: "Program oluşturulurken hata oluştu" });
}
});
// GET /api/program/meal
router.get("/meal", authenticateToken, (req, res) => {
try {
const profile = getProfile(req.user.id);
if (!profile) {
return res.status(400).json({ error: "Lütfen önce profilinizi oluşturun" });
}
const plan = generateMealPlan(profile);
res.json(plan);
} catch (err) {
console.error("Meal plan error:", err.message);
res.status(500).json({ error: "Beslenme planı oluşturulurken hata oluştu" });
}
});
// GET /api/program/shopping
router.get("/shopping", authenticateToken, (req, res) => {
try {
const profile = getProfile(req.user.id);
if (!profile) {
return res.status(400).json({ error: "Lütfen önce profilinizi oluşturun" });
}
const mealPlan = generateMealPlan(profile);
const shoppingList = generateShoppingList(mealPlan);
res.json(shoppingList);
} catch (err) {
console.error("Shopping list error:", err.message);
res.status(500).json({ error: "Alışveriş listesi oluşturulurken hata oluştu" });
}
});
module.exports = router;

View File

@@ -1,6 +1,12 @@
"use strict";
const express = require("express");
const path = require("path");
const { getDb } = require("./database");
const authRoutes = require("./routes/auth");
const profileRoutes = require("./routes/profile");
const programRoutes = require("./routes/program");
const app = express();
const PORT = parseInt(process.env.PORT, 10) || 3000;
@@ -8,14 +14,17 @@ const startTime = Date.now();
app.disable("x-powered-by");
app.get("/", (_req, res) => {
res.json({
service: "health-app",
version: process.env.APP_VERSION || "1.0.0",
environment: process.env.NODE_ENV || "development",
});
});
// Body parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Static files
app.use(express.static(path.join(__dirname, "public")));
// Initialize database on startup
getDb();
// Health & readiness probes (Kubernetes)
app.get("/health", (_req, res) => {
res.json({
status: "healthy",
@@ -26,16 +35,36 @@ app.get("/health", (_req, res) => {
});
app.get("/ready", (_req, res) => {
res.json({ ready: true });
try {
getDb();
res.json({ ready: true });
} catch (err) {
res.status(503).json({ ready: false, error: err.message });
}
});
app.use((_req, res) => {
res.status(404).json({ error: "Not Found" });
// API routes
app.use("/api/auth", authRoutes);
app.use("/api/profile", profileRoutes);
app.use("/api/program", programRoutes);
// SPA fallback - serve index.html for non-API routes
app.get("*", (req, res) => {
if (req.path.startsWith("/api/")) {
return res.status(404).json({ error: "API endpoint bulunamadı" });
}
res.sendFile(path.join(__dirname, "public", "index.html"));
});
// 404 handler for API
app.use((req, res) => {
res.status(404).json({ error: "Bulunamadı" });
});
// Error handler
app.use((err, _req, res, _next) => {
console.error("Unhandled error:", err.message);
res.status(500).json({ error: "Internal Server Error" });
res.status(500).json({ error: "Sunucu hatası" });
});
const server = app.listen(PORT, "0.0.0.0", () => {

754
src/services/dietitian.js Normal file
View File

@@ -0,0 +1,754 @@
"use strict";
// Mifflin-St Jeor BMR
function calculateBMR(weight, height, age, gender) {
if (gender === "male") {
return 10 * weight + 6.25 * height - 5 * age + 5;
}
return 10 * weight + 6.25 * height - 5 * age - 161;
}
const ACTIVITY_MULTIPLIERS = {
sedentary: 1.2,
light: 1.375,
moderate: 1.55,
active: 1.725,
very_active: 1.9,
};
function calculateTDEE(bmr, activityLevel) {
return bmr * (ACTIVITY_MULTIPLIERS[activityLevel] || 1.55);
}
function adjustCalories(tdee, goal) {
switch (goal) {
case "lose_weight": return tdee - 500;
case "gain_weight": return tdee + 500;
case "build_muscle": return tdee + 300;
default: return tdee;
}
}
function calculateMacros(calories, goal) {
let proteinPct, carbsPct, fatPct;
switch (goal) {
case "lose_weight":
proteinPct = 0.35; carbsPct = 0.35; fatPct = 0.30; break;
case "build_muscle":
proteinPct = 0.35; carbsPct = 0.40; fatPct = 0.25; break;
case "gain_weight":
proteinPct = 0.30; carbsPct = 0.45; fatPct = 0.25; break;
default:
proteinPct = 0.30; carbsPct = 0.40; fatPct = 0.30;
}
return {
protein_g: Math.round((calories * proteinPct) / 4),
carbs_g: Math.round((calories * carbsPct) / 4),
fat_g: Math.round((calories * fatPct) / 9),
};
}
// ==================== TURKISH MEALS ====================
const TURKISH_BREAKFASTS = [
{
name: "Menemen & Simit Kahvaltısı",
ingredients: [
{ item: "Yumurta", amount: "2 adet", grams: 120 },
{ item: "Domates", amount: "1 adet", grams: 100 },
{ item: "Biber (sivri)", amount: "2 adet", grams: 60 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
{ item: "Simit", amount: "1/2 adet", grams: 60 },
{ item: "Beyaz peynir", amount: "40g", grams: 40 },
],
calories: 420, protein: 22, carbs: 35, fat: 22,
},
{
name: "Peynirli Kahvaltı Tabağı",
ingredients: [
{ item: "Beyaz peynir", amount: "60g", grams: 60 },
{ item: "Kaşar peyniri", amount: "30g", grams: 30 },
{ item: "Zeytin (siyah)", amount: "8 adet", grams: 30 },
{ item: "Domates", amount: "1 adet", grams: 120 },
{ item: "Salatalık", amount: "1 adet", grams: 100 },
{ item: "Bal", amount: "1 tatlı kaşığı", grams: 10 },
{ item: "Tam buğday ekmek", amount: "2 dilim", grams: 60 },
{ item: "Tereyağı", amount: "10g", grams: 10 },
],
calories: 450, protein: 20, carbs: 38, fat: 24,
},
{
name: "Sucuklu Yumurta",
ingredients: [
{ item: "Yumurta", amount: "2 adet", grams: 120 },
{ item: "Sucuk", amount: "40g", grams: 40 },
{ item: "Tam buğday ekmek", amount: "1 dilim", grams: 30 },
{ item: "Domates", amount: "1/2 adet", grams: 60 },
{ item: "Yeşil biber", amount: "1 adet", grams: 30 },
],
calories: 400, protein: 24, carbs: 18, fat: 26,
},
{
name: "Kaymak & Bal ile Simit",
ingredients: [
{ item: "Simit", amount: "1 adet", grams: 120 },
{ item: "Kaymak", amount: "30g", grams: 30 },
{ item: "Bal", amount: "1 yemek kaşığı", grams: 20 },
{ item: "Çay", amount: "1 bardak", grams: 200 },
{ item: "Beyaz peynir", amount: "30g", grams: 30 },
],
calories: 460, protein: 14, carbs: 55, fat: 20,
},
{
name: "Sahanda Yumurta & Peynir",
ingredients: [
{ item: "Yumurta", amount: "2 adet", grams: 120 },
{ item: "Tereyağı", amount: "10g", grams: 10 },
{ item: "Beyaz peynir", amount: "40g", grams: 40 },
{ item: "Domates", amount: "1 adet", grams: 100 },
{ item: "Zeytin", amount: "6 adet", grams: 24 },
{ item: "Tam buğday ekmek", amount: "1 dilim", grams: 30 },
],
calories: 410, protein: 23, carbs: 20, fat: 27,
},
{
name: "Gözleme (Peynirli & Ispanaklı)",
ingredients: [
{ item: "Un (hamur)", amount: "80g", grams: 80 },
{ item: "Ispanak", amount: "60g", grams: 60 },
{ item: "Beyaz peynir", amount: "50g", grams: 50 },
{ item: "Tereyağı", amount: "10g", grams: 10 },
{ item: "Ayran", amount: "1 bardak", grams: 200 },
],
calories: 430, protein: 20, carbs: 42, fat: 20,
},
{
name: ılbır (Yumurtalı Yoğurt)",
ingredients: [
{ item: "Yumurta", amount: "2 adet", grams: 120 },
{ item: "Yoğurt", amount: "150g", grams: 150 },
{ item: "Tereyağı", amount: "10g", grams: 10 },
{ item: "Pul biber", amount: "1 çay kaşığı", grams: 2 },
{ item: "Tam buğday ekmek", amount: "1 dilim", grams: 30 },
],
calories: 380, protein: 24, carbs: 22, fat: 22,
},
];
const TURKISH_LUNCHES = [
{
name: "Mercimek Çorbası & Bulgur Pilavı",
ingredients: [
{ item: "Kırmızı mercimek", amount: "80g", grams: 80 },
{ item: "Soğan", amount: "1/2 adet", grams: 40 },
{ item: "Havuç", amount: "1/2 adet", grams: 40 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
{ item: "Limon", amount: "1/2 adet", grams: 30 },
{ item: "Bulgur pilavı", amount: "150g (pişmiş)", grams: 150 },
{ item: "Tereyağı", amount: "5g", grams: 5 },
],
calories: 480, protein: 22, carbs: 68, fat: 14,
},
{
name: "Izgara Köfte & Salata",
ingredients: [
{ item: "Dana kıyma (az yağlı)", amount: "150g", grams: 150 },
{ item: "Soğan (rendelenmiş)", amount: "30g", grams: 30 },
{ item: "Maydanoz", amount: "10g", grams: 10 },
{ item: "Çoban salatası", amount: "150g", grams: 150 },
{ item: "Tam buğday ekmek", amount: "1 dilim", grams: 30 },
{ item: "Sumak", amount: "1 çay kaşığı", grams: 2 },
],
calories: 450, protein: 38, carbs: 22, fat: 24,
},
{
name: "Tavuk Şiş & Bulgur",
ingredients: [
{ item: "Tavuk göğsü", amount: "180g", grams: 180 },
{ item: "Biber", amount: "2 adet", grams: 60 },
{ item: "Domates", amount: "1 adet", grams: 100 },
{ item: "Bulgur pilavı", amount: "150g (pişmiş)", grams: 150 },
{ item: "Zeytinyağı", amount: "1 tatlı kaşığı", grams: 7 },
{ item: "Soğan", amount: "1/4 adet", grams: 20 },
],
calories: 480, protein: 45, carbs: 42, fat: 14,
},
{
name: "Kuru Fasulye & Pilav",
ingredients: [
{ item: "Kuru fasulye (pişmiş)", amount: "200g", grams: 200 },
{ item: "Domates salçası", amount: "1 yemek kaşığı", grams: 15 },
{ item: "Soğan", amount: "1/2 adet", grams: 40 },
{ item: "Tereyağlı pirinç pilavı", amount: "150g (pişmiş)", grams: 150 },
{ item: "Turşu", amount: "50g", grams: 50 },
],
calories: 520, protein: 24, carbs: 78, fat: 12,
},
{
name: "Patlıcan Musakka",
ingredients: [
{ item: "Patlıcan", amount: "200g", grams: 200 },
{ item: "Dana kıyma", amount: "100g", grams: 100 },
{ item: "Domates", amount: "1 adet", grams: 100 },
{ item: "Biber", amount: "1 adet", grams: 30 },
{ item: "Soğan", amount: "1/2 adet", grams: 40 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
{ item: "Pilav", amount: "100g (pişmiş)", grams: 100 },
],
calories: 470, protein: 28, carbs: 35, fat: 24,
},
{
name: "Karnıyarık",
ingredients: [
{ item: "Patlıcan", amount: "250g", grams: 250 },
{ item: "Dana kıyma", amount: "100g", grams: 100 },
{ item: "Domates", amount: "1 adet", grams: 100 },
{ item: "Soğan", amount: "1/2 adet", grams: 40 },
{ item: "Sarımsak", amount: "2 diş", grams: 6 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
{ item: "Bulgur pilavı", amount: "100g (pişmiş)", grams: 100 },
],
calories: 490, protein: 28, carbs: 38, fat: 25,
},
{
name: "İmam Bayıldı & Pilav",
ingredients: [
{ item: "Patlıcan", amount: "250g", grams: 250 },
{ item: "Domates", amount: "2 adet", grams: 200 },
{ item: "Soğan", amount: "1 adet", grams: 80 },
{ item: "Sarımsak", amount: "3 diş", grams: 9 },
{ item: "Zeytinyağı", amount: "2 yemek kaşığı", grams: 28 },
{ item: "Pilav", amount: "120g (pişmiş)", grams: 120 },
],
calories: 440, protein: 10, carbs: 48, fat: 24,
},
{
name: "Lahmacun & Ayran",
ingredients: [
{ item: "Lahmacun", amount: "2 adet", grams: 200 },
{ item: "Maydanoz", amount: "15g", grams: 15 },
{ item: "Limon", amount: "1/2 adet", grams: 30 },
{ item: "Domates", amount: "1/2 adet", grams: 60 },
{ item: "Ayran", amount: "1 bardak", grams: 200 },
],
calories: 460, protein: 24, carbs: 52, fat: 16,
},
{
name: "Etli Pide",
ingredients: [
{ item: "Pide hamuru", amount: "150g", grams: 150 },
{ item: "Dana kıyma", amount: "100g", grams: 100 },
{ item: "Domates", amount: "1/2 adet", grams: 60 },
{ item: "Biber", amount: "1 adet", grams: 30 },
{ item: "Soğan", amount: "1/4 adet", grams: 20 },
{ item: "Ayran", amount: "1 bardak", grams: 200 },
],
calories: 530, protein: 30, carbs: 52, fat: 22,
},
{
name: "Ezogelin Çorbası & Tavuk Sote",
ingredients: [
{ item: "Kırmızı mercimek", amount: "50g", grams: 50 },
{ item: "Bulgur", amount: "20g", grams: 20 },
{ item: "Domates salçası", amount: "1 yemek kaşığı", grams: 15 },
{ item: "Tavuk göğsü", amount: "150g", grams: 150 },
{ item: "Biber", amount: "2 adet", grams: 60 },
{ item: "Domates", amount: "1 adet", grams: 100 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
],
calories: 460, protein: 40, carbs: 36, fat: 16,
},
{
name: "Yayla Çorbası & Mantı",
ingredients: [
{ item: "Yoğurt", amount: "150g", grams: 150 },
{ item: "Pirinç", amount: "30g", grams: 30 },
{ item: "Yumurta", amount: "1 adet", grams: 60 },
{ item: "Nane (kuru)", amount: "1 çay kaşığı", grams: 1 },
{ item: "Mantı (dondurulmuş veya ev yapımı)", amount: "150g", grams: 150 },
{ item: "Tereyağı", amount: "10g", grams: 10 },
],
calories: 500, protein: 26, carbs: 54, fat: 20,
},
{
name: "Taze Fasulye Yemeği & Pilav",
ingredients: [
{ item: "Taze fasulye", amount: "250g", grams: 250 },
{ item: "Domates", amount: "1 adet", grams: 100 },
{ item: "Soğan", amount: "1/2 adet", grams: 40 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
{ item: "Pilav", amount: "150g (pişmiş)", grams: 150 },
],
calories: 420, protein: 12, carbs: 58, fat: 16,
},
{
name: "Bamya Yemeği & Bulgur",
ingredients: [
{ item: "Bamya", amount: "200g", grams: 200 },
{ item: "Domates", amount: "1 adet", grams: 100 },
{ item: "Et (kuşbaşı)", amount: "100g", grams: 100 },
{ item: "Soğan", amount: "1/2 adet", grams: 40 },
{ item: "Limon suyu", amount: "1 yemek kaşığı", grams: 15 },
{ item: "Bulgur pilavı", amount: "120g (pişmiş)", grams: 120 },
],
calories: 440, protein: 30, carbs: 42, fat: 16,
},
{
name: "Adana Kebap & Şalgam",
ingredients: [
{ item: "Adana kebap", amount: "200g", grams: 200 },
{ item: "Lavaş ekmek", amount: "1 adet", grams: 60 },
{ item: "Közlenmiş domates", amount: "1 adet", grams: 100 },
{ item: "Közlenmiş biber", amount: "2 adet", grams: 60 },
{ item: "Soğan", amount: "1/4 adet", grams: 20 },
{ item: "Şalgam suyu", amount: "1 bardak", grams: 200 },
],
calories: 520, protein: 36, carbs: 30, fat: 28,
},
];
const TURKISH_DINNERS = [
{
name: "Zeytinyağlı Yaprak Sarma & Cacık",
ingredients: [
{ item: "Asma yaprağı sarması", amount: "8 adet", grams: 160 },
{ item: "Yoğurt (cacık)", amount: "150g", grams: 150 },
{ item: "Salatalık", amount: "1/2 adet", grams: 50 },
{ item: "Sarımsak", amount: "1 diş", grams: 3 },
{ item: "Nane", amount: "1 çay kaşığı", grams: 1 },
{ item: "Tam buğday ekmek", amount: "1 dilim", grams: 30 },
],
calories: 380, protein: 14, carbs: 48, fat: 14,
},
{
name: "Tavuk Izgara & Kısır",
ingredients: [
{ item: "Tavuk göğsü (ızgara)", amount: "180g", grams: 180 },
{ item: "Kısır (bulgur salatası)", amount: "150g", grams: 150 },
{ item: "Nar ekşisi", amount: "1 tatlı kaşığı", grams: 5 },
{ item: "Maydanoz", amount: "15g", grams: 15 },
{ item: "Domates", amount: "1/2 adet", grams: 60 },
],
calories: 440, protein: 44, carbs: 38, fat: 10,
},
{
name: "Kabak Mücver & Yoğurt",
ingredients: [
{ item: "Kabak (rendelenmiş)", amount: "200g", grams: 200 },
{ item: "Un", amount: "30g", grams: 30 },
{ item: "Yumurta", amount: "1 adet", grams: 60 },
{ item: "Dereotu", amount: "10g", grams: 10 },
{ item: "Beyaz peynir", amount: "30g", grams: 30 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
{ item: "Yoğurt", amount: "100g", grams: 100 },
],
calories: 360, protein: 18, carbs: 28, fat: 20,
},
{
name: "Su Böreği",
ingredients: [
{ item: "Yufka", amount: "3 yaprak", grams: 120 },
{ item: "Beyaz peynir", amount: "100g", grams: 100 },
{ item: "Maydanoz", amount: "15g", grams: 15 },
{ item: "Yumurta", amount: "1 adet", grams: 60 },
{ item: "Süt", amount: "50ml", grams: 50 },
{ item: "Tereyağı", amount: "15g", grams: 15 },
],
calories: 440, protein: 22, carbs: 38, fat: 22,
},
{
name: "Mercimek Köftesi & Salata",
ingredients: [
{ item: "Kırmızı mercimek", amount: "100g", grams: 100 },
{ item: "İnce bulgur", amount: "80g", grams: 80 },
{ item: "Soğan", amount: "1 adet", grams: 80 },
{ item: "Domates salçası", amount: "1 yemek kaşığı", grams: 15 },
{ item: "Marul", amount: "4 yaprak", grams: 40 },
{ item: "Limon", amount: "1/2 adet", grams: 30 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
],
calories: 400, protein: 20, carbs: 58, fat: 10,
},
{
name: "Sigara Böreği & Çorba",
ingredients: [
{ item: "Yufka (sigara böreği)", amount: "4 adet", grams: 120 },
{ item: "Beyaz peynir", amount: "60g", grams: 60 },
{ item: "Maydanoz", amount: "10g", grams: 10 },
{ item: "Zeytinyağı (kızartma)", amount: "15g", grams: 15 },
{ item: "Domates çorbası", amount: "250ml", grams: 250 },
],
calories: 420, protein: 18, carbs: 36, fat: 22,
},
{
name: "Çoban Salatası & Humus & Ekmek",
ingredients: [
{ item: "Domates", amount: "2 adet", grams: 200 },
{ item: "Salatalık", amount: "1 adet", grams: 100 },
{ item: "Soğan", amount: "1/2 adet", grams: 40 },
{ item: "Biber", amount: "1 adet", grams: 30 },
{ item: "Humus", amount: "100g", grams: 100 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
{ item: "Tam buğday ekmek", amount: "2 dilim", grams: 60 },
],
calories: 400, protein: 16, carbs: 42, fat: 20,
},
{
name: "Havuç Tarator",
ingredients: [
{ item: "Havuç", amount: "300g", grams: 300 },
{ item: "Yoğurt", amount: "200g", grams: 200 },
{ item: "Sarımsak", amount: "2 diş", grams: 6 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
{ item: "Ceviz", amount: "20g", grams: 20 },
{ item: "Tam buğday ekmek", amount: "2 dilim", grams: 60 },
],
calories: 380, protein: 16, carbs: 44, fat: 16,
},
{
name: "İçli Köfte",
ingredients: [
{ item: "İnce bulgur (dış)", amount: "100g", grams: 100 },
{ item: "Dana kıyma (iç)", amount: "80g", grams: 80 },
{ item: "Soğan", amount: "1/2 adet", grams: 40 },
{ item: "Ceviz", amount: "15g", grams: 15 },
{ item: "Maydanoz", amount: "10g", grams: 10 },
{ item: "Yoğurt", amount: "100g", grams: 100 },
],
calories: 470, protein: 28, carbs: 52, fat: 16,
},
{
name: "Balık Izgara & Roka Salatası",
ingredients: [
{ item: "Levrek veya çipura", amount: "200g", grams: 200 },
{ item: "Roka", amount: "50g", grams: 50 },
{ item: "Limon", amount: "1/2 adet", grams: 30 },
{ item: "Zeytinyağı", amount: "1 yemek kaşığı", grams: 14 },
{ item: "Domates", amount: "1/2 adet", grams: 60 },
{ item: "Tam buğday ekmek", amount: "1 dilim", grams: 30 },
],
calories: 380, protein: 42, carbs: 16, fat: 16,
},
];
const TURKISH_SNACKS = [
{ name: "Ayran & Ceviz", ingredients: [{ item: "Ayran", amount: "1 bardak", grams: 200 }, { item: "Ceviz", amount: "30g", grams: 30 }], calories: 240, protein: 10, carbs: 10, fat: 18 },
{ name: "Meyve & Yoğurt", ingredients: [{ item: "Mevsim meyvesi", amount: "1 porsiyon", grams: 150 }, { item: "Yoğurt", amount: "100g", grams: 100 }], calories: 170, protein: 6, carbs: 28, fat: 4 },
{ name: "Kuru Meyve & Fındık", ingredients: [{ item: "Kuru kayısı", amount: "4 adet", grams: 30 }, { item: "Fındık", amount: "20g", grams: 20 }, { item: "Badem", amount: "10g", grams: 10 }], calories: 200, protein: 5, carbs: 22, fat: 12 },
{ name: "Peynirli Kraker", ingredients: [{ item: "Tam buğday kraker", amount: "4 adet", grams: 30 }, { item: "Beyaz peynir", amount: "30g", grams: 30 }], calories: 170, protein: 8, carbs: 18, fat: 8 },
{ name: "Muzlu Süt", ingredients: [{ item: "Muz", amount: "1 adet", grams: 120 }, { item: "Süt (yarım yağlı)", amount: "200ml", grams: 200 }], calories: 210, protein: 8, carbs: 34, fat: 4 },
{ name: "Havuç & Humus", ingredients: [{ item: "Havuç çubukları", amount: "100g", grams: 100 }, { item: "Humus", amount: "50g", grams: 50 }], calories: 160, protein: 6, carbs: 18, fat: 8 },
{ name: "Yumurta (Haşlanmış)", ingredients: [{ item: "Yumurta", amount: "2 adet", grams: 120 }, { item: "Tuz & karabiber", amount: "az", grams: 1 }], calories: 155, protein: 13, carbs: 1, fat: 11 },
{ name: "Elma & Fıstık Ezmesi", ingredients: [{ item: "Elma", amount: "1 adet", grams: 180 }, { item: "Fıstık ezmesi", amount: "1 yemek kaşığı", grams: 16 }], calories: 190, protein: 5, carbs: 28, fat: 8 },
{ name: "Lor Peyniri & Domates", ingredients: [{ item: "Lor peyniri", amount: "80g", grams: 80 }, { item: "Domates", amount: "1/2 adet", grams: 60 }, { item: "Zeytinyağı", amount: "az", grams: 5 }], calories: 140, protein: 12, carbs: 6, fat: 8 },
{ name: "Türk Kahvesi & Hurma", ingredients: [{ item: "Türk kahvesi", amount: "1 fincan", grams: 60 }, { item: "Hurma", amount: "3 adet", grams: 30 }], calories: 100, protein: 1, carbs: 22, fat: 1 },
];
// ==================== USA / DEFAULT MEALS ====================
const USA_BREAKFASTS = [
{
name: "Oatmeal with Berries",
ingredients: [
{ item: "Rolled oats", amount: "80g", grams: 80 },
{ item: "Milk (low-fat)", amount: "200ml", grams: 200 },
{ item: "Mixed berries", amount: "100g", grams: 100 },
{ item: "Honey", amount: "1 tsp", grams: 7 },
{ item: "Almonds (sliced)", amount: "15g", grams: 15 },
],
calories: 400, protein: 16, carbs: 58, fat: 12,
},
{
name: "Scrambled Eggs & Toast",
ingredients: [
{ item: "Eggs", amount: "3", grams: 180 },
{ item: "Whole wheat toast", amount: "2 slices", grams: 60 },
{ item: "Butter", amount: "10g", grams: 10 },
{ item: "Avocado", amount: "1/4", grams: 50 },
{ item: "Cherry tomatoes", amount: "5", grams: 75 },
],
calories: 450, protein: 26, carbs: 30, fat: 26,
},
{
name: "Greek Yogurt Parfait",
ingredients: [
{ item: "Greek yogurt", amount: "200g", grams: 200 },
{ item: "Granola", amount: "40g", grams: 40 },
{ item: "Banana", amount: "1/2", grams: 60 },
{ item: "Honey", amount: "1 tbsp", grams: 20 },
{ item: "Chia seeds", amount: "10g", grams: 10 },
],
calories: 420, protein: 24, carbs: 54, fat: 12,
},
{
name: "Protein Smoothie Bowl",
ingredients: [
{ item: "Banana (frozen)", amount: "1", grams: 120 },
{ item: "Protein powder", amount: "1 scoop (30g)", grams: 30 },
{ item: "Almond milk", amount: "150ml", grams: 150 },
{ item: "Peanut butter", amount: "1 tbsp", grams: 16 },
{ item: "Granola topping", amount: "20g", grams: 20 },
],
calories: 430, protein: 30, carbs: 48, fat: 14,
},
{
name: "Avocado Toast with Egg",
ingredients: [
{ item: "Whole wheat bread", amount: "2 slices", grams: 60 },
{ item: "Avocado", amount: "1/2", grams: 100 },
{ item: "Egg (poached)", amount: "2", grams: 120 },
{ item: "Red pepper flakes", amount: "pinch", grams: 1 },
{ item: "Lemon juice", amount: "1 tsp", grams: 5 },
],
calories: 440, protein: 20, carbs: 32, fat: 26,
},
];
const USA_LUNCHES = [
{
name: "Grilled Chicken Salad",
ingredients: [
{ item: "Chicken breast (grilled)", amount: "180g", grams: 180 },
{ item: "Mixed greens", amount: "100g", grams: 100 },
{ item: "Cherry tomatoes", amount: "80g", grams: 80 },
{ item: "Cucumber", amount: "80g", grams: 80 },
{ item: "Olive oil dressing", amount: "1 tbsp", grams: 14 },
{ item: "Feta cheese", amount: "30g", grams: 30 },
],
calories: 420, protein: 44, carbs: 12, fat: 22,
},
{
name: "Turkey Wrap",
ingredients: [
{ item: "Whole wheat tortilla", amount: "1 large", grams: 60 },
{ item: "Turkey breast slices", amount: "120g", grams: 120 },
{ item: "Lettuce", amount: "30g", grams: 30 },
{ item: "Tomato", amount: "1/2", grams: 60 },
{ item: "Hummus", amount: "30g", grams: 30 },
{ item: "Swiss cheese", amount: "20g", grams: 20 },
],
calories: 400, protein: 36, carbs: 30, fat: 16,
},
{
name: "Salmon & Sweet Potato",
ingredients: [
{ item: "Salmon fillet", amount: "180g", grams: 180 },
{ item: "Sweet potato (baked)", amount: "200g", grams: 200 },
{ item: "Broccoli (steamed)", amount: "100g", grams: 100 },
{ item: "Olive oil", amount: "1 tsp", grams: 5 },
{ item: "Lemon", amount: "1/2", grams: 30 },
],
calories: 500, protein: 40, carbs: 42, fat: 18,
},
{
name: "Chicken & Brown Rice Bowl",
ingredients: [
{ item: "Chicken breast", amount: "180g", grams: 180 },
{ item: "Brown rice (cooked)", amount: "150g", grams: 150 },
{ item: "Mixed vegetables", amount: "100g", grams: 100 },
{ item: "Soy sauce", amount: "1 tbsp", grams: 15 },
{ item: "Sesame oil", amount: "1 tsp", grams: 5 },
],
calories: 480, protein: 42, carbs: 48, fat: 12,
},
{
name: "Tuna Sandwich",
ingredients: [
{ item: "Whole wheat bread", amount: "2 slices", grams: 60 },
{ item: "Canned tuna", amount: "120g", grams: 120 },
{ item: "Greek yogurt (instead of mayo)", amount: "30g", grams: 30 },
{ item: "Celery", amount: "30g", grams: 30 },
{ item: "Lettuce", amount: "20g", grams: 20 },
{ item: "Apple", amount: "1 small", grams: 120 },
],
calories: 420, protein: 38, carbs: 42, fat: 10,
},
];
const USA_DINNERS = [
{
name: "Grilled Steak & Vegetables",
ingredients: [
{ item: "Lean steak", amount: "180g", grams: 180 },
{ item: "Asparagus", amount: "100g", grams: 100 },
{ item: "Baked potato", amount: "150g", grams: 150 },
{ item: "Olive oil", amount: "1 tbsp", grams: 14 },
{ item: "Garlic", amount: "2 cloves", grams: 6 },
],
calories: 480, protein: 42, carbs: 30, fat: 22,
},
{
name: "Baked Chicken Thighs & Quinoa",
ingredients: [
{ item: "Chicken thighs (skinless)", amount: "200g", grams: 200 },
{ item: "Quinoa (cooked)", amount: "150g", grams: 150 },
{ item: "Roasted vegetables", amount: "150g", grams: 150 },
{ item: "Olive oil", amount: "1 tbsp", grams: 14 },
{ item: "Herbs", amount: "mixed", grams: 5 },
],
calories: 520, protein: 44, carbs: 38, fat: 20,
},
{
name: "Shrimp Stir-Fry",
ingredients: [
{ item: "Shrimp", amount: "200g", grams: 200 },
{ item: "Brown rice (cooked)", amount: "150g", grams: 150 },
{ item: "Bell peppers", amount: "100g", grams: 100 },
{ item: "Broccoli", amount: "80g", grams: 80 },
{ item: "Soy sauce", amount: "1 tbsp", grams: 15 },
{ item: "Sesame oil", amount: "1 tsp", grams: 5 },
],
calories: 440, protein: 38, carbs: 46, fat: 10,
},
{
name: "Turkey Meatballs & Pasta",
ingredients: [
{ item: "Ground turkey", amount: "150g", grams: 150 },
{ item: "Whole wheat pasta (cooked)", amount: "150g", grams: 150 },
{ item: "Marinara sauce", amount: "100g", grams: 100 },
{ item: "Parmesan", amount: "15g", grams: 15 },
{ item: "Side salad", amount: "80g", grams: 80 },
],
calories: 500, protein: 38, carbs: 50, fat: 16,
},
{
name: "Baked Cod & Roasted Vegetables",
ingredients: [
{ item: "Cod fillet", amount: "200g", grams: 200 },
{ item: "Zucchini", amount: "100g", grams: 100 },
{ item: "Cherry tomatoes", amount: "80g", grams: 80 },
{ item: "Olive oil", amount: "1 tbsp", grams: 14 },
{ item: "Brown rice (cooked)", amount: "120g", grams: 120 },
],
calories: 420, protein: 40, carbs: 34, fat: 14,
},
];
const USA_SNACKS = [
{ name: "Protein Bar", ingredients: [{ item: "Protein bar", amount: "1", grams: 60 }], calories: 220, protein: 20, carbs: 24, fat: 8 },
{ name: "Apple & Almond Butter", ingredients: [{ item: "Apple", amount: "1 medium", grams: 180 }, { item: "Almond butter", amount: "1 tbsp", grams: 16 }], calories: 190, protein: 4, carbs: 28, fat: 8 },
{ name: "Trail Mix", ingredients: [{ item: "Mixed nuts", amount: "20g", grams: 20 }, { item: "Dried cranberries", amount: "15g", grams: 15 }, { item: "Dark chocolate chips", amount: "10g", grams: 10 }], calories: 200, protein: 5, carbs: 20, fat: 12 },
{ name: "Cottage Cheese & Berries", ingredients: [{ item: "Cottage cheese", amount: "150g", grams: 150 }, { item: "Mixed berries", amount: "80g", grams: 80 }], calories: 170, protein: 18, carbs: 16, fat: 4 },
{ name: "Veggie Sticks & Hummus", ingredients: [{ item: "Carrot sticks", amount: "60g", grams: 60 }, { item: "Celery sticks", amount: "40g", grams: 40 }, { item: "Hummus", amount: "50g", grams: 50 }], calories: 150, protein: 6, carbs: 16, fat: 8 },
{ name: "Hard Boiled Eggs", ingredients: [{ item: "Eggs", amount: "2", grams: 120 }], calories: 155, protein: 13, carbs: 1, fat: 11 },
{ name: "Greek Yogurt & Honey", ingredients: [{ item: "Greek yogurt", amount: "150g", grams: 150 }, { item: "Honey", amount: "1 tsp", grams: 7 }], calories: 160, protein: 15, carbs: 16, fat: 4 },
{ name: "Banana & Peanut Butter", ingredients: [{ item: "Banana", amount: "1", grams: 120 }, { item: "Peanut butter", amount: "1 tbsp", grams: 16 }], calories: 210, protein: 6, carbs: 30, fat: 9 },
];
function getMealsByCountry(country) {
const c = (country || "").toLowerCase().trim();
if (c === "turkey" || c === "türkiye" || c === "turkiye") {
return {
breakfasts: TURKISH_BREAKFASTS,
lunches: TURKISH_LUNCHES,
dinners: TURKISH_DINNERS,
snacks: TURKISH_SNACKS,
};
}
if (c === "usa" || c === "united states" || c === "us" || c === "america") {
return {
breakfasts: USA_BREAKFASTS,
lunches: USA_LUNCHES,
dinners: USA_DINNERS,
snacks: USA_SNACKS,
};
}
// Default: Mediterranean mix (use USA meals as base)
return {
breakfasts: USA_BREAKFASTS,
lunches: USA_LUNCHES,
dinners: USA_DINNERS,
snacks: USA_SNACKS,
};
}
function scaleMeal(meal, targetCalories, mealCalorieShare) {
const target = targetCalories * mealCalorieShare;
const ratio = target / meal.calories;
return {
...meal,
calories: Math.round(meal.calories * ratio),
protein: Math.round(meal.protein * ratio),
carbs: Math.round(meal.carbs * ratio),
fat: Math.round(meal.fat * ratio),
ingredients: meal.ingredients.map((ing) => ({
...ing,
grams: Math.round(ing.grams * ratio),
})),
};
}
function pickDifferent(arr, count, usedIndices) {
const available = arr.map((item, idx) => ({ item, idx })).filter((x) => !usedIndices.has(x.idx));
const shuffled = available.sort(() => 0.5 - Math.random());
const picked = shuffled.slice(0, count);
picked.forEach((p) => usedIndices.add(p.idx));
if (picked.length < count) {
// Reset and pick more if needed
const extra = arr.map((item, idx) => ({ item, idx })).sort(() => 0.5 - Math.random()).slice(0, count - picked.length);
return [...picked.map((p) => p.item), ...extra.map((p) => p.item)];
}
return picked.map((p) => p.item);
}
function generateMealPlan(profile) {
const { weight, height, age, gender, activity_level, goal, country } = profile;
const bmr = calculateBMR(weight, height, age, gender);
const tdee = calculateTDEE(bmr, activity_level);
const targetCalories = Math.round(adjustCalories(tdee, goal));
const macros = calculateMacros(targetCalories, goal);
const meals = getMealsByCountry(country);
// Meal calorie distribution: breakfast 25%, snack1 10%, lunch 30%, snack2 10%, dinner 25%
const shares = { breakfast: 0.25, snack1: 0.10, lunch: 0.30, snack2: 0.10, dinner: 0.25 };
const dayNames = ["Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi", "Pazar"];
const usedBreakfasts = new Set();
const usedLunches = new Set();
const usedDinners = new Set();
const usedSnacks1 = new Set();
const usedSnacks2 = new Set();
const days = dayNames.map((dayName) => {
const breakfast = scaleMeal(pickDifferent(meals.breakfasts, 1, usedBreakfasts)[0], targetCalories, shares.breakfast);
const snack1 = scaleMeal(pickDifferent(meals.snacks, 1, usedSnacks1)[0], targetCalories, shares.snack1);
const lunch = scaleMeal(pickDifferent(meals.lunches, 1, usedLunches)[0], targetCalories, shares.lunch);
const snack2 = scaleMeal(pickDifferent(meals.snacks, 1, usedSnacks2)[0], targetCalories, shares.snack2);
const dinner = scaleMeal(pickDifferent(meals.dinners, 1, usedDinners)[0], targetCalories, shares.dinner);
const dayCalories = breakfast.calories + snack1.calories + lunch.calories + snack2.calories + dinner.calories;
return {
day: dayName,
total_calories: dayCalories,
meals: [
{ type: "Kahvaltı", ...breakfast },
{ type: "Ara Öğün 1", ...snack1 },
{ type: "Öğle Yemeği", ...lunch },
{ type: "Ara Öğün 2", ...snack2 },
{ type: "Akşam Yemeği", ...dinner },
],
};
});
return {
title: "Kişisel Beslenme Programı",
bmr: Math.round(bmr),
tdee: Math.round(tdee),
target_calories: targetCalories,
macros,
goal,
country: country || "Turkey",
notes: [
`Günlük hedef kalori: ${targetCalories} kcal`,
`Protein: ${macros.protein_g}g | Karbonhidrat: ${macros.carbs_g}g | Yağ: ${macros.fat_g}g`,
"Günde en az 2.5-3 litre su için",
"Öğün saatlerinizi düzenli tutun",
"Yemekleri yavaş yiyin, iyi çiğneyin",
],
days,
};
}
module.exports = { generateMealPlan };

162
src/services/shopping.js Normal file
View File

@@ -0,0 +1,162 @@
"use strict";
const CATEGORY_MAP = {
// Protein
"yumurta": "protein", "eggs": "protein", "egg": "protein",
"tavuk": "protein", "chicken": "protein", "turkey breast": "protein",
"dana": "protein", "beef": "protein", "steak": "protein", "lean steak": "protein",
"kıyma": "protein", "ground turkey": "protein", "ground beef": "protein",
"sucuk": "protein", "köfte": "protein", "kebap": "protein", "adana": "protein",
"balık": "protein", "levrek": "protein", "çipura": "protein", "salmon": "protein",
"cod": "protein", "tuna": "protein", "shrimp": "protein",
"et ": "protein", "kuşbaşı": "protein", "mantı": "protein",
"lahmacun": "protein", "pide hamuru": "protein",
"protein powder": "protein", "protein bar": "protein",
// Dairy
"peynir": "dairy", "cheese": "dairy", "feta": "dairy", "parmesan": "dairy", "swiss": "dairy",
"süt": "dairy", "milk": "dairy", "almond milk": "dairy",
"yoğurt": "dairy", "yogurt": "dairy", "greek yogurt": "dairy", "cottage cheese": "dairy",
"kaymak": "dairy", "tereyağı": "dairy", "butter": "dairy",
"ayran": "dairy", "lor": "dairy", "kaşar": "dairy",
"cream": "dairy",
// Vegetables
"domates": "vegetables", "tomato": "vegetables", "cherry tomato": "vegetables",
"salatalık": "vegetables", "cucumber": "vegetables",
"biber": "vegetables", "bell pepper": "vegetables", "pepper": "vegetables",
"soğan": "vegetables", "onion": "vegetables",
"sarımsak": "vegetables", "garlic": "vegetables",
"patlıcan": "vegetables", "havuç": "vegetables", "carrot": "vegetables",
"kabak": "vegetables", "zucchini": "vegetables",
"ıspanak": "vegetables", "ispanak": "vegetables", "spinach": "vegetables",
"marul": "vegetables", "lettuce": "vegetables", "mixed greens": "vegetables",
"roka": "vegetables", "asparagus": "vegetables", "broccoli": "vegetables",
"fasulye": "vegetables", "bamya": "vegetables", "taze fasulye": "vegetables",
"maydanoz": "vegetables", "dereotu": "vegetables", "nane": "vegetables",
"asma yaprağı": "vegetables", "celery": "vegetables",
"roasted vegetables": "vegetables", "mixed vegetables": "vegetables",
"sweet potato": "vegetables", "baked potato": "vegetables", "potato": "vegetables",
// Fruits
"muz": "fruits", "banana": "fruits",
"elma": "fruits", "apple": "fruits",
"meyve": "fruits", "berries": "fruits", "mixed berries": "fruits",
"limon": "fruits", "lemon": "fruits",
"nar ekşisi": "fruits", "cranberries": "fruits",
"hurma": "fruits", "kuru kayısı": "fruits",
// Grains
"bulgur": "grains", "pirinç": "grains", "pilav": "grains",
"rice": "grains", "brown rice": "grains", "quinoa": "grains",
"ekmek": "grains", "bread": "grains", "whole wheat": "grains", "toast": "grains",
"simit": "grains", "lavaş": "grains", "tortilla": "grains",
"un": "grains", "yufka": "grains",
"mercimek": "grains", "kuru fasulye": "grains", "nohut": "grains",
"oats": "grains", "granola": "grains", "pasta": "grains",
"chia": "grains", "kraker": "grains",
// Spices & Condiments
"pul biber": "spices", "tuz": "spices", "karabiber": "spices",
"sumak": "spices", "herbs": "spices", "red pepper": "spices",
"salça": "spices", "domates salçası": "spices",
"soy sauce": "spices", "sesame oil": "spices", "marinara": "spices",
"honey": "spices", "bal": "spices",
// Fats & Nuts
"zeytinyağı": "fats_nuts", "olive oil": "fats_nuts",
"zeytin": "fats_nuts",
"ceviz": "fats_nuts", "fındık": "fats_nuts", "badem": "fats_nuts",
"almonds": "fats_nuts", "peanut butter": "fats_nuts", "almond butter": "fats_nuts",
"fıstık ezmesi": "fats_nuts", "avocado": "fats_nuts",
"mixed nuts": "fats_nuts", "dark chocolate": "fats_nuts",
"humus": "fats_nuts",
// Beverages
"çay": "beverages", "kahve": "beverages", "türk kahvesi": "beverages",
"şalgam": "beverages",
"su": "beverages", "water": "beverages",
// Other
"turşu": "other",
};
function categorizeIngredient(itemName) {
const lower = itemName.toLowerCase();
for (const [keyword, category] of Object.entries(CATEGORY_MAP)) {
if (lower.includes(keyword)) {
return category;
}
}
return "other";
}
function generateShoppingList(mealPlan) {
const ingredientMap = {};
for (const day of mealPlan.days) {
for (const meal of day.meals) {
for (const ing of meal.ingredients) {
const key = ing.item.toLowerCase().trim();
if (!ingredientMap[key]) {
ingredientMap[key] = {
item: ing.item,
total_grams: 0,
category: categorizeIngredient(ing.item),
};
}
ingredientMap[key].total_grams += ing.grams;
}
}
}
const categories = {
protein: { name: "Protein Kaynakları", icon: "🥩", items: [] },
dairy: { name: "Süt Ürünleri", icon: "🧀", items: [] },
vegetables: { name: "Sebzeler", icon: "🥬", items: [] },
fruits: { name: "Meyveler", icon: "🍎", items: [] },
grains: { name: "Tahıllar & Baklagiller", icon: "🌾", items: [] },
fats_nuts: { name: "Yağlar & Kuruyemişler", icon: "🥜", items: [] },
spices: { name: "Baharat & Sos", icon: "🧂", items: [] },
beverages: { name: "İçecekler", icon: "🥤", items: [] },
other: { name: "Diğer", icon: "📦", items: [] },
};
for (const data of Object.values(ingredientMap)) {
const cat = categories[data.category] || categories.other;
cat.items.push({
item: data.item,
total_grams: Math.round(data.total_grams),
display_amount: formatAmount(data.total_grams),
});
}
// Sort items within each category alphabetically
for (const cat of Object.values(categories)) {
cat.items.sort((a, b) => a.item.localeCompare(b.item, "tr"));
}
// Remove empty categories
const result = {};
for (const [key, cat] of Object.entries(categories)) {
if (cat.items.length > 0) {
result[key] = cat;
}
}
return {
title: "Haftalık Alışveriş Listesi",
note: "Bu liste 7 günlük beslenme programınıza göre hazırlanmıştır.",
categories: result,
total_items: Object.values(ingredientMap).length,
};
}
function formatAmount(grams) {
if (grams >= 1000) {
return `${(grams / 1000).toFixed(1)} kg`;
}
return `${grams}g`;
}
module.exports = { generateShoppingList };

371
src/services/trainer.js Normal file
View File

@@ -0,0 +1,371 @@
"use strict";
const EXERCISES = {
chest: [
{ name: "Bench Press (Göğüs Presi)", sets: 4, reps: "8-10", rest_seconds: 90, notes: "Kontrollü iniş, göğüs kaslarını sıkın" },
{ name: "Incline Dumbbell Press (Üst Göğüs)", sets: 4, reps: "10-12", rest_seconds: 75, notes: "30 derece açı, tam hareket açıklığı" },
{ name: "Cable Flyes (Kablo Çapraz)", sets: 3, reps: "12-15", rest_seconds: 60, notes: "Kollar hafif bükük, göğüs merkezinde sıkın" },
{ name: "Dips (Paralel Bar)", sets: 3, reps: "10-12", rest_seconds: 75, notes: "Öne eğilin, göğüs hedefleyin" },
{ name: "Push-ups (Şınav)", sets: 3, reps: "15-20", rest_seconds: 45, notes: "Geniş tutuş, göğüs yere değsin" },
{ name: "Dumbbell Pullover", sets: 3, reps: "12", rest_seconds: 60, notes: "Kollar hafif bükük, göğüs gerilmesini hissedin" },
],
back: [
{ name: "Deadlift (Toplu Kaldırma)", sets: 4, reps: "6-8", rest_seconds: 120, notes: "Sırt düz, kalçadan kaldırın" },
{ name: "Barbell Row (Sırt Çekişi)", sets: 4, reps: "8-10", rest_seconds: 90, notes: "Gövde yere paralel, dirsekler geri" },
{ name: "Lat Pulldown (Lat Çekme)", sets: 4, reps: "10-12", rest_seconds: 75, notes: "Geniş tutuş, göğüse çekin" },
{ name: "Seated Cable Row (Oturarak Çekiş)", sets: 3, reps: "10-12", rest_seconds: 75, notes: "Sırt düz, kürek kemiklerini sıkın" },
{ name: "T-Bar Row", sets: 3, reps: "10-12", rest_seconds: 75, notes: "Göğüse doğru çekin, sırt kaslarını sıkın" },
{ name: "Face Pull (Yüz Çekişi)", sets: 3, reps: "15", rest_seconds: 45, notes: "Omuz arkası ve üst sırt hedefli" },
],
legs: [
{ name: "Squat (Çömelme)", sets: 4, reps: "8-10", rest_seconds: 120, notes: "Diz açısı 90 derece, sırt düz" },
{ name: "Romanian Deadlift (Romen Kaldırma)", sets: 4, reps: "10-12", rest_seconds: 90, notes: "Bacak arkası gerilmeli, kalça menteşesi" },
{ name: "Leg Press (Bacak Presi)", sets: 4, reps: "10-12", rest_seconds: 90, notes: "Ayaklar omuz genişliğinde" },
{ name: "Walking Lunges (Yürüyüş Hamle)", sets: 3, reps: "12 (her bacak)", rest_seconds: 75, notes: "Diz parmak ucunu geçmesin" },
{ name: "Leg Curl (Bacak Büküm)", sets: 3, reps: "12-15", rest_seconds: 60, notes: "Kontrollü hareket, hamstring sıkın" },
{ name: "Leg Extension (Bacak Açma)", sets: 3, reps: "12-15", rest_seconds: 60, notes: "Tepe noktada sıkın" },
{ name: "Calf Raise (Baldır Kaldırma)", sets: 4, reps: "15-20", rest_seconds: 45, notes: "Tam gerilme ve tam kasılma" },
],
shoulders: [
{ name: "Overhead Press (Omuz Presi)", sets: 4, reps: "8-10", rest_seconds: 90, notes: "Ayakta, core sıkı, başın üstüne itin" },
{ name: "Lateral Raise (Yan Kaldırma)", sets: 4, reps: "12-15", rest_seconds: 60, notes: "Hafif ağırlık, kontrollü hareket" },
{ name: "Front Raise (Ön Kaldırma)", sets: 3, reps: "12", rest_seconds: 60, notes: "Dönüşümlü kollarla" },
{ name: "Reverse Fly (Ters Sinek)", sets: 3, reps: "15", rest_seconds: 60, notes: "Arka omuz hedefli, kürek kemiklerini sıkın" },
{ name: "Arnold Press", sets: 3, reps: "10-12", rest_seconds: 75, notes: "Rotasyonlu hareket, omuzun 3 başını çalıştırır" },
{ name: "Shrugs (Omuz Silkme)", sets: 3, reps: "12-15", rest_seconds: 60, notes: "Trapez hedefli, yukarı çekin" },
],
arms: [
{ name: "Barbell Curl (Halter Biceps)", sets: 4, reps: "10-12", rest_seconds: 60, notes: "Dirsekler sabit, biceps sıkın" },
{ name: "Triceps Pushdown (Triceps İtme)", sets: 4, reps: "10-12", rest_seconds: 60, notes: "Dirsekler vücuda yapışık" },
{ name: "Hammer Curl (Çekiç Curl)", sets: 3, reps: "12", rest_seconds: 60, notes: "Nötr tutuş, ön kol da çalışır" },
{ name: "Overhead Triceps Extension", sets: 3, reps: "12", rest_seconds: 60, notes: "Dirsekler sabit, tam gerilme" },
{ name: "Concentration Curl (Konsantrasyon)", sets: 3, reps: "12 (her kol)", rest_seconds: 45, notes: "Yavaş ve kontrollü" },
{ name: "Skull Crushers", sets: 3, reps: "10-12", rest_seconds: 60, notes: "Alına doğru indirin, triceps sıkın" },
{ name: "Wrist Curl (Bilek Büküm)", sets: 2, reps: "15-20", rest_seconds: 30, notes: "Ön kol güçlendirme" },
],
cardio: [
{ name: "Koşu Bandı (Tempolu Koşu)", sets: 1, reps: "25 dakika", rest_seconds: 0, notes: "Kalp atış hızı %65-75 arası" },
{ name: "Bisiklet", sets: 1, reps: "20 dakika", rest_seconds: 0, notes: "Orta direnç, stabil tempo" },
{ name: "Kürek Çekme Makinesi", sets: 1, reps: "15 dakika", rest_seconds: 0, notes: "Tüm vücut kardiyosu" },
{ name: "Merdiven Çıkma (StairMaster)", sets: 1, reps: "15 dakika", rest_seconds: 0, notes: "Bacak ve kalça hedefli" },
{ name: "Eliptik Bisiklet", sets: 1, reps: "20 dakika", rest_seconds: 0, notes: "Düşük etki, tüm vücut" },
],
hiit: [
{ name: "Burpee", sets: 4, reps: "30 saniye", rest_seconds: 30, notes: "Maksimum efor" },
{ name: "Mountain Climber (Dağcı)", sets: 4, reps: "30 saniye", rest_seconds: 30, notes: "Core sıkı, hızlı hareket" },
{ name: "Jump Squat (Sıçramalı Çömelme)", sets: 4, reps: "30 saniye", rest_seconds: 30, notes: "Yumuşak iniş, diz koruması" },
{ name: "High Knees (Yüksek Diz)", sets: 4, reps: "30 saniye", rest_seconds: 30, notes: "Dizleri göğüse çekin" },
{ name: "Box Jump (Kutu Atlama)", sets: 4, reps: "30 saniye", rest_seconds: 30, notes: "İki ayakla atlayıp yumuşak inin" },
{ name: "Battle Ropes (Savaş İpleri)", sets: 4, reps: "30 saniye", rest_seconds: 30, notes: "Kollar ve core hedefli" },
{ name: "Kettlebell Swing", sets: 4, reps: "30 saniye", rest_seconds: 30, notes: "Kalça menteşesi, explosive hareket" },
],
core: [
{ name: "Plank (Düz Plank)", sets: 3, reps: "45 saniye", rest_seconds: 30, notes: "Vücut düz çizgi, core sıkı" },
{ name: "Russian Twist (Rus Bükülmesi)", sets: 3, reps: "20 (her taraf)", rest_seconds: 30, notes: "Ayaklar yerden, kontrollü rotasyon" },
{ name: "Bicycle Crunch (Bisiklet Karın)", sets: 3, reps: "20 (her taraf)", rest_seconds: 30, notes: "Karşı diz-dirsek buluşsun" },
{ name: "Leg Raise (Bacak Kaldırma)", sets: 3, reps: "15", rest_seconds: 30, notes: "Alt karın hedefli, sırt yere yapışık" },
{ name: "Dead Bug", sets: 3, reps: "12 (her taraf)", rest_seconds: 30, notes: "Sırt yere yapışık, kontrollü hareket" },
{ name: "Side Plank (Yan Plank)", sets: 2, reps: "30 saniye (her taraf)", rest_seconds: 30, notes: "Kalça düşmesin" },
],
flexibility: [
{ name: "Kedi-İnek Germe", sets: 1, reps: "10 tekrar", rest_seconds: 0, notes: "Nefesle senkronize" },
{ name: "Hamstring Germe", sets: 1, reps: "30 saniye (her bacak)", rest_seconds: 0, notes: "Bacak arkasını hissedin" },
{ name: "Quadriceps Germe", sets: 1, reps: "30 saniye (her bacak)", rest_seconds: 0, notes: "Dengeyi koruyun" },
{ name: "Güvercin Pozu", sets: 1, reps: "30 saniye (her taraf)", rest_seconds: 0, notes: "Kalça açıcı" },
{ name: "Çocuk Pozu", sets: 1, reps: "45 saniye", rest_seconds: 0, notes: "Sırt ve kalça rahatlama" },
{ name: "Kelebek Germe", sets: 1, reps: "30 saniye", rest_seconds: 0, notes: "İç bacak ve kasık" },
{ name: "Omuz Çapraz Germe", sets: 1, reps: "30 saniye (her kol)", rest_seconds: 0, notes: "Omuz esnekliği" },
{ name: "Aşağı Bakan Köpek", sets: 1, reps: "45 saniye", rest_seconds: 0, notes: "Tüm arka zincir" },
{ name: "Dünya Germe (World's Greatest Stretch)", sets: 1, reps: "5 tekrar (her taraf)", rest_seconds: 0, notes: "Tüm vücut mobilitesi" },
{ name: "Foam Rolling - Sırt", sets: 1, reps: "60 saniye", rest_seconds: 0, notes: "Yavaş ve kontrollü" },
{ name: "Foam Rolling - Bacaklar", sets: 1, reps: "60 saniye (her bacak)", rest_seconds: 0, notes: "Ağrılı noktada durun" },
],
compound: [
{ name: "Squat (Çömelme)", sets: 5, reps: "5", rest_seconds: 180, notes: "Ağır, compound hareket, güç odaklı" },
{ name: "Bench Press (Göğüs Presi)", sets: 5, reps: "5", rest_seconds: 180, notes: "Ağır yükleme, temel hareket" },
{ name: "Deadlift (Toplu Kaldırma)", sets: 5, reps: "5", rest_seconds: 180, notes: "En güçlü compound hareket" },
{ name: "Overhead Press (Omuz Presi)", sets: 5, reps: "5", rest_seconds: 150, notes: "Ayakta, tüm üst vücut" },
{ name: "Barbell Row (Sırt Çekişi)", sets: 5, reps: "5", rest_seconds: 150, notes: "Ağır, sırt kalınlığı" },
],
circuit: [
{ name: "Kettlebell Swing", sets: 3, reps: "15", rest_seconds: 15, notes: "Hızlı geçiş" },
{ name: "Push-ups (Şınav)", sets: 3, reps: "15", rest_seconds: 15, notes: "Hızlı geçiş" },
{ name: "Goblet Squat", sets: 3, reps: "15", rest_seconds: 15, notes: "Hızlı geçiş" },
{ name: "Dumbbell Row", sets: 3, reps: "12 (her kol)", rest_seconds: 15, notes: "Hızlı geçiş" },
{ name: "Jumping Lunges", sets: 3, reps: "12 (her bacak)", rest_seconds: 15, notes: "Hızlı geçiş" },
{ name: "Plank", sets: 3, reps: "30 saniye", rest_seconds: 60, notes: "Devrenin sonu, 60sn dinlenme sonra tekrar" },
],
};
function pickRandom(arr, count) {
const shuffled = [...arr].sort(() => 0.5 - Math.random());
return shuffled.slice(0, count);
}
function adjustForAge(exercises, age) {
if (age > 55) {
return exercises.map((e) => ({
...e,
sets: Math.max(2, e.sets - 1),
notes: e.notes + " | Yaşa uygun: Ağırlığı azaltın, kontrollü hareket",
}));
}
if (age < 20) {
return exercises.map((e) => ({
...e,
notes: e.notes + " | Genç sporcu: Form ve tekniğe odaklanın",
}));
}
return exercises;
}
function generateLoseWeightProgram(profile) {
const { age, gender } = profile;
const days = [
{
day: "Pazartesi",
type: "cardio",
title: "HIIT Kardiyo",
exercises: adjustForAge([...pickRandom(EXERCISES.hiit, 5), ...pickRandom(EXERCISES.core, 3)], age),
},
{
day: "Salı",
type: "strength",
title: "Üst Vücut Devre",
exercises: adjustForAge([...pickRandom(EXERCISES.chest, 2), ...pickRandom(EXERCISES.back, 2), ...pickRandom(EXERCISES.shoulders, 2), ...pickRandom(EXERCISES.core, 2)], age),
},
{
day: "Çarşamba",
type: "cardio",
title: "Kardiyo & Core",
exercises: adjustForAge([...pickRandom(EXERCISES.cardio, 3), ...pickRandom(EXERCISES.core, 4)], age),
},
{
day: "Perşembe",
type: "strength",
title: "Alt Vücut Devre",
exercises: adjustForAge([...pickRandom(EXERCISES.legs, 4), ...pickRandom(EXERCISES.core, 2)], age),
},
{
day: "Cuma",
type: "cardio",
title: "HIIT & Circuit",
exercises: adjustForAge(EXERCISES.circuit, age),
},
{
day: "Cumartesi",
type: "cardio",
title: "Uzun Kardiyo",
exercises: adjustForAge([
{ name: "Açık Hava Koşusu veya Yürüyüş", sets: 1, reps: "45-60 dakika", rest_seconds: 0, notes: "Orta tempo, yağ yakım bölgesi" },
...pickRandom(EXERCISES.flexibility, 4),
], age),
},
{
day: "Pazar",
type: "rest",
title: "Dinlenme & İyileşme",
exercises: adjustForAge(EXERCISES.flexibility, age),
},
];
return days;
}
function generateBuildMuscleProgram(profile) {
const { age, gender } = profile;
const days = [
{
day: "Pazartesi",
type: "strength",
title: "Göğüs & Triceps",
exercises: adjustForAge([...EXERCISES.chest.slice(0, 4), ...EXERCISES.arms.filter((e) => e.name.includes("Triceps") || e.name.includes("Skull")).slice(0, 3)], age),
},
{
day: "Salı",
type: "strength",
title: "Sırt & Biceps",
exercises: adjustForAge([...EXERCISES.back.slice(0, 4), ...EXERCISES.arms.filter((e) => e.name.includes("Curl") || e.name.includes("Hammer")).slice(0, 3)], age),
},
{
day: "Çarşamba",
type: "strength",
title: "Bacak Günü",
exercises: adjustForAge(EXERCISES.legs, age),
},
{
day: "Perşembe",
type: "rest",
title: "Aktif Dinlenme",
exercises: adjustForAge([
{ name: "Hafif Yürüyüş", sets: 1, reps: "20 dakika", rest_seconds: 0, notes: "Kan dolaşımı için" },
...pickRandom(EXERCISES.flexibility, 5),
], age),
},
{
day: "Cuma",
type: "strength",
title: "Omuz & Trapez",
exercises: adjustForAge([...EXERCISES.shoulders, ...pickRandom(EXERCISES.core, 3)], age),
},
{
day: "Cumartesi",
type: "strength",
title: "Kol Günü (Biceps & Triceps)",
exercises: adjustForAge([...EXERCISES.arms, ...pickRandom(EXERCISES.core, 2)], age),
},
{
day: "Pazar",
type: "rest",
title: "Tam Dinlenme",
exercises: adjustForAge(EXERCISES.flexibility, age),
},
];
return days;
}
function generateGainWeightProgram(profile) {
const { age, gender } = profile;
const days = [
{
day: "Pazartesi",
type: "strength",
title: "Compound Güç - Üst Vücut",
exercises: adjustForAge([...EXERCISES.compound.filter((e) => e.name.includes("Bench") || e.name.includes("Overhead") || e.name.includes("Row")), ...pickRandom(EXERCISES.chest, 2), ...pickRandom(EXERCISES.shoulders, 1)], age),
},
{
day: "Salı",
type: "strength",
title: "Compound Güç - Alt Vücut",
exercises: adjustForAge([...EXERCISES.compound.filter((e) => e.name.includes("Squat") || e.name.includes("Deadlift")), ...pickRandom(EXERCISES.legs, 3)], age),
},
{
day: "Çarşamba",
type: "rest",
title: "Dinlenme & Toparlanma",
exercises: adjustForAge(pickRandom(EXERCISES.flexibility, 6), age),
},
{
day: "Perşembe",
type: "strength",
title: "Göğüs, Sırt & Omuz",
exercises: adjustForAge([...pickRandom(EXERCISES.chest, 3), ...pickRandom(EXERCISES.back, 3), ...pickRandom(EXERCISES.shoulders, 2)], age),
},
{
day: "Cuma",
type: "strength",
title: "Bacak & Core",
exercises: adjustForAge([...pickRandom(EXERCISES.legs, 5), ...pickRandom(EXERCISES.core, 3)], age),
},
{
day: "Cumartesi",
type: "strength",
title: "Kollar & Yardımcı Kaslar",
exercises: adjustForAge([...EXERCISES.arms, ...pickRandom(EXERCISES.core, 2)], age),
},
{
day: "Pazar",
type: "rest",
title: "Tam Dinlenme",
exercises: adjustForAge(EXERCISES.flexibility, age),
},
];
return days;
}
function generateMaintainProgram(profile) {
const { age, gender } = profile;
const days = [
{
day: "Pazartesi",
type: "strength",
title: "Üst Vücut Kuvvet",
exercises: adjustForAge([...pickRandom(EXERCISES.chest, 2), ...pickRandom(EXERCISES.back, 2), ...pickRandom(EXERCISES.shoulders, 2)], age),
},
{
day: "Salı",
type: "cardio",
title: "Kardiyo & Core",
exercises: adjustForAge([...pickRandom(EXERCISES.cardio, 2), ...pickRandom(EXERCISES.core, 4)], age),
},
{
day: "Çarşamba",
type: "strength",
title: "Alt Vücut Kuvvet",
exercises: adjustForAge(pickRandom(EXERCISES.legs, 5), age),
},
{
day: "Perşembe",
type: "flexibility",
title: "Esneklik & Mobilite",
exercises: adjustForAge(EXERCISES.flexibility, age),
},
{
day: "Cuma",
type: "strength",
title: "Tüm Vücut",
exercises: adjustForAge([...pickRandom(EXERCISES.compound, 3), ...pickRandom(EXERCISES.arms, 2), ...pickRandom(EXERCISES.core, 2)], age),
},
{
day: "Cumartesi",
type: "cardio",
title: "Açık Hava Aktivitesi",
exercises: adjustForAge([
{ name: "Yürüyüş veya Koşu", sets: 1, reps: "30-45 dakika", rest_seconds: 0, notes: "Doğada, orta tempo" },
{ name: "Bisiklet veya Yüzme", sets: 1, reps: "30 dakika", rest_seconds: 0, notes: "Keyifli tempo" },
...pickRandom(EXERCISES.flexibility, 3),
], age),
},
{
day: "Pazar",
type: "rest",
title: "Tam Dinlenme",
exercises: adjustForAge(pickRandom(EXERCISES.flexibility, 5), age),
},
];
return days;
}
function generateWorkoutProgram(profile) {
const goalMap = {
lose_weight: generateLoseWeightProgram,
build_muscle: generateBuildMuscleProgram,
gain_weight: generateGainWeightProgram,
maintain: generateMaintainProgram,
};
const generator = goalMap[profile.goal] || goalMap.maintain;
const program = generator(profile);
const goalNames = {
lose_weight: "Yağ Yakma & Zayıflama",
build_muscle: "Kas Geliştirme",
gain_weight: "Kilo Alma & Güçlenme",
maintain: "Formu Koruma",
};
const activityNotes = {
sedentary: "Başlangıç seviyesi - yavaş başlayın, formu öğrenin",
light: "Hafif seviye - ağırlıkları kademeli artırın",
moderate: "Orta seviye - standart program",
active: "İleri seviye - ağırlıkları artırabilirsiniz",
very_active: "Elit seviye - süper setler ve drop setler ekleyebilirsiniz",
};
return {
title: `${goalNames[profile.goal] || "Fitness"} Programı`,
goal: profile.goal,
level: profile.activity_level,
note: activityNotes[profile.activity_level] || "",
general_tips: [
"Her antrenmandan önce 5-10 dakika ısınma yapın",
"Her antrenmandan sonra 5-10 dakika soğuma ve germe yapın",
"Günde en az 2-3 litre su için",
"Uyku düzeninize dikkat edin (7-9 saat)",
"Ağrı hissederseniz hareketi durdurun",
"Haftada en az 1 gün tam dinlenme yapın",
],
days: program,
};
}
module.exports = { generateWorkoutProgram };