From ae372d352942a04f260760724a652149473a1ea0 Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Thu, 16 Oct 2025 22:16:18 -0700 Subject: [PATCH 01/17] installed the frontend and backend dependencies, and configured db.env, created MySQL database --- server/package-lock.json | 337 ++++++++++++++++++++++++++++++++++++++- server/package.json | 5 +- 2 files changed, 339 insertions(+), 3 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index 0b7b353..3810e9b 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,13 +10,46 @@ "license": "ISC", "dependencies": { "cors": "^2.8.5", + "dotenv": "^17.2.3", "esm": "^3.2.25", - "express": "^5.1.0" + "express": "^5.1.0", + "mysql2": "^3.15.2", + "sequelize": "^6.37.7" }, "devDependencies": { "nodemon": "^3.1.10" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.8.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.8.1.tgz", + "integrity": "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.14.0" + } + }, + "node_modules/@types/validator": { + "version": "13.15.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz", + "integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==", + "license": "MIT" + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -44,6 +77,15 @@ "node": ">= 8" } }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -247,6 +289,15 @@ } } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -256,6 +307,24 @@ "node": ">= 0.8" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dottie": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz", + "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==", + "license": "MIT" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -453,6 +522,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -593,6 +671,15 @@ "dev": true, "license": "ISC" }, + "node_modules/inflection": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz", + "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==", + "engines": [ + "node >= 0.4.0" + ], + "license": "MIT" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -660,6 +747,48 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -724,12 +853,81 @@ "node": "*" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, "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/mysql2": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.2.tgz", + "integrity": "sha512-kFm5+jbwR5mC+lo+3Cy46eHiykWSpUtTLOH3GE+AR7GeLq8PgfJcvpMiyVWk9/O53DjQsqm6a3VOOfq7gYWFRg==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -839,6 +1037,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -940,6 +1144,12 @@ "node": ">=8.10.0" } }, + "node_modules/retry-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz", + "integrity": "sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==", + "license": "MIT" + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -986,7 +1196,6 @@ "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1017,6 +1226,82 @@ "node": ">= 18" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/sequelize": { + "version": "6.37.7", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.7.tgz", + "integrity": "sha512-mCnh83zuz7kQxxJirtFD7q6Huy6liPanI67BSlbzSYgVNl5eXVdE2CN1FuAeZwG1SNpGsNRCV+bJAVVnykZAFA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/sequelize" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.8", + "@types/validator": "^13.7.17", + "debug": "^4.3.4", + "dottie": "^2.0.6", + "inflection": "^1.13.4", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "moment-timezone": "^0.5.43", + "pg-connection-string": "^2.6.1", + "retry-as-promised": "^7.0.4", + "semver": "^7.5.4", + "sequelize-pool": "^7.1.0", + "toposort-class": "^1.0.1", + "uuid": "^8.3.2", + "validator": "^13.9.0", + "wkx": "^0.5.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependenciesMeta": { + "ibm_db": { + "optional": true + }, + "mariadb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-hstore": { + "optional": true + }, + "snowflake-sdk": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/sequelize-pool": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz", + "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/serve-static": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", @@ -1123,6 +1408,15 @@ "node": ">=10" } }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -1167,6 +1461,12 @@ "node": ">=0.6" } }, + "node_modules/toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==", + "license": "MIT" + }, "node_modules/touch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", @@ -1198,6 +1498,12 @@ "dev": true, "license": "MIT" }, + "node_modules/undici-types": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1207,6 +1513,24 @@ "node": ">= 0.8" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1216,6 +1540,15 @@ "node": ">= 0.8" } }, + "node_modules/wkx": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz", + "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/server/package.json b/server/package.json index 402bb6b..54c21a6 100644 --- a/server/package.json +++ b/server/package.json @@ -12,8 +12,11 @@ "description": "", "dependencies": { "cors": "^2.8.5", + "dotenv": "^17.2.3", "esm": "^3.2.25", - "express": "^5.1.0" + "express": "^5.1.0", + "mysql2": "^3.15.2", + "sequelize": "^6.37.7" }, "type": "module", "devDependencies": { From 2a9102a60a5b5274b105854593a29490b3d36f6b Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Tue, 21 Oct 2025 11:18:59 -0700 Subject: [PATCH 02/17] dockerized backedned and database, setup MySQL databae in docker compose --- docker-compose.yml | 34 ++++++++++++++++++++++++++++++++++ server/dockerfile | 18 ++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 docker-compose.yml create mode 100644 server/dockerfile diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8df38e5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +version: '3.9' + +services: + backend: + build: ./server + container_name: pixel_backend + restart: always + ports: + - "3001:3001" + environment: + - DB_USER=pixel_user + - DB_PASSWORD=PixelPattern123! + - DB_HOST=db + - DB_DATABASE=pixel_to_pattern + - DB_PORT=3306 + depends_on: + - db + + db: + image: mysql:8 + container_name: pixel_db + restart: always + environment: + MYSQL_ROOT_PASSWORD: Xyz123!abcd + MYSQL_DATABASE: pixel_to_pattern + MYSQL_USER: pixel_user + MYSQL_PASSWORD: PixelPattern123! + ports: + - "3307:3306" + volumes: + - db_data:/var/lib/mysql + +volumes: + db_data: diff --git a/server/dockerfile b/server/dockerfile new file mode 100644 index 0000000..dc28627 --- /dev/null +++ b/server/dockerfile @@ -0,0 +1,18 @@ +# server/Dockerfile +FROM node:18-alpine + +# Create app directory +WORKDIR /usr/src/app + +# Install dependencies +COPY package*.json ./ +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Expose backend port +EXPOSE 3001 + +# Start the backend server +CMD ["npm", "run", "dev"] From 38306c0e9b12790b4fd6c43a887809c2a1876ee5 Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Tue, 21 Oct 2025 11:38:32 -0700 Subject: [PATCH 03/17] dockerized the frontend and updated the docker-compose.yml with frontend --- docker-compose.yml | 13 ++++++++++++- pixel2pattern/dockerfile | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 pixel2pattern/dockerfile diff --git a/docker-compose.yml b/docker-compose.yml index 8df38e5..013bc87 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,17 @@ version: '3.9' services: + frontend: + build: ./pixel2pattern + container_name: pixel_frontend + restart: always + ports: + - "3000:3000" + environment: + - NEXT_PUBLIC_BACKEND_URL=http://backend:3001 + depends_on: + - backend + backend: build: ./server container_name: pixel_backend @@ -15,7 +26,7 @@ services: - DB_PORT=3306 depends_on: - db - + db: image: mysql:8 container_name: pixel_db diff --git a/pixel2pattern/dockerfile b/pixel2pattern/dockerfile new file mode 100644 index 0000000..3d4c41d --- /dev/null +++ b/pixel2pattern/dockerfile @@ -0,0 +1,12 @@ +# pixel2pattern/Dockerfile +FROM node:18-alpine as build + +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +# Serve build using Next.js built-in server +EXPOSE 3000 +CMD ["npm", "start"] From 6c741df3845f39d26aee04a12dfb19f1fa16bfaf Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Wed, 22 Oct 2025 13:45:08 -0700 Subject: [PATCH 04/17] deployed to VM, worked for me.., please check if it worked out for you --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 618e6b8..d3f172d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +## Alston and Danny - Working on Sprint 3 + # 🎨 Pixel to Pattern - Create your perfect piece! **Pixel to Pattern** turns your pixel art into beautiful, beginner friendly crochet patterns stitch by stitch, row by row. From 17d0910e3cae04cc79d26ce37e3c251423844500 Mon Sep 17 00:00:00 2001 From: dannymccarragher Date: Thu, 23 Oct 2025 10:27:29 -0700 Subject: [PATCH 05/17] comment out env injection since using docker now --- server/models/db.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/models/db.js b/server/models/db.js index 365de29..a4a0d34 100644 --- a/server/models/db.js +++ b/server/models/db.js @@ -3,10 +3,10 @@ import { Sequelize } from 'sequelize'; import path from 'path'; -const envPath = path.resolve(import.meta.dirname, '../../db.env'); -dotenv.config({ - path: envPath -}); +// const envPath = path.resolve(import.meta.dirname, '../../db.env'); +// dotenv.config({ +// path: envPath +// }); const { DB_USER, DB_PASSWORD, DB_HOST, DB_DATABASE, DB_PORT } = process.env; From 67b10e1e7c5ee3194618e87aef2c49e89d2885e7 Mon Sep 17 00:00:00 2001 From: dannymccarragher Date: Thu, 23 Oct 2025 11:11:17 -0700 Subject: [PATCH 06/17] delete pattern functionalilty v1 --- server/controllers/controller.js | 16 ++++++++++++++++ server/models/db.js | 10 ++++++---- server/models/model.js | 12 ++++++++++++ server/routes/router.js | 3 ++- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/server/controllers/controller.js b/server/controllers/controller.js index 7700707..fa424d3 100644 --- a/server/controllers/controller.js +++ b/server/controllers/controller.js @@ -50,4 +50,20 @@ export const uploadPattern = async (req, res) => { } catch (err) { return res.status(500).json({ error: err.message}); } +} + +// DELETE Pattern + +export const deletePattern = async (req, res) => { + + const pattern = req.body + + try{ + const patternID = await deletePattern(pattern); + res.status(201).json(patternID); + } catch (err){ + return res.status(500).json({ error : err.message }) + } + + } \ No newline at end of file diff --git a/server/models/db.js b/server/models/db.js index a4a0d34..aed1243 100644 --- a/server/models/db.js +++ b/server/models/db.js @@ -3,14 +3,16 @@ import { Sequelize } from 'sequelize'; import path from 'path'; -// const envPath = path.resolve(import.meta.dirname, '../../db.env'); -// dotenv.config({ -// path: envPath -// }); +const envPath = path.resolve(import.meta.dirname, '../../db.env'); +dotenv.config({ + path: envPath +}); + const { DB_USER, DB_PASSWORD, DB_HOST, DB_DATABASE, DB_PORT } = process.env; + // set up sequelize connection to database const sequelize = new Sequelize( DB_DATABASE, DB_USER, DB_PASSWORD, { diff --git a/server/models/model.js b/server/models/model.js index a34ba51..b7291b5 100644 --- a/server/models/model.js +++ b/server/models/model.js @@ -44,4 +44,16 @@ export const postPattern = async (pattern) => { console.error("post Pattern error: ", err); throw new Error('Failed to post pattern to database'); } +} + +export const deletePattern = async (pattern) => { + + try{ + const dbPattern = await Patterns.destroy(pattern); + return dbPattern.pattern_ID; + } catch (err) { + console.error("Error deleting pattern:", err); + throw new Error('Failed to delete pattern from database'); + } + } \ No newline at end of file diff --git a/server/routes/router.js b/server/routes/router.js index f44f70d..10d9743 100644 --- a/server/routes/router.js +++ b/server/routes/router.js @@ -1,5 +1,5 @@ import {Router} from 'express'; -import {getAll, getSpecificPattern, uploadPattern, updatePatternController} from '../controllers/controller.js'; +import {getAll, getSpecificPattern, uploadPattern, updatePatternController, deletePattern} from '../controllers/controller.js'; const router = Router(); @@ -7,5 +7,6 @@ router.get('/patterns', getAll); router.get('/patterns/:id', getSpecificPattern); router.post('/patterns', uploadPattern); router.patch('/update/:id', updatePatternController); +router.delete('/delete/:id', deletePattern); export default router; \ No newline at end of file From 58b04d64be4c04d6f34e34412ce91e8fb46fb48e Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Fri, 24 Oct 2025 15:16:58 -0700 Subject: [PATCH 07/17] updated readme file with docker setup, vm deployment --- README.md | 118 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 103 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d3f172d..9c52135 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,8 @@ -## Alston and Danny - Working on Sprint 3 - # 🎨 Pixel to Pattern - Create your perfect piece! **Pixel to Pattern** turns your pixel art into beautiful, beginner friendly crochet patterns stitch by stitch, row by row. Let the creativity flow! - - - ## 🧶 Features ### Create @@ -27,9 +22,6 @@ Users will soon be able to edit their own patterns directly. ### Delete Remove any pattern you’ve posted with one click. - - - ## ⚙️ Local Setup Follow these steps to run **Pixel to Pattern** locally: @@ -51,18 +43,12 @@ Follow these steps to run **Pixel to Pattern** locally: 6. Open your browser at http://localhost:3000 7. 🎨 Get creative! - - - ## Tech Stack - **Frontend:** NextJs, MaterialUI - **Backend:** Node.js, Express - **Database:** MySQL database with Sequelize used on the backend. - **Version:** Node 24+ - - - ## Environment Variables This project utilizes environment variables for configuration. You need to create a `db.env` file in the root directory based on the provided variable examples listed below. @@ -81,4 +67,106 @@ Restart your development server if it's already running (e.g., npm start). ## Deployment Process Linked below is the documentation that was created while setting up the virtual machine for deployment. -[Click Here!](https://loving-eye-8b5.notion.site/VM-Deployment-27e101a39e1480328574fee619f042d8) \ No newline at end of file +[Click Here!](https://loving-eye-8b5.notion.site/VM-Deployment-27e101a39e1480328574fee619f042d8) + + +--- + +## 🐳 Docker Setup + +```bash +# Build containers +docker compose build + +# Run containers +docker compose up -d + +# Stop containers +docker compose down + +# View logs +docker compose logs -f +``` + +After running these commands, visit: + +- Frontend: [http://localhost:3000](http://localhost:3000) +- Backend: [http://localhost:3001/patterns](http://localhost:3001/patterns) + +--- + +## 🐳 VM Deployment + +```bash +ssh root@ +git clone https://github.com/your-username/pixel-to-pattern.git +cd pixel-to-pattern +docker compose up -d +``` + +Once the containers are up, the application will be accessible at: +``` +http://:3000 +``` + +--- + +## 🌿 Environment Variables for Docker + +These variables are used in the `docker-compose.yml` or `.env` file: + +``` +DB_USER=your_user +DB_PASSWORD=your_password +DB_HOST=db +DB_DATABASE=pixel_to_pattern +DB_PORT=3306 +``` + +✅ **Note:** +- `DB_HOST=db` is used so the backend connects to the MySQL container through the Docker network. +- Make sure these match your database credentials. + +--- + +## 💾 Volume Persistence + +A Docker volume is configured to persist the database data: +```yaml +volumes: + db_data: +``` + +This ensures that your MySQL data is **not lost** when containers stop or restart. + +To list volumes: +```bash +docker volume ls +``` + +To inspect: +```bash +docker volume inspect pixel-to-pattern_db_data +``` + + +--- + +## 🧰 Troubleshooting + +| Command | Purpose | +|-----------------------------------------|--------------------------------------------| +| `docker compose logs -f` | View live logs of all services | +| `docker logs ` | View logs for a specific container | +| `docker exec -it sh` | Open a shell inside a running container | +| `docker ps` | Check running containers | +| `docker system prune -a` | Clean up unused images and containers ⚠️ | +| `docker compose down -v` | Stop and remove containers **and volumes** (careful) | + +If something isn’t working: +- Ensure ports **3000** (frontend) and **3001** (backend) are open. +- Check logs for DB connection issues. +- Rebuild images if code changes: + ```bash + docker compose up --build -d + ``` From d31eb694fb9bcf2171833ddcbeac359307629a9b Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Tue, 28 Oct 2025 11:02:50 -0700 Subject: [PATCH 08/17] created a script automation deployment --- deploy.sh | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100755 deploy.sh diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..c586f53 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -e + +echo "Starting Pixel-to-Pattern deployment..." + +# System update +sudo apt update && sudo apt upgrade -y + +# Install Docker +if ! [ -x "$(command -v docker)" ]; then + echo "Installing Docker..." + curl -fsSL https://get.docker.com | sh +fi + +# Install Docker Compose +if ! [ -x "$(command -v docker compose)" ]; then + echo "🧩 Installing Docker Compose plugin..." + sudo apt install -y docker-compose-plugin +fi + +# Clone repo if not exists +if [ ! -d "pixel-to-pattern" ]; then + echo "Cloning repository..." + git clone https://github.com//pixel-to-pattern.git +fi + +cd pixel-to-pattern + +# Pull latest changes +git pull origin main + +# Build and run containers +echo "🛠 Building and starting containers..." +docker compose build +docker compose up -d + +echo "Deployment complete! App running at http://:3000" From fcdf67f40c214bf27b636b82360ed9cb2ab0b4da Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Tue, 28 Oct 2025 18:06:18 -0700 Subject: [PATCH 09/17] updated the frontend in docker file, fixed some typo error, and updated the API URL --- docker-compose.yml | 2 +- pixel2pattern/src/app/view/[id]/page.jsx | 4 +++- pixel2pattern/src/components/EditablePatternView.jsx | 4 +++- pixel2pattern/src/components/PixelForm.jsx | 3 ++- pixel2pattern/src/components/PostsCollection.jsx | 3 ++- server/controllers/controller.js | 2 +- server/models/db.js | 1 + 7 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 013bc87..068a79a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: ports: - "3000:3000" environment: - - NEXT_PUBLIC_BACKEND_URL=http://backend:3001 + - NEXT_PUBLIC_API_URL=http://143.198.63.60:3001 depends_on: - backend diff --git a/pixel2pattern/src/app/view/[id]/page.jsx b/pixel2pattern/src/app/view/[id]/page.jsx index 1145ffa..b73db6b 100644 --- a/pixel2pattern/src/app/view/[id]/page.jsx +++ b/pixel2pattern/src/app/view/[id]/page.jsx @@ -16,6 +16,8 @@ export default function PatternPage({params}) { const [patternConfig, setPatternConfig] = useState({}); const [editView, setEditView] = useState(false); + const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://backend:3001"; + const onCancel = () => { setEditView(false); } @@ -27,7 +29,7 @@ export default function PatternPage({params}) { useEffect(()=> { const fetchPost = async () => { try{ - const res = await fetch(`http://localhost:3001/patterns/${id}`); + const res = await fetch(`${API_URL}/patterns/${id}`); if(!res.ok) throw new Error(`Failed to fetch post with ID: ${id}`); const post = await res.json(); setPost(post); diff --git a/pixel2pattern/src/components/EditablePatternView.jsx b/pixel2pattern/src/components/EditablePatternView.jsx index 1d2e271..211a8cd 100644 --- a/pixel2pattern/src/components/EditablePatternView.jsx +++ b/pixel2pattern/src/components/EditablePatternView.jsx @@ -8,6 +8,8 @@ import PixelDisplay from "@/components/PixelDisplay"; export default function EditablePatternView({ post, onCancel, params}) { const { id } = useParams(); + const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://backend:3001"; + const [formData, setFormData] = useState({ pattern_ID: id, pattern_name: post.pattern_name, @@ -24,7 +26,7 @@ export default function EditablePatternView({ post, onCancel, params}) { const handleSubmit = async(e) => { try{ - const res = await fetch(`http://localhost:3001/update/${id}`, + const res = await fetch(`${API_URL}/update/${id}`, { method: 'PATCH', headers: {"Content-Type": "application/json"}, diff --git a/pixel2pattern/src/components/PixelForm.jsx b/pixel2pattern/src/components/PixelForm.jsx index b366459..ea3f37a 100644 --- a/pixel2pattern/src/components/PixelForm.jsx +++ b/pixel2pattern/src/components/PixelForm.jsx @@ -27,6 +27,7 @@ export default function PixelForm() { const [ pixelFill, setPixelFill] = useState([]); const router = useRouter(); + const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://backend:3001"; useEffect(() => { const startPixels = Array(canvasHeight * canvasWidth).fill("#fff"); @@ -72,7 +73,7 @@ export default function PixelForm() { } try{ - const res = await fetch('http://localhost:3001/patterns', + const res = await fetch(`${API_URL}/patterns`, { method: "POST", headers: {"Content-Type": "application/json"}, diff --git a/pixel2pattern/src/components/PostsCollection.jsx b/pixel2pattern/src/components/PostsCollection.jsx index ca194c9..19f3e28 100644 --- a/pixel2pattern/src/components/PostsCollection.jsx +++ b/pixel2pattern/src/components/PostsCollection.jsx @@ -5,11 +5,12 @@ import Link from "next/link.js"; export default function PostsCollection() { const [ pixelPosts, setPixelPosts] = useState([]); + const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://backend:3001"; useEffect(() => { const fetchPosts = async () => { try { - const res = await fetch("http://localhost:3001/patterns"); + const res = await fetch(`${API_URL}/patterns`); if (!res.ok) throw new Error("failed to fetch posts"); const posts = await res.json(); setPixelPosts(posts); diff --git a/server/controllers/controller.js b/server/controllers/controller.js index 7700707..bb307d3 100644 --- a/server/controllers/controller.js +++ b/server/controllers/controller.js @@ -5,7 +5,7 @@ export const updatePatternController = async(req, res) => { const patternInfo = req.body; const ID = patternInfo.pattern_ID; const pattern = { - pattern_name: patternInfo.patern_name, + pattern_name: patternInfo.pattern_name, pattern_author: patternInfo.author, description: patternInfo.description } diff --git a/server/models/db.js b/server/models/db.js index a4a0d34..db71874 100644 --- a/server/models/db.js +++ b/server/models/db.js @@ -1,4 +1,5 @@ import dotenv from 'dotenv'; +dotenv.config({ path: './db.env' }); import { Sequelize } from 'sequelize'; import path from 'path'; From 55d516e997e52586b5cc300f7e4a90e28b91d96e Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Wed, 29 Oct 2025 01:48:10 -0700 Subject: [PATCH 10/17] fixed frontend by removing the fallback - http://backend:3001 --- pixel2pattern/src/app/view/[id]/page.jsx | 2 +- pixel2pattern/src/components/EditablePatternView.jsx | 2 +- pixel2pattern/src/components/PixelForm.jsx | 2 +- pixel2pattern/src/components/PostsCollection.jsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pixel2pattern/src/app/view/[id]/page.jsx b/pixel2pattern/src/app/view/[id]/page.jsx index b73db6b..d5908e2 100644 --- a/pixel2pattern/src/app/view/[id]/page.jsx +++ b/pixel2pattern/src/app/view/[id]/page.jsx @@ -16,7 +16,7 @@ export default function PatternPage({params}) { const [patternConfig, setPatternConfig] = useState({}); const [editView, setEditView] = useState(false); - const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://backend:3001"; + const API_URL = process.env.NEXT_PUBLIC_API_URL; const onCancel = () => { setEditView(false); diff --git a/pixel2pattern/src/components/EditablePatternView.jsx b/pixel2pattern/src/components/EditablePatternView.jsx index 211a8cd..9ee1e83 100644 --- a/pixel2pattern/src/components/EditablePatternView.jsx +++ b/pixel2pattern/src/components/EditablePatternView.jsx @@ -8,7 +8,7 @@ import PixelDisplay from "@/components/PixelDisplay"; export default function EditablePatternView({ post, onCancel, params}) { const { id } = useParams(); - const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://backend:3001"; + const API_URL = process.env.NEXT_PUBLIC_API_URL; const [formData, setFormData] = useState({ pattern_ID: id, diff --git a/pixel2pattern/src/components/PixelForm.jsx b/pixel2pattern/src/components/PixelForm.jsx index ea3f37a..9bd7dd3 100644 --- a/pixel2pattern/src/components/PixelForm.jsx +++ b/pixel2pattern/src/components/PixelForm.jsx @@ -27,7 +27,7 @@ export default function PixelForm() { const [ pixelFill, setPixelFill] = useState([]); const router = useRouter(); - const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://backend:3001"; + const API_URL = process.env.NEXT_PUBLIC_API_URL; useEffect(() => { const startPixels = Array(canvasHeight * canvasWidth).fill("#fff"); diff --git a/pixel2pattern/src/components/PostsCollection.jsx b/pixel2pattern/src/components/PostsCollection.jsx index 19f3e28..26e07da 100644 --- a/pixel2pattern/src/components/PostsCollection.jsx +++ b/pixel2pattern/src/components/PostsCollection.jsx @@ -5,7 +5,7 @@ import Link from "next/link.js"; export default function PostsCollection() { const [ pixelPosts, setPixelPosts] = useState([]); - const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://backend:3001"; + const API_URL = process.env.NEXT_PUBLIC_API_URL; useEffect(() => { const fetchPosts = async () => { From 69fa685e22c4cfebf9c4da2f23f246173c0085c8 Mon Sep 17 00:00:00 2001 From: Danny McCarragher Date: Wed, 29 Oct 2025 14:49:09 -0700 Subject: [PATCH 11/17] Sequelize Delete functionality using unique PatternID --- server/controllers/controller.js | 16 +++++++--------- server/models/model.js | 11 ++++++----- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/server/controllers/controller.js b/server/controllers/controller.js index fa424d3..11559cd 100644 --- a/server/controllers/controller.js +++ b/server/controllers/controller.js @@ -1,4 +1,4 @@ -import {getAllPatterns, getPattern, postPattern, updatePattern} from '../models/model.js' +import {getAllPatterns, getPattern, postPattern, updatePattern, deletePattern} from '../models/model.js' export const updatePatternController = async(req, res) => { @@ -54,16 +54,14 @@ export const uploadPattern = async (req, res) => { // DELETE Pattern -export const deletePattern = async (req, res) => { - - const pattern = req.body +export const deletePatternById = async (req, res) => { + const patternID = req.params.id; + try{ - const patternID = await deletePattern(pattern); - res.status(201).json(patternID); + await deletePattern(patternID); + res.status(200).json({ message: 'Pattern deleted successfully', pattern_id: patternID }); } catch (err){ - return res.status(500).json({ error : err.message }) + return res.status(500).json({ error : err.message }); } - - } \ No newline at end of file diff --git a/server/models/model.js b/server/models/model.js index b7291b5..67a42da 100644 --- a/server/models/model.js +++ b/server/models/model.js @@ -46,11 +46,12 @@ export const postPattern = async (pattern) => { } } -export const deletePattern = async (pattern) => { - - try{ - const dbPattern = await Patterns.destroy(pattern); - return dbPattern.pattern_ID; +export const deletePattern = async (patternID) => { + try { + await Patterns.destroy({ + where: { pattern_ID: patternID } + }); + return patternID; } catch (err) { console.error("Error deleting pattern:", err); throw new Error('Failed to delete pattern from database'); From d7366008418abee71ccd360ee4680715863f26dd Mon Sep 17 00:00:00 2001 From: Danny McCarragher Date: Wed, 29 Oct 2025 14:49:30 -0700 Subject: [PATCH 12/17] mount delete route --- server/routes/router.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/routes/router.js b/server/routes/router.js index 10d9743..eee4dd5 100644 --- a/server/routes/router.js +++ b/server/routes/router.js @@ -1,5 +1,5 @@ import {Router} from 'express'; -import {getAll, getSpecificPattern, uploadPattern, updatePatternController, deletePattern} from '../controllers/controller.js'; +import {getAll, getSpecificPattern, uploadPattern, updatePatternController, deletePatternById} from '../controllers/controller.js'; const router = Router(); @@ -7,6 +7,6 @@ router.get('/patterns', getAll); router.get('/patterns/:id', getSpecificPattern); router.post('/patterns', uploadPattern); router.patch('/update/:id', updatePatternController); -router.delete('/delete/:id', deletePattern); +router.delete('/delete/:id', deletePatternById); export default router; \ No newline at end of file From 6a80aebb377f694bff747319a8159349696620de Mon Sep 17 00:00:00 2001 From: Danny McCarragher Date: Wed, 29 Oct 2025 14:50:37 -0700 Subject: [PATCH 13/17] fix env path --- server/models/db.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/server/models/db.js b/server/models/db.js index aed1243..ae1dc24 100644 --- a/server/models/db.js +++ b/server/models/db.js @@ -1,18 +1,10 @@ import dotenv from 'dotenv'; +dotenv.config({ path: './db.env' }); import { Sequelize } from 'sequelize'; -import path from 'path'; - - -const envPath = path.resolve(import.meta.dirname, '../../db.env'); -dotenv.config({ - path: envPath -}); - const { DB_USER, DB_PASSWORD, DB_HOST, DB_DATABASE, DB_PORT } = process.env; - // set up sequelize connection to database const sequelize = new Sequelize( DB_DATABASE, DB_USER, DB_PASSWORD, { From 23be8f4096ba05f60a515f9e45de910fa11e5546 Mon Sep 17 00:00:00 2001 From: Danny McCarragher Date: Wed, 29 Oct 2025 14:51:20 -0700 Subject: [PATCH 14/17] delete button component completed --- pixel2pattern/src/app/view/[id]/page.jsx | 3 ++ .../src/components/DeletePattern.jsx | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 pixel2pattern/src/components/DeletePattern.jsx diff --git a/pixel2pattern/src/app/view/[id]/page.jsx b/pixel2pattern/src/app/view/[id]/page.jsx index 1145ffa..529fe91 100644 --- a/pixel2pattern/src/app/view/[id]/page.jsx +++ b/pixel2pattern/src/app/view/[id]/page.jsx @@ -8,6 +8,7 @@ import EditIcon from '@mui/icons-material/Edit'; import PatternGenerator from "@/components/PatternGenerator"; import Button from "@mui/material/Button"; import EditablePatternView from "@/components/EditablePatternView"; +import DeletePattern from "@/components/DeletePattern"; export default function PatternPage({params}) { @@ -68,6 +69,8 @@ export default function PatternPage({params}) { + + {post.description} diff --git a/pixel2pattern/src/components/DeletePattern.jsx b/pixel2pattern/src/components/DeletePattern.jsx new file mode 100644 index 0000000..729c8af --- /dev/null +++ b/pixel2pattern/src/components/DeletePattern.jsx @@ -0,0 +1,28 @@ +import { Button } from "@mui/material"; + +const DeletePattern = ({ id }) => { + const API_URL = process.env.NEXT_PUBLIC_API_URL; + + const handleDelete = async () => { + try { + const res = await fetch(`${API_URL}/delete/${id}`, { + method: 'DELETE', + headers: { "Content-Type": "application/json" } + }) + if (!res.ok) { + throw new Error(`Failed to delete pattern with ID: ${id}`); + } + // redirect to home page after successful deletion + next.router.push('/'); + } catch (err) { + console.error("Error deleting pattern: ", err); + } + } + return ( +
+ +
+ ) +} + +export default DeletePattern; \ No newline at end of file From 78eba68deea9a6bbc8dfd45fb9e7d359f0d8e29f Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Thu, 30 Oct 2025 00:58:11 -0700 Subject: [PATCH 15/17] minor changes on the delete function - changed the router route, and deletePattern file --- .../src/components/DeletePattern.jsx | 35 +++++++++++-------- server/routes/router.js | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/pixel2pattern/src/components/DeletePattern.jsx b/pixel2pattern/src/components/DeletePattern.jsx index 729c8af..e312a35 100644 --- a/pixel2pattern/src/components/DeletePattern.jsx +++ b/pixel2pattern/src/components/DeletePattern.jsx @@ -1,28 +1,33 @@ +"use client"; import { Button } from "@mui/material"; +import { useRouter } from "next/navigation"; const DeletePattern = ({ id }) => { const API_URL = process.env.NEXT_PUBLIC_API_URL; + const router = useRouter(); const handleDelete = async () => { + if (!confirm("Delete this pattern?")) return; + try { - const res = await fetch(`${API_URL}/delete/${id}`, { - method: 'DELETE', - headers: { "Content-Type": "application/json" } - }) - if (!res.ok) { - throw new Error(`Failed to delete pattern with ID: ${id}`); - } - // redirect to home page after successful deletion - next.router.push('/'); + const res = await fetch(`${API_URL}/patterns/${id}`, { + method: "DELETE" + }); + if (!res.ok) throw new Error(); + + alert("Pattern deleted!"); + router.push("/"); } catch (err) { console.error("Error deleting pattern: ", err); + alert("Error deleting pattern"); } - } + }; + return ( -
- -
- ) -} + + ); +}; export default DeletePattern; \ No newline at end of file diff --git a/server/routes/router.js b/server/routes/router.js index eee4dd5..6044d88 100644 --- a/server/routes/router.js +++ b/server/routes/router.js @@ -7,6 +7,6 @@ router.get('/patterns', getAll); router.get('/patterns/:id', getSpecificPattern); router.post('/patterns', uploadPattern); router.patch('/update/:id', updatePatternController); -router.delete('/delete/:id', deletePatternById); +router.delete('/patterns/:id', deletePatternById); export default router; \ No newline at end of file From e801bc0ac7659ca4f27d7138ec09d8035684226a Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Thu, 30 Oct 2025 01:08:15 -0700 Subject: [PATCH 16/17] added export PDF functionality --- pixel2pattern/package-lock.json | 217 +++++++++++++++++++++++ pixel2pattern/package.json | 1 + pixel2pattern/src/app/view/[id]/page.jsx | 49 ++++- 3 files changed, 265 insertions(+), 2 deletions(-) diff --git a/pixel2pattern/package-lock.json b/pixel2pattern/package-lock.json index a386fe8..6f6f6d9 100644 --- a/pixel2pattern/package-lock.json +++ b/pixel2pattern/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.14.1", "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", + "jspdf": "^3.0.3", "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0" @@ -1511,6 +1512,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -1523,6 +1530,13 @@ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "license": "MIT" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/react": { "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", @@ -1542,6 +1556,13 @@ "@types/react": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.44.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz", @@ -2407,6 +2428,16 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2510,6 +2541,26 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2575,6 +2626,18 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "license": "MIT" }, + "node_modules/core-js": { + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz", + "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -2606,6 +2669,16 @@ "node": ">= 8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -2766,6 +2839,16 @@ "csstype": "^3.0.2" } }, + "node_modules/dompurify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3463,6 +3546,17 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -3473,6 +3567,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3834,6 +3934,20 @@ "react-is": "^16.7.0" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3885,6 +3999,12 @@ "node": ">= 0.4" } }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4394,6 +4514,23 @@ "json5": "lib/cli.js" } }, + "node_modules/jspdf": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz", + "integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.9", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.2.4", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -4841,6 +4978,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4906,6 +5049,13 @@ "node": ">=8" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5015,6 +5165,16 @@ ], "license": "MIT" }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -5081,6 +5241,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -5152,6 +5319,16 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5466,6 +5643,16 @@ "dev": true, "license": "MIT" }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -5670,6 +5857,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -5933,6 +6140,16 @@ "punycode": "^2.1.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/pixel2pattern/package.json b/pixel2pattern/package.json index b621395..616329d 100644 --- a/pixel2pattern/package.json +++ b/pixel2pattern/package.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.14.1", "@mui/icons-material": "^7.3.4", "@mui/material": "^7.3.4", + "jspdf": "^3.0.3", "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0" diff --git a/pixel2pattern/src/app/view/[id]/page.jsx b/pixel2pattern/src/app/view/[id]/page.jsx index 2b6baf3..f4bc107 100644 --- a/pixel2pattern/src/app/view/[id]/page.jsx +++ b/pixel2pattern/src/app/view/[id]/page.jsx @@ -9,7 +9,8 @@ import PatternGenerator from "@/components/PatternGenerator"; import Button from "@mui/material/Button"; import EditablePatternView from "@/components/EditablePatternView"; import DeletePattern from "@/components/DeletePattern"; - +import jsPDF from "jspdf"; +import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf"; export default function PatternPage({params}) { const { id } = useParams(); @@ -23,10 +24,42 @@ export default function PatternPage({params}) { setEditView(false); } const clickEditButton = () => { - console.log("Clicked!"); setEditView(true); } + const exportPDF = () => { + if (!post || !patternConfig) return; + + const doc = new jsPDF(); + + doc.setFontSize(18); + doc.text(post.pattern_name, 10, 10); + + doc.setFontSize(12); + doc.text(`Author: ${post.author}`, 10, 20); + doc.text(`Date: ${post.date?.slice(0,10)}`, 10, 28); + doc.text("Description:", 10, 38); + doc.text(post.description || "", 10, 46); + + doc.text("Pattern Stitch Rows:", 10, 60); + let y = 70; + + const rows = patternConfig?.colorConfig || []; + const width = patternConfig?.width || 0; + + for (let i = 0; i < rows.length; i += width) { + const rowColors = rows.slice(i, i + width); + doc.text(`Row ${i / width + 1}: ${rowColors.join(", ")}`, 10, y); + y += 6; + if (y > 270) { + doc.addPage(); + y = 10; + } + } + + doc.save(`${post.pattern_name}.pdf`); + }; + useEffect(()=> { const fetchPost = async () => { try{ @@ -71,6 +104,18 @@ export default function PatternPage({params}) { + {/*Export PDF Button */} + + + {/* Delete Button */} From a8840b9eddf2a138ddec4d2219dc22592872e6c5 Mon Sep 17 00:00:00 2001 From: alstondsouza1 Date: Thu, 30 Oct 2025 09:54:50 -0700 Subject: [PATCH 17/17] updated the script file and the readme file, commenting on the export PDF and delete Pattern file --- README.md | 10 ++++++ deploy.sh | 33 ++++++++++++------- pixel2pattern/src/app/view/[id]/page.jsx | 13 ++++++++ .../src/components/DeletePattern.jsx | 8 +++++ 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9c52135..8805bb0 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,16 @@ Users will soon be able to edit their own patterns directly. ### Delete Remove any pattern you’ve posted with one click. +### Export to PDF +Download a printable crochet pattern PDF including: + +- Pattern title +- Author & date +- Description +- Pixel color rows (stitch instructions) + +Great for crocheting offline + ## ⚙️ Local Setup Follow these steps to run **Pixel to Pattern** locally: diff --git a/deploy.sh b/deploy.sh index c586f53..8bb2a72 100755 --- a/deploy.sh +++ b/deploy.sh @@ -6,16 +6,25 @@ echo "Starting Pixel-to-Pattern deployment..." # System update sudo apt update && sudo apt upgrade -y +# Install Git if missing +if ! command -v git &> /dev/null +then + echo "Installing Git..." + sudo apt install -y git +fi + # Install Docker -if ! [ -x "$(command -v docker)" ]; then - echo "Installing Docker..." - curl -fsSL https://get.docker.com | sh +if ! command -v docker &> /dev/null +then + echo "Installing Docker..." + curl -fsSL https://get.docker.com | sh fi -# Install Docker Compose -if ! [ -x "$(command -v docker compose)" ]; then - echo "🧩 Installing Docker Compose plugin..." - sudo apt install -y docker-compose-plugin +# Install Docker Compose plugin +if ! command -v docker compose &> /dev/null +then + echo "Installing Docker Compose..." + sudo apt install -y docker-compose-plugin fi # Clone repo if not exists @@ -26,12 +35,14 @@ fi cd pixel-to-pattern -# Pull latest changes +echo "Pulling latest updates..." git pull origin main -# Build and run containers -echo "🛠 Building and starting containers..." +echo "Building containers..." docker compose build + +echo "Starting services..." docker compose up -d -echo "Deployment complete! App running at http://:3000" +echo "Deployment complete!" +echo "App running at: http://:3000" diff --git a/pixel2pattern/src/app/view/[id]/page.jsx b/pixel2pattern/src/app/view/[id]/page.jsx index f4bc107..51bec90 100644 --- a/pixel2pattern/src/app/view/[id]/page.jsx +++ b/pixel2pattern/src/app/view/[id]/page.jsx @@ -9,7 +9,10 @@ import PatternGenerator from "@/components/PatternGenerator"; import Button from "@mui/material/Button"; import EditablePatternView from "@/components/EditablePatternView"; import DeletePattern from "@/components/DeletePattern"; + +// importing jsPDF library to generate PDF files in the browser import jsPDF from "jspdf"; +// import PDF icon to display on the Export PDF button import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf"; export default function PatternPage({params}) { @@ -27,28 +30,37 @@ export default function PatternPage({params}) { setEditView(true); } + // export pattern as PDF const exportPDF = () => { + // safety check, making sure data is loaded if (!post || !patternConfig) return; + // creating a new PDF document const doc = new jsPDF(); + // titile of the pattern doc.setFontSize(18); doc.text(post.pattern_name, 10, 10); + // pattern details like (Author, Date, Description) doc.setFontSize(12); doc.text(`Author: ${post.author}`, 10, 20); doc.text(`Date: ${post.date?.slice(0,10)}`, 10, 28); doc.text("Description:", 10, 38); doc.text(post.description || "", 10, 46); + // Stitch rows header doc.text("Pattern Stitch Rows:", 10, 60); let y = 70; const rows = patternConfig?.colorConfig || []; const width = patternConfig?.width || 0; + // looping through color grid rows and printing it as text for (let i = 0; i < rows.length; i += width) { const rowColors = rows.slice(i, i + width); + + // example outpuit: Row 1: white, white, yellow doc.text(`Row ${i / width + 1}: ${rowColors.join(", ")}`, 10, y); y += 6; if (y > 270) { @@ -57,6 +69,7 @@ export default function PatternPage({params}) { } } + // save the PDF with pattern name doc.save(`${post.pattern_name}.pdf`); }; diff --git a/pixel2pattern/src/components/DeletePattern.jsx b/pixel2pattern/src/components/DeletePattern.jsx index e312a35..af5778a 100644 --- a/pixel2pattern/src/components/DeletePattern.jsx +++ b/pixel2pattern/src/components/DeletePattern.jsx @@ -3,19 +3,27 @@ import { Button } from "@mui/material"; import { useRouter } from "next/navigation"; const DeletePattern = ({ id }) => { + + // API base URL fronm environment variables const API_URL = process.env.NEXT_PUBLIC_API_URL; const router = useRouter(); + // handling deletion of pattern const handleDelete = async () => { + + // confirm action before deleting if (!confirm("Delete this pattern?")) return; try { + // send DELETE request to backend const res = await fetch(`${API_URL}/patterns/${id}`, { method: "DELETE" }); if (!res.ok) throw new Error(); alert("Pattern deleted!"); + + // redirect to homepage after deletion router.push("/"); } catch (err) { console.error("Error deleting pattern: ", err);