diff --git a/.github/workflows/app-deployment.yml b/.github/workflows/app-deployment.yml new file mode 100644 index 0000000..9d44420 --- /dev/null +++ b/.github/workflows/app-deployment.yml @@ -0,0 +1,47 @@ +name: App Deployment +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + deploy-app: + runs-on: ubuntu-latest + steps: + - name: Deploy Generator App + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USERNAME }} + key: ${{ secrets.EC2_PEM }} + port: 22 + script: | + echo "STARTING Deployment" + [ -d "deeplink-resolver-server" ] && sudo rm -rf "deeplink-resolver-server" + sudo git clone https://github.com/abhik-wil/deeplink-resolver-server.git + cd deeplink-resolver-server/deeplink-generator + + + echo "STARTING Deployment" + sudo touch .env + # Create backend .env file + echo "DATABASE_URL=${{ secrets.DATABASE_URL }}" | sudo tee .env + echo "POSTGRES_USER=${{ secrets.POSTGRES_USER }}" | sudo tee -a .env + echo "POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}" | sudo tee -a .env + echo "POSTGRES_DB=${{ secrets.POSTGRES_DB }}" | sudo tee -a .env + echo "ACCESS_TOKEN_REPO=${{ secrets.ACCESS_TOKEN_REPO }}" | sudo tee -a .env + echo "OWNER_NAME_REPO=${{ secrets.OWNER_NAME_REPO }}" | sudo tee -a .env + echo "STORAGE_REPO_NAME=${{ secrets.STORAGE_REPO_NAME }}" | sudo tee -a .env + + echo "STARTING build" + sudo cp docker-compose.dev.yml docker-compose.yml + sudo docker compose up ondc_deep_link_app -d --build + sudo docker system prune -f + + + echo "BUILD Complete; Restarting Nginx" + sudo systemctl restart nginx + echo "Deployment COMPLETE" + diff --git a/.github/workflows/db-operations.yml b/.github/workflows/db-operations.yml new file mode 100644 index 0000000..cbe58dd --- /dev/null +++ b/.github/workflows/db-operations.yml @@ -0,0 +1,29 @@ +name: Database Operations +on: + workflow_dispatch: + +jobs: + db-reset-and-migrate: + runs-on: ubuntu-latest + steps: + - name: Execute Generator DB Operations + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USERNAME }} + key: ${{ secrets.EC2_PEM }} + port: 22 + script: | + cd deeplink-resolver-server/deeplink-generator + + echo "DB Teardown and Restart" + sudo docker compose down ondc_deep_link_db + sudo docker compose up -d ondc_deep_link_db + + sleep 10 + + sudo docker build -t migrations-runner -f Dockerfile.migration . + sudo docker run --rm \ + --network deeplink-generator_default \ + migrations-runner + diff --git a/deeplink-generator/.gitignore b/deeplink-generator/.gitignore index d32cc78..6232615 100644 --- a/deeplink-generator/.gitignore +++ b/deeplink-generator/.gitignore @@ -38,3 +38,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts +docker-compose.yml + diff --git a/deeplink-generator/Dockerfile.migration b/deeplink-generator/Dockerfile.migration new file mode 100644 index 0000000..bc99cfe --- /dev/null +++ b/deeplink-generator/Dockerfile.migration @@ -0,0 +1,10 @@ +FROM node:22-alpine + +WORKDIR /app + +COPY package*.json ./ +COPY . . + +RUN npm install + +CMD ["sh", "-c", "npx prisma generate && npx prisma migrate deploy && node seeding/seed.js"] diff --git a/deeplink-generator/docker-compose.dev.yml b/deeplink-generator/docker-compose.dev.yml new file mode 100644 index 0000000..3d01240 --- /dev/null +++ b/deeplink-generator/docker-compose.dev.yml @@ -0,0 +1,25 @@ +services: + ondc_deep_link_app: + build: . + ports: + - "8080:3000" + env_file: + - ./.env + depends_on: + - ondc_deep_link_db + + ondc_deep_link_db: + image: postgres + restart: always + # set shared memory limit when using docker-compose + shm_size: 128mb + # or set shared memory limit when deploy via swarm stack + #volumes: + # - type: tmpfs + # target: /dev/shm + # tmpfs: + # size: 134217728 # 128*2^20 bytes = 128Mb + ports: + - "5432:5432" + env_file: + - ./.env \ No newline at end of file diff --git a/deeplink-generator/docker-compose.yml b/deeplink-generator/docker-compose.local.yml similarity index 100% rename from deeplink-generator/docker-compose.yml rename to deeplink-generator/docker-compose.local.yml diff --git a/deeplink-generator/next.config.ts b/deeplink-generator/next.config.ts index e23d70d..e636b99 100644 --- a/deeplink-generator/next.config.ts +++ b/deeplink-generator/next.config.ts @@ -1,8 +1,13 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ - output: "standalone" + /* config options here */ + output: "standalone", + images: { + remotePatterns: [ + { protocol: "https", hostname: "raw.githubusercontent.com" }, + ], + }, }; export default nextConfig; diff --git a/deeplink-generator/nginx/nginx.conf b/deeplink-generator/nginx/nginx.conf index 0415c5b..41f2441 100644 --- a/deeplink-generator/nginx/nginx.conf +++ b/deeplink-generator/nginx/nginx.conf @@ -4,25 +4,15 @@ http { upstream nextjs_upstream { - server host.docker.internal:8080; + server localhost:8080; } server { listen 80; listen [::]:80; - server_name _; + server_name deeplink.resolver.ondc.org; - location / { - proxy_pass http://nextjs_upstream; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } + return 301 https://$server_name$request_uri; } server { @@ -30,9 +20,8 @@ http { listen [::]:443 ssl; server_name _; - ssl_certificate /etc/nginx/ssl/cert.pem; - ssl_certificate_key /etc/nginx/ssl/key.pem; - + ssl_certificate /etc/letsencrypt/live/deeplink.resolver.ondc.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/deeplink.resolver.ondc.org/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; @@ -49,3 +38,7 @@ http { } } } + +add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; +add_header X-Frame-Options DENY; +add_header X-Content-Type-Options nosniff; diff --git a/deeplink-generator/package-lock.json b/deeplink-generator/package-lock.json index 266499b..f0db430 100644 --- a/deeplink-generator/package-lock.json +++ b/deeplink-generator/package-lock.json @@ -13,10 +13,12 @@ "@mui/base": "^5.0.0-beta.64", "@mui/icons-material": "^6.1.10", "@mui/material": "^6.1.9", + "@octokit/rest": "^21.0.2", "@prisma/client": "^6.1.0", "jspdf": "^2.5.2", "next": "15.0.3", "pdf-lib": "^1.17.1", + "qrcode": "^1.5.4", "react": "19.0.0", "react-dom": "19.0.0", "react-json-view-lite": "^2.0.1", @@ -25,11 +27,12 @@ }, "devDependencies": { "@types/node": "^20.17.9", + "@types/qrcode": "^1.5.5", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", "eslint-config-next": "15.0.3", - "prisma": "^6.1.0", + "prisma": "^6.0.1", "ts-node": "^10.9.2", "typescript": "^5.7.2" } @@ -1303,6 +1306,148 @@ "node": ">=12.4.0" } }, + "node_modules/@octokit/auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", + "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.3.tgz", + "integrity": "sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow==", + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.1.2", + "@octokit/request": "^9.1.4", + "@octokit/request-error": "^6.1.6", + "@octokit/types": "^13.6.2", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.2.tgz", + "integrity": "sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==", + "dependencies": { + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.2.tgz", + "integrity": "sha512-bdlj/CJVjpaz06NBpfHhp4kGJaRZfz7AzC+6EwUImRtrwIw8dIgJ63Xg0OzV9pRn3rIzrt5c2sa++BL0JJ8GLw==", + "dependencies": { + "@octokit/request": "^9.1.4", + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.6.tgz", + "integrity": "sha512-zcvqqf/+TicbTCa/Z+3w4eBJcAxCFymtc0UAIsR3dEVoNilWld4oXdscQ3laXamTszUZdusw97K8+DrbFiOwjw==", + "dependencies": { + "@octokit/types": "^13.6.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-5.3.1.tgz", + "integrity": "sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.6", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.6.tgz", + "integrity": "sha512-wMsdyHMjSfKjGINkdGKki06VEkgdEldIGstIEyGX0wbYHGByOwN/KiM+hAAlUwAtPkP3gvXtVQA9L3ITdV2tVw==", + "dependencies": { + "@octokit/types": "^13.6.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/request": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.4.tgz", + "integrity": "sha512-tMbOwGm6wDII6vygP3wUVqFTw3Aoo0FnVQyhihh8vVq12uO3P+vQZeo2CKMpWtPSogpACD0yyZAlVlQnjW71DA==", + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.6.2", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.6.tgz", + "integrity": "sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==", + "dependencies": { + "@octokit/types": "^13.6.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-21.0.2.tgz", + "integrity": "sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==", + "dependencies": { + "@octokit/core": "^6.1.2", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/plugin-request-log": "^5.3.1", + "@octokit/plugin-rest-endpoint-methods": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.2.tgz", + "integrity": "sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, "node_modules/@pdf-lib/standard-fonts": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", @@ -1470,6 +1615,15 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, + "node_modules/@types/qrcode": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz", + "integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/raf": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", @@ -1834,7 +1988,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true, "engines": { "node": ">=8" } @@ -1843,7 +1996,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2126,6 +2278,11 @@ "node": ">= 0.6.0" } }, + "node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2198,6 +2355,14 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001684", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", @@ -2289,6 +2454,16 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2314,7 +2489,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2325,8 +2499,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-string": { "version": "1.9.1", @@ -2499,6 +2672,14 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decompress-response": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", @@ -2583,6 +2764,11 @@ "node": ">=0.3.1" } }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -3266,6 +3452,21 @@ "node": ">=0.10.0" } }, + "node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3501,6 +3702,14 @@ "node": ">=10" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -3980,7 +4189,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "optional": true, "engines": { "node": ">=8" } @@ -4867,6 +5075,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -4904,7 +5120,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -5001,6 +5216,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -5085,6 +5308,22 @@ "node": ">=6" } }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/qrcode-generator": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz", @@ -5269,6 +5508,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -5436,8 +5688,7 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "optional": true + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/set-function-length": { "version": "1.2.2", @@ -5642,7 +5893,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "optional": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5655,8 +5905,7 @@ "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "optional": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/string.prototype.includes": { "version": "2.0.1", @@ -5761,7 +6010,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -6119,6 +6367,11 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, + "node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6250,6 +6503,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, "node_modules/which-typed-array": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", @@ -6287,12 +6545,30 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=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==", "devOptional": true }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -6307,6 +6583,87 @@ "node": ">= 6" } }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/deeplink-generator/package.json b/deeplink-generator/package.json index 070231e..06d3c72 100644 --- a/deeplink-generator/package.json +++ b/deeplink-generator/package.json @@ -22,10 +22,12 @@ "@mui/base": "^5.0.0-beta.64", "@mui/icons-material": "^6.1.10", "@mui/material": "^6.1.9", + "@octokit/rest": "^21.0.2", "@prisma/client": "^6.1.0", "jspdf": "^2.5.2", "next": "15.0.3", "pdf-lib": "^1.17.1", + "qrcode": "^1.5.4", "react": "19.0.0", "react-dom": "19.0.0", "react-json-view-lite": "^2.0.1", @@ -34,11 +36,12 @@ }, "devDependencies": { "@types/node": "^20.17.9", + "@types/qrcode": "^1.5.5", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", "eslint-config-next": "15.0.3", - "prisma": "^6.1.0", + "prisma": "^6.0.1", "ts-node": "^10.9.2", "typescript": "^5.7.2" } diff --git a/deeplink-generator/prisma/migrations/20250106065259_category_sub/migration.sql b/deeplink-generator/prisma/migrations/20250106065259_category_sub/migration.sql new file mode 100644 index 0000000..c7e0ca0 --- /dev/null +++ b/deeplink-generator/prisma/migrations/20250106065259_category_sub/migration.sql @@ -0,0 +1,17 @@ +/* + Warnings: + + - Added the required column `usecaseCategoryId` to the `UsecaseSubcategory` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Template" ALTER COLUMN "id" SET DEFAULT substr(gen_random_uuid()::text, 1, 16); + +-- AlterTable +ALTER TABLE "Usecase" ALTER COLUMN "id" SET DEFAULT substr(gen_random_uuid()::text, 1, 16); + +-- AlterTable +ALTER TABLE "UsecaseSubcategory" ADD COLUMN "usecaseCategoryId" TEXT NOT NULL; + +-- AddForeignKey +ALTER TABLE "UsecaseSubcategory" ADD CONSTRAINT "UsecaseSubcategory_usecaseCategoryId_fkey" FOREIGN KEY ("usecaseCategoryId") REFERENCES "UsecaseCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/deeplink-generator/prisma/migrations/20250106084158_dev/migration.sql b/deeplink-generator/prisma/migrations/20250106084158_dev/migration.sql new file mode 100644 index 0000000..d92f613 --- /dev/null +++ b/deeplink-generator/prisma/migrations/20250106084158_dev/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Template" ALTER COLUMN "id" SET DEFAULT substr(gen_random_uuid()::text, 1, 16); + +-- AlterTable +ALTER TABLE "Usecase" ALTER COLUMN "id" SET DEFAULT substr(gen_random_uuid()::text, 1, 16); diff --git a/deeplink-generator/prisma/migrations/migration_lock.toml b/deeplink-generator/prisma/migrations/migration_lock.toml index fbffa92..648c57f 100644 --- a/deeplink-generator/prisma/migrations/migration_lock.toml +++ b/deeplink-generator/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) +# It should be added in your version-control system (e.g., Git) provider = "postgresql" \ No newline at end of file diff --git a/deeplink-generator/prisma/schema.prisma b/deeplink-generator/prisma/schema.prisma index 0aea5e4..3228c54 100644 --- a/deeplink-generator/prisma/schema.prisma +++ b/deeplink-generator/prisma/schema.prisma @@ -5,7 +5,7 @@ // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" binaryTargets = ["native", "linux-musl", "windows"] } @@ -61,13 +61,16 @@ model Usecase { } model UsecaseCategory { - id String @id @default(uuid()) - name String @unique - Template Template[] + id String @id @default(uuid()) + name String @unique + Template Template[] + UsecaseSubcategory UsecaseSubcategory[] } model UsecaseSubcategory { - id String @id @default(uuid()) - name String @unique - Template Template[] + id String @id @default(uuid()) + name String @unique + category UsecaseCategory @relation(fields: [usecaseCategoryId], references: [id]) + Template Template[] + usecaseCategoryId String } diff --git a/deeplink-generator/seeding/seed.js b/deeplink-generator/seeding/seed.js index 6a3c345..e9303ad 100644 --- a/deeplink-generator/seeding/seed.js +++ b/deeplink-generator/seeding/seed.js @@ -4,42 +4,6 @@ import path from "path"; const prisma = new PrismaClient(); -const CATEGORIES = [ - { - name: "Retail", - }, - { - name: "Logistics", - }, - { - name: "Services", - }, - { - name: "Subscription", - }, - { - name: "IGM", - }, -]; - -const SUB_CATEGORIES = [ - { - name: "Search By Item Name", - }, - { - name: "Search By Item Code", - }, - { - name: "Seller-Catalog Search", - }, - { - name: "Search By City", - }, - { - name: "Search By Category", - }, -]; - async function readJsonFiles() { const currentDir = path.join(process.cwd(), "seeding"); console.log("CWD", currentDir); @@ -57,21 +21,60 @@ async function readJsonFiles() { } async function main() { - const c = await prisma.usecaseCategory.createMany({ - data: CATEGORIES, + const filesData = await readJsonFiles(); + // console.log(filesData) + const categorySubcategoryMap = {}; + filesData.forEach(async (file) => { + const { category, subCategory } = file; + if (categorySubcategoryMap[category]) { + categorySubcategoryMap[category].push(subCategory); + } else { + categorySubcategoryMap[category] = [subCategory]; + } }); - const sc =await prisma.usecaseSubcategory.createMany({ - data: SUB_CATEGORIES, + console.log(categorySubcategoryMap); + Object.keys(categorySubcategoryMap).forEach(async (category) => { + const c = await prisma.usecaseCategory.create({ + data: { + name: category, + }, + }); + const sc = await prisma.usecaseSubcategory.createMany({ + data: categorySubcategoryMap[category].map((subCategory) => ({ + name: subCategory, + usecaseCategoryId: c.id, + })), + }); + console.log("Seeded Subcategory", sc); }); - console.log("Seeded Categories ", c); - console.log("Seeded Sub Categories ", sc); + const seededCategories = await prisma.usecaseCategory.findMany(); const seededSubCategories = await prisma.usecaseSubcategory.findMany(); - const filesData = await readJsonFiles(); - // console.log(filesData) + + console.log("Seeded Categories", seededCategories); + console.log("Seeded SubCategories", seededSubCategories); + const processedData = filesData.map((file) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const {category, subCategory, ...remainingFile} = file + const { category, subCategory, ...remainingFile } = file; + console.log( + "FILTERED category", + file.category.toLowerCase(), + seededCategories.filter( + (category) => + category.name.toLowerCase() === file.category.toLowerCase() + )[0].id + ); + + console.log( + "FILTERED sub-category", + file.subCategory.toLowerCase(), + seededCategories.filter( + (category) => + category.name.toLowerCase() === file.category.toLowerCase() + )[0].id + ); + return { ...remainingFile, usecaseCategoryId: seededCategories.filter( diff --git a/deeplink-generator/src/app/(deep-link)/deep-link/filter/[categoryId]/page.tsx b/deeplink-generator/src/app/(deep-link)/deep-link/filter/[categoryId]/page.tsx index 9fb4479..d7403e7 100644 --- a/deeplink-generator/src/app/(deep-link)/deep-link/filter/[categoryId]/page.tsx +++ b/deeplink-generator/src/app/(deep-link)/deep-link/filter/[categoryId]/page.tsx @@ -1,7 +1,7 @@ import { getUsecaseCategories, getUsecaseSubcategories } from "@/app/actions"; import { CustomHeading, CustomOutlinedButton } from "@/app/components"; // import { formatToNormalCasing } from "@/app/utils"; -import { Grid2 as Grid, Paper } from "@mui/material"; +import { Grid2 as Grid, Paper, Typography } from "@mui/material"; import { UsecaseCategory } from "@prisma/client"; import React from "react"; @@ -9,7 +9,7 @@ export const revalidate = 3600; export const dynamicParams = true; // or false, to 404 on unknown paths export async function generateStaticParams() { - const posts: UsecaseCategory[] = await getUsecaseCategories() + const posts: UsecaseCategory[] = await getUsecaseCategories(); return posts.map((post) => ({ id: String(post.id), })); @@ -21,7 +21,7 @@ const SelectUsecaseSubcategory = async ({ params: Promise<{ categoryId: string }>; }) => { const categoryId = (await params).categoryId; - const subcategories = await getUsecaseSubcategories(); + const subcategories = await getUsecaseSubcategories(categoryId); return ( <> @@ -38,17 +38,21 @@ const SelectUsecaseSubcategory = async ({ width: "100%", }} > - - {subcategories.map((subcategory, index) => ( - - - - ))} - + {subcategories.length === 0 ? ( + No subcategories found for this category + ) : ( + + {subcategories.map((subcategory, index) => ( + + + + ))} + + )} ); diff --git a/deeplink-generator/src/app/(deep-link)/deep-link/usecases/create/[templateId]/page.tsx b/deeplink-generator/src/app/(deep-link)/deep-link/usecases/create/[templateId]/page.tsx index bcc87c9..7392a0f 100644 --- a/deeplink-generator/src/app/(deep-link)/deep-link/usecases/create/[templateId]/page.tsx +++ b/deeplink-generator/src/app/(deep-link)/deep-link/usecases/create/[templateId]/page.tsx @@ -14,6 +14,7 @@ import Form from "next/form"; import { FillerTypeObject, flattenTemplate, + formDataToFormItemArray, inputTypeMapper, NamedEnum, } from "@/app/utils"; @@ -39,7 +40,8 @@ const GenerateDeepLinkPage = async ({ throw new Error("Form data is required"); } console.log("CREATING DEEP LINK"); - const deepLink = await createDeepLink(templateId, form); + const value = formDataToFormItemArray(form) + const deepLink = await createDeepLink({templateId, value}); console.log("Redirecting"); redirect(`/deep-link/usecases/publish/${deepLink.id}`); }; diff --git a/deeplink-generator/src/app/(deep-link)/deep-link/usecases/publish/[deepLinkId]/page.tsx b/deeplink-generator/src/app/(deep-link)/deep-link/usecases/publish/[deepLinkId]/page.tsx index 5c5ca95..90cebd7 100644 --- a/deeplink-generator/src/app/(deep-link)/deep-link/usecases/publish/[deepLinkId]/page.tsx +++ b/deeplink-generator/src/app/(deep-link)/deep-link/usecases/publish/[deepLinkId]/page.tsx @@ -1,10 +1,15 @@ -import { getUsecaseById, publishUsecase } from "@/app/actions"; +import { + getUsecaseById, + publishUsecase, + PublishUsecaseFormType, +} from "@/app/actions"; import { CustomContainedButtom, CustomHeading, FieldName, UsecaseEditor, } from "@/app/components"; +import { formDataToEntry } from "@/app/utils"; import { Box, Divider, @@ -14,8 +19,7 @@ import { Select, TextField, Typography, - Stack - + Stack, } from "@mui/material"; import { UsecaseStage } from "@prisma/client"; import Form from "next/form"; @@ -29,12 +33,10 @@ const PublishDeepLinkPage = async ({ }) => { const deepLinkId = (await params).deepLinkId; const usecase = await getUsecaseById(deepLinkId); - const handleFormSubmit = async (form: FormData) => { + const handleFormSubmit = async (formData: FormData) => { "use server"; - if (!form) { - throw new Error("Form data is required"); - } - const publishedUsecase = await publishUsecase(usecase!, form); + const form = formDataToEntry(formData); + const publishedUsecase = await publishUsecase({ usecase: usecase!, form }); redirect(`/deep-link/usecases/thank-you/${publishedUsecase.id}`); }; return ( @@ -64,9 +66,9 @@ const PublishDeepLinkPage = async ({ alignItems="center" justifyContent="flex-start" > - +     - + - +     - + - +     - + @@ -115,11 +117,10 @@ const PublishDeepLinkPage = async ({ - + {/* {JSON.stringify(usecase?.value)} */} - ; }) => { const deepLinkId = (await params).deepLinkId; + const usecase = await getUsecaseById(deepLinkId); + if (!usecase) { + redirect("/"); + } return ( - + Thank You for using ONDC, here's your deep link! - + + QR + + + - + Back To Home - + diff --git a/deeplink-generator/src/app/actions/create-deep-link.ts b/deeplink-generator/src/app/actions/create-deep-link.ts index 2e760a6..9a92a8a 100644 --- a/deeplink-generator/src/app/actions/create-deep-link.ts +++ b/deeplink-generator/src/app/actions/create-deep-link.ts @@ -4,20 +4,17 @@ import { UsecaseStage } from "@prisma/client"; import { db } from "../../../db"; import { FormItem, inflateDeepLink } from "../utils"; -export async function createDeepLink(templateId: string, form: FormData) { - if (!form) { - throw new Error("Form data is required"); - } - console.log("Generating Deep Link...", form); - const inflatedValue = inflateDeepLink( - Array.from(form.entries()).map( - ([key, value]) => - ({ - name: key, - value: value, - } as FormItem) - ) - ); +export type CreateDeepLinkType = { + templateId: string; + value: FormItem[]; +}; + +export async function createDeepLink({ + templateId, + value, +}: CreateDeepLinkType) { + console.log("Generating Deep Link...", templateId, value); + const inflatedValue = inflateDeepLink(value); console.log("VALUE INFLATED", inflatedValue); const deepLink = await db.usecase.create({ data: { diff --git a/deeplink-generator/src/app/actions/get-usecase-subcategories.ts b/deeplink-generator/src/app/actions/get-usecase-subcategories.ts index 718e535..3838ad5 100644 --- a/deeplink-generator/src/app/actions/get-usecase-subcategories.ts +++ b/deeplink-generator/src/app/actions/get-usecase-subcategories.ts @@ -1,6 +1,10 @@ "use server"; import { db } from "../../../db"; -export async function getUsecaseSubcategories() { - return db.usecaseSubcategory.findMany(); +export async function getUsecaseSubcategories(usecaseCategoryId: string) { + return db.usecaseSubcategory.findMany({ + where: { + usecaseCategoryId, + } + }); } diff --git a/deeplink-generator/src/app/actions/publish-usecase.ts b/deeplink-generator/src/app/actions/publish-usecase.ts index f387c1c..b9eafc6 100644 --- a/deeplink-generator/src/app/actions/publish-usecase.ts +++ b/deeplink-generator/src/app/actions/publish-usecase.ts @@ -2,16 +2,92 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Usecase, UsecaseStage } from "@prisma/client"; import { db } from "../../../db"; +import { Octokit } from "@octokit/rest"; +import QRCode from "qrcode"; -export async function publishUsecase(usecase: Usecase, form: FormData) { +export type PublishUsecaseType = { + usecase: Usecase; + form: PublishUsecaseFormType; +}; +export type PublishUsecaseFormType = { + submissionOption: UsecaseStage; + name: string; + creatorName: string; + description: string; +}; + +const FOLDER_PATH = "usecases"; + +export async function publishUsecase({ usecase, form }: PublishUsecaseType) { const updatedUsecase = await db.usecase.update({ where: { id: usecase.id, }, data: { - usecaseStage: form.get("submissionOption") as UsecaseStage, - name: form.get("name") as string, + usecaseStage: form.submissionOption, + name: form.name, + creatorName: form.creatorName, + description: form.description, }, }); + console.log("ENVs", process.env.OWNER_NAME_REPO, process.env.STORAGE_REPO_NAME, process.env.ACCESS_TOKEN_REPO); + const octokit = new Octokit({ + auth: process.env.ACCESS_TOKEN_REPO, + }); + + if (form.submissionOption === UsecaseStage.PUBLISHED) { + const fileName = `${updatedUsecase.id}.json`; + const filePath = `${FOLDER_PATH}/json/${fileName}`; + const content = Buffer.from( + JSON.stringify(updatedUsecase, null, 2) + ).toString("base64"); + + try { + await octokit.repos.getContent({ + owner: process.env.OWNER_NAME_REPO || "", + repo: process.env.STORAGE_REPO_NAME || "", + path: FOLDER_PATH, + }); + } catch (error: any) { + if (error.status === 404) { + // Create the folder by creating a dummy file and then deleting it + await octokit.repos.createOrUpdateFileContents({ + owner: process.env.OWNER_NAME_REPO || "", + repo: process.env.STORAGE_REPO_NAME || "", + path: `${FOLDER_PATH}/.gitkeep`, + message: "Create hello folder", + content: Buffer.from("").toString("base64"), + }); + } + } + + // Create the new JSON file + await octokit.repos.createOrUpdateFileContents({ + owner: process.env.OWNER_NAME_REPO || "", + repo: process.env.STORAGE_REPO_NAME || "", + path: filePath, + message: `Add user submission ${updatedUsecase.id}`, + content, + }); + } + + const fileName = `${updatedUsecase.id}.png`; + const filePath = `${FOLDER_PATH}/qr/${fileName}`; + + const qrCodeBase64 = await QRCode.toDataURL( + JSON.stringify(updatedUsecase.value, null, 2) + ); + + // Convert base64 to buffer (remove data:image/png;base64, prefix) + const qrCodeBuffer = Buffer.from(qrCodeBase64.split(",")[1], "base64"); + + await octokit.repos.createOrUpdateFileContents({ + owner: process.env.OWNER_NAME_REPO || "", + repo: process.env.STORAGE_REPO_NAME || "", + path: filePath, + message: `Update QR code for ${updatedUsecase.id}`, + content: qrCodeBuffer.toString("base64"), + }); + return updatedUsecase; } diff --git a/deeplink-generator/src/app/resolver/[deepLinkId]/route.ts b/deeplink-generator/src/app/api/resolver/[deepLinkId]/route.ts similarity index 63% rename from deeplink-generator/src/app/resolver/[deepLinkId]/route.ts rename to deeplink-generator/src/app/api/resolver/[deepLinkId]/route.ts index 3788bca..c7baeb6 100644 --- a/deeplink-generator/src/app/resolver/[deepLinkId]/route.ts +++ b/deeplink-generator/src/app/api/resolver/[deepLinkId]/route.ts @@ -6,6 +6,7 @@ export async function GET( { params }: { params: Promise<{ deepLinkId: string }> } ) { const deepLinkId = (await params).deepLinkId; - const deepLink = await getUsecaseById(deepLinkId); + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + const {template ,...deepLink} = (await getUsecaseById(deepLinkId)) as any; return NextResponse.json(deepLink); } diff --git a/deeplink-generator/src/app/usecase/[usecaseId]/route.ts b/deeplink-generator/src/app/api/usecase/[usecaseId]/route.ts similarity index 100% rename from deeplink-generator/src/app/usecase/[usecaseId]/route.ts rename to deeplink-generator/src/app/api/usecase/[usecaseId]/route.ts diff --git a/deeplink-generator/src/app/api/usecase/pdf/[usecaseId]/route.ts b/deeplink-generator/src/app/api/usecase/pdf/[usecaseId]/route.ts new file mode 100644 index 0000000..877103a --- /dev/null +++ b/deeplink-generator/src/app/api/usecase/pdf/[usecaseId]/route.ts @@ -0,0 +1,66 @@ +import { PDFDocument } from "pdf-lib"; +import QRCode from "qrcode"; +import fs from "fs"; +import path from "path"; +import { NextRequest, NextResponse } from "next/server"; +import { getUsecaseById } from "@/app/actions"; + +export async function GET( + req: NextRequest, + { params }: { params: Promise<{ usecaseId: string }> } +) { + try { + const usecaseId = (await params).usecaseId; + const usecase = await getUsecaseById(usecaseId); + if (!usecase) + return NextResponse.json({ error: "Usecase not found" }, { status: 404 }); + const { value } = usecase; + const qrCodeBase64 = await QRCode.toDataURL(JSON.stringify(value, null, 2)); + + // Remove data:image/png;base64 prefix + const qrCodeImageBytes = Buffer.from(qrCodeBase64.split(",")[1], "base64"); + + // Load template PDF + const templatePath = path.join(process.cwd(), "public", "Poster.pdf"); + const templateBytes = fs.readFileSync(templatePath); + + // Load the template PDF + const pdfDoc = await PDFDocument.load(templateBytes); + + // Embed the QR code image + const qrCodeImage = await pdfDoc.embedPng(qrCodeImageBytes); + + // Get the first page + const page = pdfDoc.getPages()[0]; + + // Calculate center position + const { width, height } = page.getSize(); + const qrSize = 150; + const x = (width - qrSize) / 2 + 20; + const y = (height - qrSize) / 2 + 140; + + // Draw QR code + page.drawImage(qrCodeImage, { + x, + y, + width: qrSize, + height: qrSize, + }); + + // Save the PDF + const pdfBytes = await pdfDoc.save(); + + return new NextResponse(pdfBytes, { + headers: { + "Content-Type": "application/pdf", + "Content-Disposition": `attachment; filename=${usecase.name || "Deep Link"}.pdf`, + }, + }); + } catch (error) { + console.error("Error:", error); + return NextResponse.json( + { error: "Error generating PDF" }, + { status: 500 } + ); + } +} diff --git a/deeplink-generator/src/app/components/CustomOutlinedButton.tsx b/deeplink-generator/src/app/components/CustomOutlinedButton.tsx index 371494e..24db3dd 100644 --- a/deeplink-generator/src/app/components/CustomOutlinedButton.tsx +++ b/deeplink-generator/src/app/components/CustomOutlinedButton.tsx @@ -23,7 +23,7 @@ export const CustomOutlinedButton = ({ bgcolor: "transparent", width: "100%", "&:hover": { - borderWidth: 3.5, + transform: 'scale(1.04)', bgcolor: colors.grey[100], borderColor: "primary.dark", color: colors.grey[900], diff --git a/deeplink-generator/src/app/components/DownloadQr.tsx b/deeplink-generator/src/app/components/DownloadQr.tsx index 42febca..0d49fea 100644 --- a/deeplink-generator/src/app/components/DownloadQr.tsx +++ b/deeplink-generator/src/app/components/DownloadQr.tsx @@ -1,15 +1,35 @@ "use client"; import { DownloadTwoTone } from "@mui/icons-material"; import { Box, IconButton, Typography } from "@mui/material"; -import React, { useState } from "react"; -import QrDialog from "./QrDialog"; +import React from "react"; type DownloadQrProps = { - link?: string; + usecaseId: string; + name: string }; -export const DownloadQr = ({ link }: DownloadQrProps) => { - const [openQrDialog, setOpenQrDialog] = useState(false); +export const DownloadQr = ({ usecaseId, name}: DownloadQrProps) => { + const handleDownloadPDF = async () => { + try { + const response = await fetch(`/api/usecase/pdf/${usecaseId}`); + if (response.ok) { + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${name}.pdf`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + a.remove(); + } else { + alert('Error generating PDF'); + } + } catch (error) { + console.error('Error:', error); + alert('Error generating PDF'); + } + }; return ( { bgcolor: "primary.light", "&:hover": { bgcolor: "primary.dark" }, }} - onClick={() => setOpenQrDialog(true)} + onClick={handleDownloadPDF} > - setOpenQrDialog(false)} - providerName="Deep Link QR" - /> ); }; diff --git a/deeplink-generator/src/app/components/SocialsList.tsx b/deeplink-generator/src/app/components/SocialsList.tsx index 8daa922..bd68915 100644 --- a/deeplink-generator/src/app/components/SocialsList.tsx +++ b/deeplink-generator/src/app/components/SocialsList.tsx @@ -1,4 +1,4 @@ -import { Box, Grid, Typography, IconButton } from "@mui/material"; +import { Box, Grid2 as Grid, Typography, IconButton } from "@mui/material"; import React from "react"; import EmailIcon from "@mui/icons-material/Email"; import WhatsAppIcon from "@mui/icons-material/WhatsApp"; @@ -18,36 +18,42 @@ export const SocialsList = () => { flexDirection: "column", }} > - + Share directly on Socials - + - + - + - + - + - + @@ -55,4 +61,4 @@ export const SocialsList = () => { ); -}; \ No newline at end of file +}; diff --git a/deeplink-generator/src/app/components/UsecaseEditor.tsx b/deeplink-generator/src/app/components/UsecaseEditor.tsx index 3f6da9e..e64062f 100644 --- a/deeplink-generator/src/app/components/UsecaseEditor.tsx +++ b/deeplink-generator/src/app/components/UsecaseEditor.tsx @@ -104,7 +104,7 @@ export const UsecaseEditor = ({ usecase }: UsecaseEditorProps) => { .map((key: string) => ( - + {typeof flattenedTemplate[key] === "string" ? ( diff --git a/deeplink-generator/src/app/utils/formDataToEntry.ts b/deeplink-generator/src/app/utils/formDataToEntry.ts new file mode 100644 index 0000000..964afbb --- /dev/null +++ b/deeplink-generator/src/app/utils/formDataToEntry.ts @@ -0,0 +1,25 @@ +import { FormItem } from "./inflate"; + +export function formDataToEntry(form: FormData): T { + const entries = Array.from(form.entries()); + const result = entries.reduce((acc, [key, value]) => { + return { + ...acc, + [key]: value, + }; + }, {} as T); + + return result; +} + +export function formDataToFormItemArray(form: FormData): FormItem[] { + const result = Array.from(form.entries()).map( + ([key, value]) => + ({ + name: key, + value: value, + } as FormItem) + ); + + return result; +} diff --git a/deeplink-generator/src/app/utils/index.ts b/deeplink-generator/src/app/utils/index.ts index a933079..fc6e3a3 100644 --- a/deeplink-generator/src/app/utils/index.ts +++ b/deeplink-generator/src/app/utils/index.ts @@ -2,4 +2,5 @@ export * from "./flatten"; export * from "./inflate"; export * from "./formatToNormalCasing"; export * from "./inputTypeMapper"; -export * from "./getTextPlacement" \ No newline at end of file +export * from "./getTextPlacement" +export * from "./formDataToEntry" \ No newline at end of file