Bug fixes and refactoring
This commit is contained in:
@@ -82,4 +82,4 @@ Error generating stack: `+n.message+`
|
|||||||
<div id='root'></div>
|
<div id='root'></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
<script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,UEsDBBQAAAgIAFsGgluswruJZgEAADIDAAAZAAAAYzM0ZWJiZWIzMjllNzhjNTQ4NWYuanNvbs2RMU/DMBCF/0p0sxO1btOm3lBZKiEWKiFBMrjOJTV17Mi+AFXV/45isiDBgFjYzrbefe89X6DRBnc1CFCLJR4OeFjwDa4LlS+LvAEW3+9lhyBAKtKvmAYMQTub1miQMDWubbFOA1IWelQZBWBAGCiAeL7E6cf96WbVcOT1Zr7iy4UqsDgs1CjXZEbiNktuIjR5+IQmaXIbscldxCYPSMCg9+4FFU0+1dG7Tg8dMDBOSdLOgrjEJL9LYbRFEGsGypmhsyCKK4N68NPKGQNpraN4nNKe+5HR6PcOv8ffirLculrbtizbc9cY91aWsa6y/Ju1a8WAZDsaqRi4gZSLdYST7nusx5okHUE8w263y5JH509uoGTvpTpp20LFwGMYzPRxX3IGkp72Ou7jM56nc57O5nvORT4TeZ7N+fIJRj358yTAfnKC3js/zYEkDeGLqf9WoSSS6tihjT1U45U7gWikCXitrh9QSwMEFAAACAgAWwaCW5FEcBSjAQAAcgMAAAsAAAByZXBvcnQuanNvbq2ST2+cMBDFvwqas1mFPwusb1VyWanqJZF6CHvwmoF11mBkD21WiO9e2RA1lZJD1fo0T/L49+aNZ+iRRCNIAJ9BSJqE/m7sFa0DniwMHAlLT6pH4ElZ5EV6yPdZXlUMmskKUmYAnhR5usuS8rCe8i5l0CqNDvjzHKpjAxxkluP5jOcsPWBZyX1e7VtYb34THuD56gfGDp1TZogb1EgYa9N12MQOaedGlDtywIDQ0fq+rz59Pz4UbYppc0iKNM9khdU5k75dkfbE+130JUCjxxUaxdFDwEZfAzZ6RAIGozUvKGnzKS/W9GrqgYE2ckthnfTvptBqQOAlA2n01A/Aq+V9sHcMxDAYCnKb9jZ6Rqtee/wY/8Dr+t40aujqurv1rTY/6zrEVdf/Zm05MSDReSMnBmYiaUIc7qrGERsfk6AL8Gc4Ho+7yP8jM1H0ZIW8qqED33QF3grtkIFFN+lth4JIyEuPQ9Cn5eRRjoSXM5AhoYEnDPB1REnYhGim4Q/ZanG9herNj+/wQLITBvPvtug5v/f432kM0Fpj36IatwXOy/ILUEsBAj8DFAAACAgAWwaCW6zCu4lmAQAAMgMAABkAAAAAAAAAAAAAALSBAAAAAGMzNGViYmViMzI5ZTc4YzU0ODVmLmpzb25QSwECPwMUAAAICABbBoJbkURwFKMBAAByAwAACwAAAAAAAAAAAAAAtIGdAQAAcmVwb3J0Lmpzb25QSwUGAAAAAAIAAgCAAAAAaQMAAAAA</script>
|
<script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,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</script>
|
||||||
@@ -11,6 +11,11 @@ export default defineConfig({
|
|||||||
baseURL: 'http://localhost:3000',
|
baseURL: 'http://localhost:3000',
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
},
|
},
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run dev:full',
|
||||||
|
url: 'http://localhost:3000',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
|
|||||||
123
server/package-lock.json
generated
123
server/package-lock.json
generated
@@ -13,7 +13,7 @@
|
|||||||
"@prisma/client": "^7.1.0",
|
"@prisma/client": "^7.1.0",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"bcryptjs": "3.0.3",
|
"bcryptjs": "3.0.3",
|
||||||
"better-sqlite3": "^12.5.0",
|
"better-sqlite3": "^11.0.0",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"dotenv": "17.2.3",
|
"dotenv": "17.2.3",
|
||||||
"express": "5.1.0",
|
"express": "5.1.0",
|
||||||
@@ -182,6 +182,20 @@
|
|||||||
"better-sqlite3": "^12.4.5"
|
"better-sqlite3": "^12.4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@prisma/adapter-better-sqlite3/node_modules/better-sqlite3": {
|
||||||
|
"version": "12.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.5.0.tgz",
|
||||||
|
"integrity": "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bindings": "^1.5.0",
|
||||||
|
"prebuild-install": "^7.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@prisma/client": {
|
"node_modules/@prisma/client": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.1.0.tgz",
|
||||||
@@ -434,15 +448,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/express": {
|
"node_modules/@types/express": {
|
||||||
"version": "5.0.5",
|
"version": "5.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
|
||||||
"integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==",
|
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/body-parser": "*",
|
"@types/body-parser": "*",
|
||||||
"@types/express-serve-static-core": "^5.0.0",
|
"@types/express-serve-static-core": "^5.0.0",
|
||||||
"@types/serve-static": "^1"
|
"@types/serve-static": "^2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/express-serve-static-core": {
|
"node_modules/@types/express-serve-static-core": {
|
||||||
@@ -476,13 +490,6 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/mime": {
|
|
||||||
"version": "1.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
|
||||||
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/ms": {
|
"node_modules/@types/ms": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||||
@@ -535,25 +542,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/serve-static": {
|
"node_modules/@types/serve-static": {
|
||||||
"version": "1.15.10",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
|
||||||
"integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
|
"integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/http-errors": "*",
|
"@types/http-errors": "*",
|
||||||
"@types/node": "*",
|
|
||||||
"@types/send": "<1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/serve-static/node_modules/@types/send": {
|
|
||||||
"version": "0.17.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
|
|
||||||
"integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/mime": "^1",
|
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -671,17 +666,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/better-sqlite3": {
|
"node_modules/better-sqlite3": {
|
||||||
"version": "12.5.0",
|
"version": "11.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz",
|
||||||
"integrity": "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==",
|
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bindings": "^1.5.0",
|
"bindings": "^1.5.0",
|
||||||
"prebuild-install": "^7.1.1"
|
"prebuild-install": "^7.1.1"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
@@ -1404,9 +1396,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/finalhandler": {
|
"node_modules/finalhandler": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
|
||||||
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
|
"integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.4.0",
|
"debug": "^4.4.0",
|
||||||
@@ -1417,7 +1409,11 @@
|
|||||||
"statuses": "^2.0.1"
|
"statuses": "^2.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 18.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/foreground-child": {
|
"node_modules/foreground-child": {
|
||||||
@@ -1672,28 +1668,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/http-errors": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"depd": "2.0.0",
|
"depd": "~2.0.0",
|
||||||
"inherits": "2.0.4",
|
"inherits": "~2.0.4",
|
||||||
"setprototypeof": "1.2.0",
|
"setprototypeof": "~1.2.0",
|
||||||
"statuses": "2.0.1",
|
"statuses": "~2.0.2",
|
||||||
"toidentifier": "1.0.1"
|
"toidentifier": "~1.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/http-errors/node_modules/statuses": {
|
"funding": {
|
||||||
"version": "2.0.1",
|
"type": "opencollective",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"url": "https://opencollective.com/express"
|
||||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/http-status-codes": {
|
"node_modules/http-status-codes": {
|
||||||
@@ -2046,15 +2037,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mime-types": {
|
"node_modules/mime-types": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
|
||||||
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
"integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime-db": "^1.54.0"
|
"mime-db": "^1.54.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mimic-response": {
|
"node_modules/mimic-response": {
|
||||||
@@ -2536,15 +2531,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/raw-body": {
|
"node_modules/raw-body": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
|
||||||
"integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==",
|
"integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "3.1.2",
|
"bytes": "~3.1.2",
|
||||||
"http-errors": "2.0.0",
|
"http-errors": "~2.0.1",
|
||||||
"iconv-lite": "0.7.0",
|
"iconv-lite": "~0.7.0",
|
||||||
"unpipe": "1.0.0"
|
"unpipe": "~1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"@prisma/client": "^7.1.0",
|
"@prisma/client": "^7.1.0",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"bcryptjs": "3.0.3",
|
"bcryptjs": "3.0.3",
|
||||||
"better-sqlite3": "^12.5.0",
|
"better-sqlite3": "^11.0.0",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"dotenv": "17.2.3",
|
"dotenv": "17.2.3",
|
||||||
"express": "5.1.0",
|
"express": "5.1.0",
|
||||||
|
|||||||
BIN
server/prisma/prisma/dev.db
Normal file
BIN
server/prisma/prisma/dev.db
Normal file
Binary file not shown.
BIN
server/prisma/prisma/dev.db-journal
Normal file
BIN
server/prisma/prisma/dev.db-journal
Normal file
Binary file not shown.
118
server/prisma/schema.migrate.prisma
Normal file
118
server/prisma/schema.migrate.prisma
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "sqlite"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
email String @unique
|
||||||
|
password String
|
||||||
|
role String @default("USER") // USER, ADMIN
|
||||||
|
isFirstLogin Boolean @default(true)
|
||||||
|
isBlocked Boolean @default(false)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
profile UserProfile?
|
||||||
|
sessions WorkoutSession[]
|
||||||
|
exercises Exercise[]
|
||||||
|
plans WorkoutPlan[]
|
||||||
|
weightRecords BodyWeightRecord[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model BodyWeightRecord {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
weight Float
|
||||||
|
date DateTime @default(now())
|
||||||
|
dateStr String // YYYY-MM-DD for unique constraint
|
||||||
|
|
||||||
|
@@unique([userId, dateStr])
|
||||||
|
}
|
||||||
|
|
||||||
|
model UserProfile {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String @unique
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
weight Float?
|
||||||
|
height Float?
|
||||||
|
gender String?
|
||||||
|
birthDate DateTime?
|
||||||
|
language String? @default("en")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Exercise {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String? // Null means system default
|
||||||
|
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
name String
|
||||||
|
type String // STRENGTH, CARDIO, BODYWEIGHT, STATIC
|
||||||
|
bodyWeightPercentage Float? @default(0)
|
||||||
|
isArchived Boolean @default(false)
|
||||||
|
isUnilateral Boolean @default(false)
|
||||||
|
|
||||||
|
sets WorkoutSet[]
|
||||||
|
planExercises PlanExercise[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model WorkoutSession {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
startTime DateTime
|
||||||
|
endTime DateTime?
|
||||||
|
userBodyWeight Float?
|
||||||
|
note String?
|
||||||
|
planId String?
|
||||||
|
planName String?
|
||||||
|
type String @default("STANDARD") // STANDARD, QUICK_LOG
|
||||||
|
|
||||||
|
sets WorkoutSet[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model WorkoutSet {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
sessionId String
|
||||||
|
session WorkoutSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||||||
|
exerciseId String
|
||||||
|
exercise Exercise @relation(fields: [exerciseId], references: [id])
|
||||||
|
|
||||||
|
order Int
|
||||||
|
weight Float?
|
||||||
|
reps Int?
|
||||||
|
distanceMeters Float?
|
||||||
|
durationSeconds Int?
|
||||||
|
height Float?
|
||||||
|
bodyWeightPercentage Float?
|
||||||
|
completed Boolean @default(true)
|
||||||
|
side String? // LEFT, RIGHT, or null for bilateral
|
||||||
|
timestamp DateTime @default(now())
|
||||||
|
}
|
||||||
|
|
||||||
|
model WorkoutPlan {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
name String
|
||||||
|
description String?
|
||||||
|
exercises String? // JSON string of exercise IDs (Deprecated, to be removed)
|
||||||
|
planExercises PlanExercise[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model PlanExercise {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
planId String
|
||||||
|
plan WorkoutPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
|
||||||
|
exerciseId String
|
||||||
|
exercise Exercise @relation(fields: [exerciseId], references: [id])
|
||||||
|
order Int
|
||||||
|
isWeighted Boolean @default(false)
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import authRoutes from './routes/auth';
|
import authRoutes from './routes/auth';
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
|
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3';
|
||||||
import BetterSqlite3 from 'better-sqlite3';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
// Ensure env vars are loaded
|
// Ensure env vars are loaded
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
@@ -18,6 +16,7 @@ if (!dbUrl) {
|
|||||||
throw new Error("DATABASE_URL environment variable is not set. Please check your .env file.");
|
throw new Error("DATABASE_URL environment variable is not set. Please check your .env file.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('Initializing Prisma Adapter with URL:', dbUrl);
|
||||||
const adapter = new PrismaBetterSqlite3({ url: dbUrl });
|
const adapter = new PrismaBetterSqlite3({ url: dbUrl });
|
||||||
|
|
||||||
const prisma =
|
const prisma =
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ router.post('/login', async (req, res) => {
|
|||||||
|
|
||||||
res.json({ success: true, user: userSafe, token });
|
res.json({ success: true, user: userSafe, token });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Login error:', error);
|
||||||
res.status(500).json({ error: 'Server error' });
|
res.status(500).json({ error: 'Server error' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
async function backup() {
|
|
||||||
try {
|
|
||||||
console.log('Starting backup...');
|
|
||||||
// Backup Quick Log sessions and their sets (formerly SporadicSets)
|
|
||||||
const quickLogSessions = await prisma.workoutSession.findMany({
|
|
||||||
where: { type: 'QUICK_LOG' },
|
|
||||||
include: { sets: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
const backupPath = path.join(__dirname, '../../sporadic_backup.json');
|
|
||||||
fs.writeFileSync(backupPath, JSON.stringify(quickLogSessions, null, 2));
|
|
||||||
console.log(`Backed up ${quickLogSessions.length} quick log sessions to ${backupPath}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Backup failed:', error);
|
|
||||||
} finally {
|
|
||||||
await prisma.$disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
backup();
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
async function migrate() {
|
|
||||||
console.log('Starting migration...');
|
|
||||||
const plans = await prisma.workoutPlan.findMany();
|
|
||||||
console.log(`Found ${plans.length} plans.`);
|
|
||||||
|
|
||||||
for (const plan of plans) {
|
|
||||||
if (plan.exercises) {
|
|
||||||
try {
|
|
||||||
const steps = JSON.parse(plan.exercises);
|
|
||||||
console.log(`Migrating plan ${plan.name} (${plan.id}) with ${steps.length} steps.`);
|
|
||||||
|
|
||||||
let order = 0;
|
|
||||||
for (const step of steps) {
|
|
||||||
await prisma.planExercise.create({
|
|
||||||
data: {
|
|
||||||
planId: plan.id,
|
|
||||||
exerciseId: step.exerciseId,
|
|
||||||
order: order++,
|
|
||||||
isWeighted: step.isWeighted || false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Error parsing JSON for plan ${plan.id}:`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('Migration complete.');
|
|
||||||
}
|
|
||||||
|
|
||||||
migrate()
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
})
|
|
||||||
.finally(async () => {
|
|
||||||
await prisma.$disconnect();
|
|
||||||
});
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
async function restore() {
|
|
||||||
try {
|
|
||||||
const backupPath = path.join(__dirname, '../../sporadic_backup.json');
|
|
||||||
if (!fs.existsSync(backupPath)) {
|
|
||||||
console.error('Backup file not found!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sporadicSets = JSON.parse(fs.readFileSync(backupPath, 'utf-8'));
|
|
||||||
console.log(`Found ${sporadicSets.length} sporadic sets to restore.`);
|
|
||||||
|
|
||||||
for (const set of sporadicSets) {
|
|
||||||
const date = new Date(set.timestamp);
|
|
||||||
const startOfDay = new Date(date);
|
|
||||||
startOfDay.setHours(0, 0, 0, 0);
|
|
||||||
const endOfDay = new Date(date);
|
|
||||||
endOfDay.setHours(23, 59, 59, 999);
|
|
||||||
|
|
||||||
// Find or create a QUICK_LOG session for this day
|
|
||||||
let session = await prisma.workoutSession.findFirst({
|
|
||||||
where: {
|
|
||||||
userId: set.userId,
|
|
||||||
type: 'QUICK_LOG',
|
|
||||||
startTime: {
|
|
||||||
gte: startOfDay,
|
|
||||||
lte: endOfDay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
session = await prisma.workoutSession.create({
|
|
||||||
data: {
|
|
||||||
userId: set.userId,
|
|
||||||
startTime: startOfDay, // Use start of day as session start
|
|
||||||
type: 'QUICK_LOG',
|
|
||||||
note: 'Daily Quick Log'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log(`Created new QUICK_LOG session for ${startOfDay.toISOString()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the WorkoutSet
|
|
||||||
await prisma.workoutSet.create({
|
|
||||||
data: {
|
|
||||||
sessionId: session.id,
|
|
||||||
exerciseId: set.exerciseId,
|
|
||||||
order: 0, // Order doesn't matter much for sporadic sets, or we could increment
|
|
||||||
weight: set.weight,
|
|
||||||
reps: set.reps,
|
|
||||||
distanceMeters: set.distanceMeters,
|
|
||||||
durationSeconds: set.durationSeconds,
|
|
||||||
height: set.height,
|
|
||||||
bodyWeightPercentage: set.bodyWeightPercentage,
|
|
||||||
side: set.side,
|
|
||||||
timestamp: new Date(set.timestamp),
|
|
||||||
completed: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Restoration complete!');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Restoration failed:', error);
|
|
||||||
} finally {
|
|
||||||
await prisma.$disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
restore();
|
|
||||||
Reference in New Issue
Block a user