From adbbb555f972300b9f6fe5a03fe56d78c1a716e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl?= Date: Fri, 5 Apr 2024 17:47:08 +0300 Subject: [PATCH 01/19] bug oujas --- .env | 4 +++- .env.development | 14 ++++++++++---- components/availability/AvailabilityForm.js | 2 ++ components/calendar/avcalendar.tsx | 4 ++-- package-lock.json | 4 ++-- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.env b/.env index d0883ef..c8d1e6a 100644 --- a/.env +++ b/.env @@ -9,9 +9,11 @@ NEXTAUTH_SECRET=ed8a9681efc414df89dfd03cd188ed58 # mysql DATABASE_PROVIDER=mysql # DATABASE_URL=mysql://cart:cart2023@192.168.0.10:3306/cart_dev -DATABASE_URL=mysql://root:Zelen0ku4e@192.168.0.10:3306/cart_dev +# DATABASE_URL=mysql://root:Zelen0ku4e@192.168.0.10:3306/cart_dev # DATABASE_URL=mysql://cart:cartpw@20.101.62.76:3307/cart +DATABASE_URL="mysql://root:mdp-11000@127.0.0.1:3306/cart" + # DATABASE_URL=mysql://cart:cartpw@localhost:3306/cart # npx prisma migrate dev # // owner: dobromir.popov@gmail.com | Специално Свидетелстване София diff --git a/.env.development b/.env.development index 21fcc42..b2e953b 100644 --- a/.env.development +++ b/.env.development @@ -1,12 +1,18 @@ NODE_TLS_REJECT_UNAUTHORIZED=0 # NODE_EXTRA_CA_CERTS=C:\\Users\\popov\\AppData\\Local\\mkcert -PROTOCOL=https +PROTOCOL=http PORT=3003 HOST=localhost -NEXT_PUBLIC_PUBLIC_URL=https://localhost:3003 +NEXT_PUBLIC_PUBLIC_URL=http://localhost:3003 SSL_KEY=./certificates/localhost-key.pem SSL_CERT=./certificates/localhost.pem -DATABASE_URL=mysql://root:Zelen0ku4e@192.168.0.10:3306/cart_dev -# DATABASE_URL=mysql://cart:cartpw@localhost:3306/cart \ No newline at end of file +# DATABASE_URL=mysql://root:Zelen0ku4e@192.168.0.10:3306/cart_dev + + +DATABASE_URL="mysql://root:mdp-11000@127.0.0.1:3306/cart?connection_limit=5&charset=utf8mb4&collation=utf8mb4_unicode_ci" + + + +# DATABASE_URL=mysql://cart:cartpw@localhost:3306/cart \ No newline at end of file diff --git a/components/availability/AvailabilityForm.js b/components/availability/AvailabilityForm.js index a24f2af..9ac31ce 100644 --- a/components/availability/AvailabilityForm.js +++ b/components/availability/AvailabilityForm.js @@ -23,6 +23,8 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o indexUrl: "/cart/availabilities" }; + existingitems = existingitems || []; + const [editMode, setEditMode] = useState(existingItems.length > 0); const [publisher, setPublisher] = useState({ id: publisherId }); const [day, setDay] = useState(new Date(date)); diff --git a/components/calendar/avcalendar.tsx b/components/calendar/avcalendar.tsx index 5403f1f..a83d257 100644 --- a/components/calendar/avcalendar.tsx +++ b/components/calendar/avcalendar.tsx @@ -57,13 +57,13 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => { // Update internal state when `events` prop changes useEffect(() => { //if we have isBySystem - set type to assignment - let updatedEvents = events.map(event => { + let updatedEvents = events?.map(event => { if (event.isBySystem) { event.type = "assignment"; } return event; }); - updatedEvents = events.map(event => ({ + updatedEvents = events?.map(event => ({ ...event, date: new Date(event.startTime).setHours(0, 0, 0, 0), startTime: new Date(event.startTime), diff --git a/package-lock.json b/package-lock.json index f9dac5e..ad8e28f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pwwa", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pwwa", - "version": "1.1.1", + "version": "1.1.2", "dependencies": { "@auth/prisma-adapter": "^1.4.0", "@emotion/react": "^11.11.3", From 508db35ee0fce6184b4cfb54783dda60192061b3 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 5 Apr 2024 17:57:20 +0300 Subject: [PATCH 02/19] try to fix env loading on windows --- .env.development | 4 +--- server.js | 10 +++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.env.development b/.env.development index 21fcc42..b3a4d31 100644 --- a/.env.development +++ b/.env.development @@ -4,9 +4,7 @@ PROTOCOL=https PORT=3003 HOST=localhost NEXT_PUBLIC_PUBLIC_URL=https://localhost:3003 +DATABASE_URL=mysql://root:Zelen0ku4e@192.168.0.10:3306/cart_dev SSL_KEY=./certificates/localhost-key.pem SSL_CERT=./certificates/localhost.pem - -DATABASE_URL=mysql://root:Zelen0ku4e@192.168.0.10:3306/cart_dev -# DATABASE_URL=mysql://cart:cartpw@localhost:3306/cart \ No newline at end of file diff --git a/server.js b/server.js index 8e3982c..cb52bca 100644 --- a/server.js +++ b/server.js @@ -29,9 +29,12 @@ let baseUrlGlobal; // } console.log("initial process.env.NODE_ENV = ", process.env.NODE_ENV); //NODE_ENV can be passed as docker param -require('dotenv').config({ - path: `.env.${process.env.NODE_ENV}` -}); +// require('dotenv').config({ +// path: `.env.${process.env.NODE_ENV}` +// }); +require('dotenv').config(); // Fallback to default +require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` }); // Environment-specific + console.log("process.env.NODE_ENV = ", process.env.NODE_ENV); @@ -48,6 +51,7 @@ console.log("process.env.NEXT_PUBLIC_PUBLIC_URL = ", process.env.NEXT_PUBLIC_PUB console.log("process.env.NEXTAUTH_URL = ", process.env.NEXTAUTH_URL); console.log("process.env.PORT = ", process.env.PORT); console.log("process.env.TELEGRAM_BOT = ", process.env.TELEGRAM_BOT); +console.log("process.env.DATABASE_URL = ", process.env.DATABASE_URL); //require('module-alias/register'); From 3209d273e28598be49dcc46dadf7b0de7f28a71d Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 5 Apr 2024 18:04:46 +0300 Subject: [PATCH 03/19] env.dev.raph created --- .env.development.raph | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .env.development.raph diff --git a/.env.development.raph b/.env.development.raph new file mode 100644 index 0000000..65172a3 --- /dev/null +++ b/.env.development.raph @@ -0,0 +1,10 @@ +NODE_TLS_REJECT_UNAUTHORIZED=0 +# NODE_EXTRA_CA_CERTS=C:\\Users\\popov\\AppData\\Local\\mkcert +PROTOCOL=http +PORT=3003 +HOST=localhost +NEXT_PUBLIC_PUBLIC_URL=http://localhost:3003 +DATABASE_URL="mysql://root:mdp-11000@127.0.0.1:3306/cart?connection_limit=5&charset=utf8mb4&collation=utf8mb4_unicode_ci" + +SSL_KEY=./certificates/localhost-key.pem +SSL_CERT=./certificates/localhost.pem From 8becbfc9267bdd5332ec6f85e678d7cf52e371cf Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 5 Apr 2024 19:58:07 +0300 Subject: [PATCH 04/19] env and database access overhaul; new dev scripts --- .env | 9 +-------- .env.development | 5 +++-- .env.development.raph | 2 +- .env.production | 2 +- .env.test | 2 +- .vscode/launch.json | 14 ++++++++++++-- _deploy/demo.10.yml | 2 +- _deploy/demo.11-demo.yml | 2 +- _deploy/deoloy.azure.demo.yml | 2 +- _deploy/deoloy.azure.production.yml | 4 ++-- _deploy/homelab.deploy.production.yml | 2 +- _deploy/sample.docker-compose.yml | 2 +- _doc/notes.mb | 10 ++++++++++ package.json | 6 +++--- prisma/administrative_scripts/create_user.sql | 2 ++ prisma/schema.prisma | 4 ++-- server.js | 18 +++--------------- src/helpers/common.js | 6 +++--- 18 files changed, 49 insertions(+), 45 deletions(-) create mode 100644 prisma/administrative_scripts/create_user.sql diff --git a/.env b/.env index c8d1e6a..24dc32d 100644 --- a/.env +++ b/.env @@ -6,15 +6,8 @@ # Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32 NEXTAUTH_SECRET=ed8a9681efc414df89dfd03cd188ed58 +NODE_ENV=development # mysql -DATABASE_PROVIDER=mysql -# DATABASE_URL=mysql://cart:cart2023@192.168.0.10:3306/cart_dev -# DATABASE_URL=mysql://root:Zelen0ku4e@192.168.0.10:3306/cart_dev -# DATABASE_URL=mysql://cart:cartpw@20.101.62.76:3307/cart - -DATABASE_URL="mysql://root:mdp-11000@127.0.0.1:3306/cart" - -# DATABASE_URL=mysql://cart:cartpw@localhost:3306/cart # npx prisma migrate dev # // owner: dobromir.popov@gmail.com | Специално Свидетелстване София # // https://console.cloud.google.com/apis/credentials/oauthclient/926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com?project=grand-forge-108716 diff --git a/.env.development b/.env.development index 7ae61ee..943fde9 100644 --- a/.env.development +++ b/.env.development @@ -1,10 +1,11 @@ NODE_TLS_REJECT_UNAUTHORIZED=0 # NODE_EXTRA_CA_CERTS=C:\\Users\\popov\\AppData\\Local\\mkcert -PROTOCOL=http +PROTOCOL=https PORT=3003 HOST=localhost NEXT_PUBLIC_PUBLIC_URL=https://localhost:3003 -DATABASE_URL=mysql://root:Zelen0ku4e@192.168.0.10:3306/cart_dev +DATABASE=mysql://cart:cartpw@192.168.0.10:3306/cart_dev +#DATABASE=mysql://cart:cartpw@localhost:3306/cart SSL_KEY=./certificates/localhost-key.pem SSL_CERT=./certificates/localhost.pem diff --git a/.env.development.raph b/.env.development.raph index 65172a3..52d1a21 100644 --- a/.env.development.raph +++ b/.env.development.raph @@ -4,7 +4,7 @@ PROTOCOL=http PORT=3003 HOST=localhost NEXT_PUBLIC_PUBLIC_URL=http://localhost:3003 -DATABASE_URL="mysql://root:mdp-11000@127.0.0.1:3306/cart?connection_limit=5&charset=utf8mb4&collation=utf8mb4_unicode_ci" +DATABASE="mysql://root:mdp-11000@127.0.0.1:3306/cart?connection_limit=5&charset=utf8mb4&collation=utf8mb4_unicode_ci" SSL_KEY=./certificates/localhost-key.pem SSL_CERT=./certificates/localhost.pem diff --git a/.env.production b/.env.production index 5853ac7..15d66db 100644 --- a/.env.production +++ b/.env.production @@ -6,4 +6,4 @@ NEXT_PUBLIC_PUBLIC_URL= https://sofia.mwitnessing.com # Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32 NEXTAUTH_SECRET=1dd8a5457970d1dda50600be28e935ecc4513ff27c49c431849e6746f158d638 # ? do we need to duplicate this? already defined in the deoployment yml file -DATABASE_URL=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia \ No newline at end of file +DATABASE=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia \ No newline at end of file diff --git a/.env.test b/.env.test index 2e62731..4d21b0b 100644 --- a/.env.test +++ b/.env.test @@ -6,7 +6,7 @@ NEXT_PUBLIC_PUBLIC_URL=https://staging.mwitnessing.com # Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32 NEXTAUTH_SECRET=1dd8a5457970d1dda50600be28e935ecc4513ff27c49c431849e6746f158d638 # ? do we need to duplicate this? already defined in the deoployment yml file -DATABASE_URL=mysql://jwpwsofia_demo:dwxhns9p9vp248@mariadb:3306/jwpwsofia_demo +DATABASE=mysql://jwpwsofia_demo:dwxhns9p9vp248@mariadb:3306/jwpwsofia_demo APPLE_ID= APPLE_TEAM_ID= diff --git a/.vscode/launch.json b/.vscode/launch.json index 963bb15..a2cc8ff 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,13 +5,23 @@ "version": "0.2.0", "configurations": [ { - "name": "Run npm nodemon (DEV)", + "name": "Run npm nodemon (DB)", "command": "npm run debug", "request": "launch", "type": "node-terminal", "preLaunchTask": "killInspector", "env": { - "NODE_ENV": "development" + "APP_ENV": "development" + } + }, + { + "name": "Run npm nodemon (Raph)", + "command": "npm run debug", + "request": "launch", + "type": "node-terminal", + "preLaunchTask": "killInspector", + "env": { + "APP_ENV": "development.raph" } }, { diff --git a/_deploy/demo.10.yml b/_deploy/demo.10.yml index 307260e..06a3a49 100644 --- a/_deploy/demo.10.yml +++ b/_deploy/demo.10.yml @@ -10,7 +10,7 @@ services: - /mnt/apps/docker_volumes/cart/app/next-cart-app:/app environment: - NODE_ENV=demo - - DATABASE_URL=mysql://cart:cart2023@192.168.0.10:3306/cart + - DATABASE=mysql://cart:cart2023@192.168.0.10:3306/cart #command: sh -c "apk update && apk add git && rm -rf /tmp/clone && git clone https://git.d-popov.com/popov/next-cart-app.git /tmp/clone && rm -rf /app/* && cp -R /tmp/clone/next-cart-app/* /app/ && rm -rf /tmp/clone && npm cache clean --force && rm -rf /app/node_modules /app/package-lock.json && npm --silent --prefix /app install /app && npx --prefix /app prisma generate && npm --prefix /app run test; tail -f /dev/null" #command: sh -c "rm -rf /tmp/clone && git clone https://git.d-popov.com/popov/next-cart-app.git /tmp/clone && rm -rf /app/* && cp -R /tmp/clone/next-cart-app/* /app/ && rm -rf /tmp/clone && npm cache clean --force && rm -rf /app/node_modules /app/package-lock.json && npm --silent --prefix /app install /app && npx --prefix /app prisma generate && npm --prefix /app run test; tail -f /dev/null" command: sh -c "npm cache clean --force && rm -rf /app/node_modules /app/package-lock.json && npm --silent --prefix /app install /app && npx --prefix /app prisma generate && npm --prefix /app run test; tail -f /dev/null" diff --git a/_deploy/demo.11-demo.yml b/_deploy/demo.11-demo.yml index 07a4a7f..3a3a4d9 100644 --- a/_deploy/demo.11-demo.yml +++ b/_deploy/demo.11-demo.yml @@ -8,7 +8,7 @@ services: - /mnt/apps/DEV/cart-demo:/app environment: - NODE_ENV=demo - - DATABASE_URL=mysql://cart:cart2023@192.168.0.10:3306/cart + - DATABASE=mysql://cart:cart2023@192.168.0.10:3306/cart command: sh -c " cd /app && npm run test; tail -f /dev/null" tty: true stdin_open: true diff --git a/_deploy/deoloy.azure.demo.yml b/_deploy/deoloy.azure.demo.yml index d81bbbb..5e868fa 100644 --- a/_deploy/deoloy.azure.demo.yml +++ b/_deploy/deoloy.azure.demo.yml @@ -8,7 +8,7 @@ services: environment: - NODE_ENV=demo - TZ=Europe/Sofia - - DATABASE_URL=mysql://jwpwsofia_demo:dwxhns9p9vp248@jwpwsofia:3306/jwpwsofia_demo + - DATABASE=mysql://jwpwsofia_demo:dwxhns9p9vp248@jwpwsofia:3306/jwpwsofia_demo - UPDATE_CODE_FROM_GIT=true # Set to true to pull latest code from Git - GIT_BRANCH=main - GIT_USERNAME=deploy diff --git a/_deploy/deoloy.azure.production.yml b/_deploy/deoloy.azure.production.yml index d653294..7f8bcdb 100644 --- a/_deploy/deoloy.azure.production.yml +++ b/_deploy/deoloy.azure.production.yml @@ -12,8 +12,8 @@ services: environment: - NODE_ENV=production - TZ=Europe/Sofia - - DATABASE_URL=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia - #- DATABASE_URL=postgres://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia + - DATABASE=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia + #- DATABASE=postgres://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia - UPDATE_CODE_FROM_GIT=true # Set to true to pull latest code from Git - GIT_BRANCH=production - GIT_USERNAME=deploy diff --git a/_deploy/homelab.deploy.production.yml b/_deploy/homelab.deploy.production.yml index 402af4d..3c1826a 100644 --- a/_deploy/homelab.deploy.production.yml +++ b/_deploy/homelab.deploy.production.yml @@ -6,7 +6,7 @@ services: - "5001:3000" environment: - NODE_ENV=prod - - DATABASE_URL=mysql://cart:o74x642Rc8@mariadb:3306/cart + - DATABASE=mysql://cart:o74x642Rc8@mariadb:3306/cart - UPDATE_CODE_FROM_GIT=true # Set to true to pull latest code from Git - GIT_USERNAME=deploy - GIT_PASSWORD=%L3Kr2R438u4F7^%40 diff --git a/_deploy/sample.docker-compose.yml b/_deploy/sample.docker-compose.yml index 06a9ae4..32347a9 100644 --- a/_deploy/sample.docker-compose.yml +++ b/_deploy/sample.docker-compose.yml @@ -40,7 +40,7 @@ services: - /mnt/data/apps/docker_volumes/cart/app:/app environment: - NODE_ENV=demo - - DATABASE_URL=mysql://cart:cartpw2024@mariadb:3306/cart + - DATABASE=mysql://cart:cartpw2024@mariadb:3306/cart #! entrypoint: ["/bin/sh", "/entrypoint.sh"] #run: npm install && npx prisma generate && npm run test; # command: "npx prisma migrate deploy && npx prisma migrate deploy && npm run build && npm run start" diff --git a/_doc/notes.mb b/_doc/notes.mb index 7b8ee37..a94440c 100644 --- a/_doc/notes.mb +++ b/_doc/notes.mb @@ -111,6 +111,11 @@ export OPENAI_API_KEY=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 # dev- # ----------------------------------------------update PRISMA schema/sync database ----------------------------------------------- # # prisma migrate dev --create-only +NODE_ENV=production npx prisma migrate deploy +#windows +$env:DATABASE="mysql://cart:cartpw@localhost:3306/cart"; npx prisma migrate deploy +$env:DATABASE="mysql://cart:cartpw@192.168.0.10:3306/cart_dev"; npx prisma migrate deploy + npx prisma generate npx prisma migrate dev --name fix_nextauth_schema --create-only >Prisma Migrate created the following migration without applying it 20231214163235_fix_nextauth_schema @@ -196,3 +201,8 @@ ncu -u enable apple ID: curl https://gist.githubusercontent.com/balazsorban44/09613175e7b37ec03f676dcefb7be5eb/raw/b0d31aa0c7f58e0088fdf59ec30cad1415a3475b/apple-gen-secret.mjs -o apple-gen-secret.mjs + + + + +Project setup: diff --git a/package.json b/package.json index e8738af..2d0ae8e 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ }, "homepage": "https://git.d-popov.com/popov/next-cart-app", "scripts": { - "debug": "nodemon --inspect server.js", - "debug-env-dev": "dotenv -e .env.development -- nodemon --inspect server.js", + "debug": "node server.js", + "debug-env": "dotenv -e .env.$APP_ENV -- nodemon --inspect server.js", "build": "next build", "buildWin": "npm run build", "start": "next start", @@ -109,4 +109,4 @@ "depcheck": "^1.4.7", "prisma": "^5.11.0" } -} +} \ No newline at end of file diff --git a/prisma/administrative_scripts/create_user.sql b/prisma/administrative_scripts/create_user.sql new file mode 100644 index 0000000..b521ce0 --- /dev/null +++ b/prisma/administrative_scripts/create_user.sql @@ -0,0 +1,2 @@ +CREATE USER 'cart'@'%' IDENTIFIED BY 'cartpw'; +GRANT ALL PRIVILEGES ON `cart\_dev`.* TO 'cart'@'%' WITH GRANT OPTION; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1faea21..aa7ecdd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,7 +10,6 @@ // //to generate schema // > npx prisma -// GPT // This is a Prisma database schema definition, which describes the structure and relationships between various entities in the database. Here's a brief overview of the different models: // Publisher: Represents a publisher, with attributes such as first name, last name, email, phone, age, and availability. A publisher can have many availabilities and assignments, and can also have multiple user accounts and sessions. @@ -21,9 +20,10 @@ // Location: Represents a location where a cart event can take place. A location can have a name, address, and multiple cart events. // Overall, this schema seems to represent a system for managing publishers and their assignments to cart events, including their availabilities and locations. +//$env:DATABASE="{connection string}"; npx prisma migrate deploy datasource db { provider = "mysql" - url = env("DATABASE_URL") + url = env("DATABASE") } generator client { diff --git a/server.js b/server.js index cb52bca..a2d5ad9 100644 --- a/server.js +++ b/server.js @@ -20,22 +20,9 @@ process.env.TZ = 'Europe/Sofia'; // Global variable to store the base URL let baseUrlGlobal; -// if (process.env.NODE_ENV === 'test') { -// // Load environment variables from .env.test -// require('dotenv').config({ path: '.env.test' }); -// } else { -// // Load default environment variables -// require('dotenv').config(); -// } - +console.log("initial process.env.APP_ENV = ", process.env.APP_ENV); console.log("initial process.env.NODE_ENV = ", process.env.NODE_ENV); //NODE_ENV can be passed as docker param -// require('dotenv').config({ -// path: `.env.${process.env.NODE_ENV}` -// }); -require('dotenv').config(); // Fallback to default -require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` }); // Environment-specific - - +require('dotenv').config({ path: `.env.${process.env.APP_ENV}` }); console.log("process.env.NODE_ENV = ", process.env.NODE_ENV); const PROTOCOL = process.env.PROTOCOL; @@ -52,6 +39,7 @@ console.log("process.env.NEXTAUTH_URL = ", process.env.NEXTAUTH_URL); console.log("process.env.PORT = ", process.env.PORT); console.log("process.env.TELEGRAM_BOT = ", process.env.TELEGRAM_BOT); console.log("process.env.DATABASE_URL = ", process.env.DATABASE_URL); +console.log("process.env.DATABASE = ", process.env.DATABASE); //require('module-alias/register'); diff --git a/src/helpers/common.js b/src/helpers/common.js index fdac13e..210119e 100644 --- a/src/helpers/common.js +++ b/src/helpers/common.js @@ -83,14 +83,14 @@ exports.getBaseUrl = function (relative = "", req = null) { let prisma; exports.getPrismaClient = function getPrismaClient() { if (!prisma) { - logger.debug("getPrismaClient: process.env.DATABASE_URL = ", process.env.DATABASE_URL); + logger.debug("getPrismaClient: process.env.DATABASE = ", process.env.DATABASE); prisma = new PrismaClient({ // Optional: Enable logging //log: ['query', 'info', 'warn', 'error'], - datasources: { db: { url: process.env.DATABASE_URL } }, + datasources: { db: { url: process.env.DATABASE } }, }); } - logger.debug("getPrismaClient: process.env.DATABASE_URL = ", process.env.DATABASE_URL); + logger.debug("getPrismaClient: process.env.DATABASE = ", process.env.DATABASE); return prisma; } From 077359203e1f5d2c65a76f4921dfae0052f7bcb2 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 5 Apr 2024 20:14:08 +0300 Subject: [PATCH 05/19] fix client compile error --- _deploy/prod.Dockerfile | 2 ++ components/availability/AvailabilityForm.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/_deploy/prod.Dockerfile b/_deploy/prod.Dockerfile index f894520..14d7092 100644 --- a/_deploy/prod.Dockerfile +++ b/_deploy/prod.Dockerfile @@ -4,6 +4,8 @@ FROM node:current-alpine # Set environment variables for Node.js ENV NODE_ENV=production +# ENV MYSQL_ROOT_PASSWORD=pass +ENV MYSQL_DATABASE=cart # Create and set the working directory WORKDIR /app diff --git a/components/availability/AvailabilityForm.js b/components/availability/AvailabilityForm.js index 7a40f01..f8f52c6 100644 --- a/components/availability/AvailabilityForm.js +++ b/components/availability/AvailabilityForm.js @@ -25,7 +25,8 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o indexUrl: "/cart/availabilities" }; - existingitems = existingitems || []; + //coalsce existingItems to empty array + existingItems = existingItems || []; const [editMode, setEditMode] = useState(existingItems.length > 0); const [publisher, setPublisher] = useState({ id: publisherId }); From dea97cf7e51def9ea760794d507303a6b18abe75 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 5 Apr 2024 20:20:23 +0300 Subject: [PATCH 06/19] using let if existingItems is strangely undefined --- components/availability/AvailabilityForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/availability/AvailabilityForm.js b/components/availability/AvailabilityForm.js index f8f52c6..ed8905f 100644 --- a/components/availability/AvailabilityForm.js +++ b/components/availability/AvailabilityForm.js @@ -26,7 +26,7 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o }; //coalsce existingItems to empty array - existingItems = existingItems || []; + let existingItems = existingItems || []; const [editMode, setEditMode] = useState(existingItems.length > 0); const [publisher, setPublisher] = useState({ id: publisherId }); From 10939019e87daa20fd919ced7f854a5bd1b4045b Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 5 Apr 2024 20:28:39 +0300 Subject: [PATCH 07/19] set test env --- .env.test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.env.test b/.env.test index 4d21b0b..b99f907 100644 --- a/.env.test +++ b/.env.test @@ -1,3 +1,5 @@ +NODE_ENV=test + PROTOCOL=http HOST=staging.mwitnessing.com PORT= From 5e1415046dce1c23e01955e3f610ea0fca4eb8c5 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 5 Apr 2024 20:31:56 +0300 Subject: [PATCH 08/19] revert --- components/availability/AvailabilityForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/availability/AvailabilityForm.js b/components/availability/AvailabilityForm.js index ed8905f..f8f52c6 100644 --- a/components/availability/AvailabilityForm.js +++ b/components/availability/AvailabilityForm.js @@ -26,7 +26,7 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o }; //coalsce existingItems to empty array - let existingItems = existingItems || []; + existingItems = existingItems || []; const [editMode, setEditMode] = useState(existingItems.length > 0); const [publisher, setPublisher] = useState({ id: publisherId }); From 61645091bd32eba2ecab584c7a86ea75f05ee762 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 5 Apr 2024 20:49:31 +0300 Subject: [PATCH 09/19] email subscription UI in publisher settings --- .env.development | 2 +- _doc/ToDo.md | 6 ++ components/publisher/PublisherForm.js | 121 ++++++++++++++------------ 3 files changed, 73 insertions(+), 56 deletions(-) diff --git a/.env.development b/.env.development index 943fde9..0bf4e92 100644 --- a/.env.development +++ b/.env.development @@ -5,7 +5,7 @@ PORT=3003 HOST=localhost NEXT_PUBLIC_PUBLIC_URL=https://localhost:3003 DATABASE=mysql://cart:cartpw@192.168.0.10:3306/cart_dev -#DATABASE=mysql://cart:cartpw@localhost:3306/cart +DATABASE=mysql://cart:cartpw@localhost:3306/cart SSL_KEY=./certificates/localhost-key.pem SSL_CERT=./certificates/localhost.pem diff --git a/_doc/ToDo.md b/_doc/ToDo.md index bd32c11..b8801e8 100644 --- a/_doc/ToDo.md +++ b/_doc/ToDo.md @@ -196,3 +196,9 @@ fix Time ZONE (currently Z, but it leads to shift when the DST changes ( winter fix repeating availabilities - Tanq kolcjanova only blue first thursday add assignment in calendar planner fix database + +-- +emails +mobile apps +apple login +разрешителни - upload diff --git a/components/publisher/PublisherForm.js b/components/publisher/PublisherForm.js index e3e6f16..99bde59 100644 --- a/components/publisher/PublisherForm.js +++ b/components/publisher/PublisherForm.js @@ -185,10 +185,13 @@ export default function PublisherForm({ item, me }) { -
- - + +
+
+ + +
@@ -232,60 +235,68 @@ export default function PublisherForm({ item, me }) { -
- - -
-
- - -
-
- - +
+
+ + +
+
+
+ + + +
- -
- - -
-
-
- - - - - - + + {/* ADMINISTRATORS ONLY */} + +
+
+ +
+
+ + +
+
+ + +
+
+
+ + + + + + +
+
+
+ + +
+ + Телеграм + Телеграм +
-
- - -
- - Телеграм - Телеграм -
{/* ---------------------------- Actions --------------------------------- */}
@@ -312,7 +323,7 @@ export default function PublisherForm({ item, me }) {
-
+ ) From bac4f4c7d55b06f20511645deb7f475cae70fd2d Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 5 Apr 2024 21:47:18 +0300 Subject: [PATCH 10/19] email API and first templates --- .env.development | 2 +- pages/api/email.ts | 162 +++++++++++++++++++++++++++++++ src/templates/emails/coverMe.hbs | 0 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 pages/api/email.ts create mode 100644 src/templates/emails/coverMe.hbs diff --git a/.env.development b/.env.development index 0bf4e92..40806ac 100644 --- a/.env.development +++ b/.env.development @@ -4,7 +4,7 @@ PROTOCOL=https PORT=3003 HOST=localhost NEXT_PUBLIC_PUBLIC_URL=https://localhost:3003 -DATABASE=mysql://cart:cartpw@192.168.0.10:3306/cart_dev +# DATABASE=mysql://cart:cartpw@192.168.0.10:3306/cart_dev DATABASE=mysql://cart:cartpw@localhost:3306/cart SSL_KEY=./certificates/localhost-key.pem diff --git a/pages/api/email.ts b/pages/api/email.ts new file mode 100644 index 0000000..7aae223 --- /dev/null +++ b/pages/api/email.ts @@ -0,0 +1,162 @@ +// API endpoint to process email user actions - urls we send in emails to users + +import { getToken } from "next-auth/jwt"; +import type { NextApiRequest, NextApiResponse } from 'next'; +import { createRouter, expressWrapper } from "next-connect"; +const common = require('../../src/helpers/common'); +const email = require('../../src/helpers/'); + +const handlebars = require("handlebars"); + +const router = createRouter(); + + +//action to accept coverme request from email + + +/** + * + * @param req import { NextApiRequest, NextApiResponse } from 'next' + * @param res import { NextApiRequest, NextApiResponse } from 'next' + */ +export default async function handler(req, res) { + const prisma = common.getPrismaClient(); + + var action = req.query.action; + // Retrieve and validate the JWT token + const token = await getToken({ req: req }); + + //response is a special action that does not require a token + if (action !== "email_response") { + if (!token) { + // If no token or invalid token, return unauthorized status + return res.status(401).json({ message: "Unauthorized to call this API endpoint" }); + } + } + + var userId = req.query.userId; + var email = req.query.email; + let date = new Date(); + + if (!userId && !email) { + return res.status(400).json({ message: "User ID or email is not provided" }); + } + // Retrieve the user + const user = await prisma.publisher.findUnique({ + where: { + id: userId, + email: email + } + }); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + switch (action) { + case "send_coverme_request": + // Send CoverMe request to the user + //get from POST data: shiftId, assignmentId, date + let shiftId = req.body.shiftId; + let assignmentId = req.body.assignmentId; + let date = req.body.date; + + console.log("User: " + user.email + " sent a CoverMe request: " + + shiftId + " " + assignmentId + " " + date); + + //get all subscribed publisers + const subscribedPublishers = await prisma.publisher.findMany({ + where: { + isSubscribedToCoverMe: true + } + }); + //send email to all subscribed publishers + for (let i = 0; i < subscribedPublishers.length; i++) { + //send email to subscribed publisher + //send email to subscribed publisher + const emailResponse = await common.sendEmail(subscribedPublishers[i].email, "CoverMe Request", + "User: " + user.email + " sent a CoverMe request: " + + shiftId + " " + assignmentId + " " + date); + } + + break; + case "coverme_accept": + // Update the user status to accepted + console.log("User: " + user.firstName + " " + user.lastName + " accepted the CoverMe request"); + //validate shiftId and assignmentId + let shiftId = req.query.shiftId; + let assignmentId = req.query.assignmentId; + if (!shiftId || !assignmentId) { + return res.status(400).json({ message: "Shift ID or Assignment ID is not provided" }); + } + + //get the assignment + const assignment = await prisma.assignment.findUnique({ + where: { + id: parseInt(assignmentId) + } + }); + if (!assignment) { + return res.status(404).json({ message: "Assignment not found" }); + } + if (assignment.shiftId != parseInt(shiftId)) { + return res.status(400).json({ message: "Shift ID does not match" }); + } + + + + + // await prisma.user.update({ + // where: { + // id: parseInt(userId) + // }, + // data: { + // status: "accepted", + // acceptedAt: date + // } + // }); + break; + + //POST + case "send_report": //we can send report form in the emails to the user. process the POSTED data here + // Send report form to the user + //get from POST data: locationId, date, placementCount, videoCount, returnVisitInfoCount, conversationCount + let locationId = req.body.locationId; + let date = req.body.date; + let placementCount = req.body.placementCount; + let videoCount = req.body.videoCount; + let returnVisitInfoCount = req.body.returnVisitInfoCount; + let conversationCount = req.body.conversationCount; + + console.log("User: " + user.email + " sent a report: " + + locationId + " " + date + " " + + placementCount + " " + videoCount + " " + + returnVisitInfoCount + " " + conversationCount); + + //save the report in the database + await prisma.report.create({ + data: { + userId: parseInt(userId), + locationId: parseInt(locationId), + date: date, + placementCount: parseInt(placementCount), + videoCount: parseInt(videoCount), + returnVisitInfoCount: parseInt(returnVisitInfoCount), + conversationCount: parseInt(conversationCount) + } + }); + + break; + + default: + return res.status(400).json({ message: "Invalid action" }); + } + return res.status(200).json({ message: "User action processed" }); +} + +router.use(expressWrapper(handler)); + + + + + + diff --git a/src/templates/emails/coverMe.hbs b/src/templates/emails/coverMe.hbs new file mode 100644 index 0000000..e69de29 From fa5d3f4f990d03bb4bbb2060ab434c7cb44b4762 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 5 Apr 2024 23:44:27 +0300 Subject: [PATCH 11/19] email template fo rreplacements --- pages/api/email.ts | 183 +++++++++++++++++++------------ src/helpers/email.js | 47 ++++++++ src/templates/emails/coverMe.hbs | 24 ++++ src/templates/emails/main.hbs | 25 +++++ 4 files changed, 207 insertions(+), 72 deletions(-) create mode 100644 src/templates/emails/main.hbs diff --git a/pages/api/email.ts b/pages/api/email.ts index 7aae223..e77134e 100644 --- a/pages/api/email.ts +++ b/pages/api/email.ts @@ -4,8 +4,10 @@ import { getToken } from "next-auth/jwt"; import type { NextApiRequest, NextApiResponse } from 'next'; import { createRouter, expressWrapper } from "next-connect"; const common = require('../../src/helpers/common'); -const email = require('../../src/helpers/'); +const emailHelper = require('../../src/helpers/email'); +import fs from 'fs'; +import path from 'path'; const handlebars = require("handlebars"); const router = createRouter(); @@ -51,7 +53,7 @@ export default async function handler(req, res) { if (!user) { return res.status(404).json({ message: "User not found" }); } - + var emailaction = req.query.emailaction; switch (action) { case "send_coverme_request": // Send CoverMe request to the user @@ -72,84 +74,121 @@ export default async function handler(req, res) { //send email to all subscribed publishers for (let i = 0; i < subscribedPublishers.length; i++) { //send email to subscribed publisher - //send email to subscribed publisher - const emailResponse = await common.sendEmail(subscribedPublishers[i].email, "CoverMe Request", - "User: " + user.email + " sent a CoverMe request: " + - shiftId + " " + assignmentId + " " + date); - } - - break; - case "coverme_accept": - // Update the user status to accepted - console.log("User: " + user.firstName + " " + user.lastName + " accepted the CoverMe request"); - //validate shiftId and assignmentId - let shiftId = req.query.shiftId; - let assignmentId = req.query.assignmentId; - if (!shiftId || !assignmentId) { - return res.status(400).json({ message: "Shift ID or Assignment ID is not provided" }); - } - - //get the assignment - const assignment = await prisma.assignment.findUnique({ - where: { - id: parseInt(assignmentId) + let shift = await prisma.shift.findUnique({ + where: { + id: parseInt(shiftId) + }, + include: { + cartEvent: { + include: { + location: true + } + }, + } } - }); - if (!assignment) { - return res.status(404).json({ message: "Assignment not found" }); - } - if (assignment.shiftId != parseInt(shiftId)) { - return res.status(400).json({ message: "Shift ID does not match" }); + ); + + let acceptUrl = process.env.NEXTAUTH_URL + "/api/emailActions?action=coverme_accept&userId=" + user.id + "&shiftId=" + shiftId + "&assignmentId=" + assignmentId; + + let model = { + user: user, + shiftId: shiftId, + assignmentId: assignmentId, + acceptUrl: acceptUrl, + prefix: subscribedPublishers[i].isMale ? "Брат" : "Сестра", + firstName: subscribedPublishers[i].firstName, + lastName: subscribedPublishers[i].lastName, + placeName: shift.cartEvent.location.name, + dateStr: date.toLocaleDateString(), + sentDate: date.toLocaleTimeString() + }; + emailHelper.SendEmailHandlebars(subscribedPublishers[i].email, "coverMe", model); } - - - - // await prisma.user.update({ - // where: { - // id: parseInt(userId) - // }, - // data: { - // status: "accepted", - // acceptedAt: date - // } - // }); break; - - //POST - case "send_report": //we can send report form in the emails to the user. process the POSTED data here - // Send report form to the user - //get from POST data: locationId, date, placementCount, videoCount, returnVisitInfoCount, conversationCount - let locationId = req.body.locationId; - let date = req.body.date; - let placementCount = req.body.placementCount; - let videoCount = req.body.videoCount; - let returnVisitInfoCount = req.body.returnVisitInfoCount; - let conversationCount = req.body.conversationCount; - - console.log("User: " + user.email + " sent a report: " + - locationId + " " + date + " " + - placementCount + " " + videoCount + " " + - returnVisitInfoCount + " " + conversationCount); - - //save the report in the database - await prisma.report.create({ - data: { - userId: parseInt(userId), - locationId: parseInt(locationId), - date: date, - placementCount: parseInt(placementCount), - videoCount: parseInt(videoCount), - returnVisitInfoCount: parseInt(returnVisitInfoCount), - conversationCount: parseInt(conversationCount) - } - }); - + case "email_response": + //get email action + if (!emailaction) { + return res.status(400).json({ message: "Email action is not provided" }); + } break; - default: return res.status(400).json({ message: "Invalid action" }); } + + + + if (action !== "email_response") { + switch (emailaction) { + case "coverme_accept": + // Update the user status to accepted + console.log("User: " + user.firstName + " " + user.lastName + " accepted the CoverMe request"); + //validate shiftId and assignmentId + let shiftId = req.query.shiftId; + let assignmentId = req.query.assignmentId; + if (!shiftId || !assignmentId) { + return res.status(400).json({ message: "Shift ID or Assignment ID is not provided" }); + } + + //get the assignment + const assignment = await prisma.assignment.findUnique({ + where: { + id: parseInt(assignmentId) + } + }); + if (!assignment) { + return res.status(404).json({ message: "Assignment not found" }); + } + if (assignment.shiftId != parseInt(shiftId)) { + return res.status(400).json({ message: "Shift ID does not match" }); + } + + // await prisma.user.update({ + // where: { + // id: parseInt(userId) + // }, + // data: { + // status: "accepted", + // acceptedAt: date + // } + // }); + break; + + //POST + case "send_report": //we can send report form in the emails to the user. process the POSTED data here + // Send report form to the user + //get from POST data: locationId, date, placementCount, videoCount, returnVisitInfoCount, conversationCount + let locationId = req.body.locationId; + let date = req.body.date; + let placementCount = req.body.placementCount; + let videoCount = req.body.videoCount; + let returnVisitInfoCount = req.body.returnVisitInfoCount; + let conversationCount = req.body.conversationCount; + + console.log("User: " + user.email + " sent a report: " + + locationId + " " + date + " " + + placementCount + " " + videoCount + " " + + returnVisitInfoCount + " " + conversationCount); + + //save the report in the database + await prisma.report.create({ + data: { + userId: parseInt(userId), + locationId: parseInt(locationId), + date: date, + placementCount: parseInt(placementCount), + videoCount: parseInt(videoCount), + returnVisitInfoCount: parseInt(returnVisitInfoCount), + conversationCount: parseInt(conversationCount) + } + }); + + break; + } + // //send email response to the user + // const emailResponse = await common.sendEmail(user.email, "Email Action Processed", + // "Your email action was processed successfully"); + } return res.status(200).json({ message: "User action processed" }); } diff --git a/src/helpers/email.js b/src/helpers/email.js index f73ef5f..09c4e82 100644 --- a/src/helpers/email.js +++ b/src/helpers/email.js @@ -39,6 +39,53 @@ exports.SendEmail = async function (to, subject, text, html) { }; }; +exports.SendEmailHandlebars = async function (to, templateName, model) { + // Ensure the sender and mailtrapTestClient are correctly defined or imported + + // Load and compile the main template + const mainTemplateSource = fs.readFileSync(path.join(__dirname, 'src', 'templates', 'emails', 'main.hbs'), 'utf8'); + const mainTemplate = Handlebars.compile(mainTemplateSource); + + // Dynamically load and compile the specified template + const templateSource = fs.readFileSync(path.join(__dirname, 'src', 'templates', 'emails', `${templateName}.hbs`), 'utf8'); + + // Extract subject and optional text version from the template source + const subjectMatch = templateSource.match(/{{!-- Subject: (.*) --}}/); + const textMatch = templateSource.match(/{{!-- Text: ([\s\S]*?) --}}/); + + const subject = subjectMatch ? subjectMatch[1].trim() : 'Default Subject'; + const textVersion = textMatch ? textMatch[1].trim() : null; + + // Remove the subject and text annotations from the template source + const cleanTemplateSource = templateSource.replace(/{{!-- Subject: .* --}}/, '').replace(/{{!-- Text: [\s\S]*? --}}/, ''); + + // Compile the cleaned template + const template = Handlebars.compile(cleanTemplateSource); + + // Render the specified template with the provided model + const templateHtml = template(model); + + // Render the main template, inserting the specific template HTML + const html = mainTemplate({ body: templateHtml }); + + // Generate a plain text version if not explicitly provided + const text = textVersion || html.replace(/<[^>]*>?/gm, ''); // Simple regex to strip HTML tags. Might need refinement. + + const message = { + from: sender, // Ensure 'sender' is defined + to, + subject, + text, + html, + }; + + // Assuming mailtrapTestClient is correctly set up to send emails + await mailtrapTestClient + .send(message) + .then(console.log) + .catch(console.error); +}; + exports.SendEmail_Test = async function (to, subject, text, html) { const message = { from: sender, diff --git a/src/templates/emails/coverMe.hbs b/src/templates/emails/coverMe.hbs index e69de29..90a58e8 100644 --- a/src/templates/emails/coverMe.hbs +++ b/src/templates/emails/coverMe.hbs @@ -0,0 +1,24 @@ +{{!-- Subject: Your email subject here --}} +{{!-- Text: Plain text version of your email. If not provided, HTML tags will be stripped from the HTML version for the +text version. --}} + +
+

{{firstName}} {{lastName}} търси зместник за {{placeName}}!

+

Здравейте,

+

{{prefix}} {{firstName}} {{lastName}} търси заместник за своята смяна на {{dateStr}} на {{placeName}}.

+ {{!--

Shift Details:

--}} + {{!--

Date: {{date}}
Time: {{time}}
Location: {{placeName}}

--}} +

С натискането на бутона по-долу можете да премете да го замествате. Вие, той/тя и останалите участници в смяната + ще бъдат уведумени чрез имейл за промяната. Вашата помощ е високо ценена.

+

+ Приеми + смяната +

+ {{!--

Thank you very much for considering my request.

+

Best regards,
{{name}}

--}} +
+
+

Изпратено на: {{sentDate}}

+
\ No newline at end of file diff --git a/src/templates/emails/main.hbs b/src/templates/emails/main.hbs new file mode 100644 index 0000000..80249dd --- /dev/null +++ b/src/templates/emails/main.hbs @@ -0,0 +1,25 @@ + + + + + + + Email Template + + + +
+

Company Name

+
+ +
+ {{{body}}} +
+ +
+ © 2024 Company Name. All rights reserved. +
+ + + \ No newline at end of file From 09db5ca8b9199387fdd122e12721a20719f51266 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 6 Apr 2024 11:09:22 +0300 Subject: [PATCH 12/19] cover me email request routine --- package-lock.json | 1 + package.json | 3 +- pages/api/email.ts | 260 +++++++++++++---------- pages/cart/publishers/myschedule.tsx | 25 ++- pages/message.tsx | 27 +++ src/helpers/common.js | 8 +- src/helpers/email.js | 289 ++++++++++++++++---------- src/templates/emails/coverMe copy.hbs | 24 +++ src/templates/emails/coverMe.hbs | 24 +-- src/templates/emails/main.hbs | 4 +- src/templates/emails/newShifts.hbs | 14 ++ 11 files changed, 433 insertions(+), 246 deletions(-) create mode 100644 pages/message.tsx create mode 100644 src/templates/emails/coverMe copy.hbs create mode 100644 src/templates/emails/newShifts.hbs diff --git a/package-lock.json b/package-lock.json index ad8e28f..c876ac9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,6 +80,7 @@ "tailwindcss": "^3.4.1", "tw-elements": "^1.1.0", "typescript": "^5", + "uuid": "^9.0.1", "webpack-bundle-analyzer": "^4.10.1", "winston": "^3.11.0", "xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz", diff --git a/package.json b/package.json index 2d0ae8e..cedd7b8 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "tailwindcss": "^3.4.1", "tw-elements": "^1.1.0", "typescript": "^5", + "uuid": "^9.0.1", "webpack-bundle-analyzer": "^4.10.1", "winston": "^3.11.0", "xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz", @@ -109,4 +110,4 @@ "depcheck": "^1.4.7", "prisma": "^5.11.0" } -} \ No newline at end of file +} diff --git a/pages/api/email.ts b/pages/api/email.ts index e77134e..cb84196 100644 --- a/pages/api/email.ts +++ b/pages/api/email.ts @@ -5,6 +5,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { createRouter, expressWrapper } from "next-connect"; const common = require('../../src/helpers/common'); const emailHelper = require('../../src/helpers/email'); +const { v4: uuidv4 } = require('uuid'); import fs from 'fs'; import path from 'path'; @@ -24,125 +25,60 @@ const router = createRouter(); export default async function handler(req, res) { const prisma = common.getPrismaClient(); - var action = req.query.action; + const action = req.query.action; + const emailaction = req.query.emailaction; // Retrieve and validate the JWT token - const token = await getToken({ req: req }); //response is a special action that does not require a token - if (action !== "email_response") { - if (!token) { - // If no token or invalid token, return unauthorized status - return res.status(401).json({ message: "Unauthorized to call this API endpoint" }); - } - } - - var userId = req.query.userId; - var email = req.query.email; - let date = new Date(); - - if (!userId && !email) { - return res.status(400).json({ message: "User ID or email is not provided" }); - } - // Retrieve the user - const user = await prisma.publisher.findUnique({ - where: { - id: userId, - email: email - } - }); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - var emailaction = req.query.emailaction; - switch (action) { - case "send_coverme_request": - // Send CoverMe request to the user - //get from POST data: shiftId, assignmentId, date - let shiftId = req.body.shiftId; - let assignmentId = req.body.assignmentId; - let date = req.body.date; - - console.log("User: " + user.email + " sent a CoverMe request: " + - shiftId + " " + assignmentId + " " + date); - - //get all subscribed publisers - const subscribedPublishers = await prisma.publisher.findMany({ - where: { - isSubscribedToCoverMe: true - } - }); - //send email to all subscribed publishers - for (let i = 0; i < subscribedPublishers.length; i++) { - //send email to subscribed publisher - let shift = await prisma.shift.findUnique({ - where: { - id: parseInt(shiftId) - }, - include: { - cartEvent: { - include: { - location: true - } - }, - } - } - ); - - let acceptUrl = process.env.NEXTAUTH_URL + "/api/emailActions?action=coverme_accept&userId=" + user.id + "&shiftId=" + shiftId + "&assignmentId=" + assignmentId; - - let model = { - user: user, - shiftId: shiftId, - assignmentId: assignmentId, - acceptUrl: acceptUrl, - prefix: subscribedPublishers[i].isMale ? "Брат" : "Сестра", - firstName: subscribedPublishers[i].firstName, - lastName: subscribedPublishers[i].lastName, - placeName: shift.cartEvent.location.name, - dateStr: date.toLocaleDateString(), - sentDate: date.toLocaleTimeString() - }; - emailHelper.SendEmailHandlebars(subscribedPublishers[i].email, "coverMe", model); - } - - break; - case "email_response": - //get email action - if (!emailaction) { - return res.status(400).json({ message: "Email action is not provided" }); - } - break; - default: - return res.status(400).json({ message: "Invalid action" }); - } - - - - if (action !== "email_response") { + if (action == "email_response") { switch (emailaction) { - case "coverme_accept": - // Update the user status to accepted - console.log("User: " + user.firstName + " " + user.lastName + " accepted the CoverMe request"); + case "coverMeAccept": //validate shiftId and assignmentId let shiftId = req.query.shiftId; - let assignmentId = req.query.assignmentId; - if (!shiftId || !assignmentId) { - return res.status(400).json({ message: "Shift ID or Assignment ID is not provided" }); - } - - //get the assignment - const assignment = await prisma.assignment.findUnique({ + let userId = req.query.userId; + let user = await prisma.publisher.findUnique({ where: { - id: parseInt(assignmentId) + id: userId } }); + // Update the user status to accepted + console.log("User: " + user.firstName + " " + user.lastName + " accepted the CoverMe request"); + + let assignmentPID = req.query.assignmentPID; + if (!shiftId) { + return res.status(400).json({ message: "Shift ID is not provided" }); + } + if (!assignmentPID) { + return res.status(400).json({ message: "Assignment PID is not provided" }); + } + //check if the assignment request is still open + const assignment = await prisma.assignment.findFirst({ + where: { + publicGuid: assignmentPID, + shiftId: parseInt(shiftId), + isConfirmed: false + }, + include: { + shift: { + include: { + cartEvent: { + include: { + location: true + } + } + } + } + } + }); + if (!assignment) { - return res.status(404).json({ message: "Assignment not found" }); - } - if (assignment.shiftId != parseInt(shiftId)) { - return res.status(400).json({ message: "Shift ID does not match" }); + const messagePageUrl = `/message?message=${encodeURIComponent('Някой друг вече е отговорил на рази заявка за заместване')}&type=info&caption=${encodeURIComponent('Заявката е вече обработена')}`; + res.redirect(messagePageUrl); + return; } + emailHelper.SendEmail_NewShifts(user, [assignment.shift]); + // await prisma.user.update({ // where: { // id: parseInt(userId) @@ -189,13 +125,109 @@ export default async function handler(req, res) { // const emailResponse = await common.sendEmail(user.email, "Email Action Processed", // "Your email action was processed successfully"); } - return res.status(200).json({ message: "User action processed" }); -} + else { + const token = await getToken({ req: req }); + if (!token) { + // If no token or invalid token, return unauthorized status + return res.status(401).json({ message: "Unauthorized to call this API endpoint" }); + } + + const user = await prisma.publisher.findUnique({ + where: { + email: token.email + } + }); + + switch (action) { + case "sendCoverMeRequestByEmail": + // Send CoverMe request to the user + //get from POST data: shiftId, assignmentId, date + //let shiftId = req.body.shiftId; + let assignmentId = req.body.assignmentId; + let date = req.body.date; + + console.log("User: " + user.email + " sent a 'CoverMe' request for his assignment " + assignmentId + " " + date); + + let assignment = await prisma.assignment.findUnique({ + where: { + id: parseInt(assignmentId) + }, + include: { + shift: { + include: { + cartEvent: { + include: { + location: true + } + } + } + } + } + }); + + // update the assignment. generate new publicGuid, isConfirmed to false + let newPublicGuid = uuidv4(); + await prisma.assignment.update({ + where: { + id: parseInt(assignmentId) + }, + data: { + publicGuid: newPublicGuid, // if this exists, we consider the request open + isConfirmed: false + } + }); + + //get all subscribed publisers + const subscribedPublishers = await prisma.publisher.findMany({ + where: { + isSubscribedToCoverMe: true + } + }); + //send email to all subscribed publishers + for (let i = 0; i < subscribedPublishers.length; i++) { + if (subscribedPublishers[i].id == user.id) { + continue; + } + + //send email to subscribed publisher + let acceptUrl = process.env.NEXTAUTH_URL + "/api/email?action=email_response&emailaction=coverMeAccept&userId=" + subscribedPublishers[i].id + "&shiftId=" + assignment.shiftId + "&assignmentPID=" + newPublicGuid; + + let model = { + user: user, + shiftId: assignment.shiftId, + acceptUrl: acceptUrl, + prefix: user.isMale ? "Брат" : "Сестра", + firstName: subscribedPublishers[i].firstName, + lastName: subscribedPublishers[i].lastName, + placeName: assignment.shift.cartEvent.location.name, + dateStr: common.getDateFormated(assignment.shift.startTime), + time: common.formatTimeHHmm(assignment.shift.startTime), + sentDate: common.getDateFormated(new Date()) + }; + let results = emailHelper.SendEmailHandlebars( + { + name: subscribedPublishers[i].firstName + " " + subscribedPublishers[i].lastName, + email: subscribedPublishers[i].email + }, "coverMe", model); + // if (results) { + // console.log("Error sending email: " + error); + // return res.status(500).json({ message: "Error sending email:" + error }); + //} + + if (results) { + console.log("Email sent to: " + subscribedPublishers[i].email); + } + + } + break; + default: + return res.status(400).json({ message: "Invalid action" }); + } + + return res.status(200).json({ message: "User action processed" }); + } + +} router.use(expressWrapper(handler)); - - - - - diff --git a/pages/cart/publishers/myschedule.tsx b/pages/cart/publishers/myschedule.tsx index 273dc56..ad93c97 100644 --- a/pages/cart/publishers/myschedule.tsx +++ b/pages/cart/publishers/myschedule.tsx @@ -51,6 +51,23 @@ export default function MySchedulePage({ assignments }) { console.log("error", error); }); }; + + const searchReplacement = (assignmentId) => { + axiosInstance.post('/api/email?action=sendCoverMeRequestByEmail', { + assignmentId: assignmentId, + }).then(response => { + console.log("response", response); + //toast success and confirm the change + toast.success("Заявката за заместник е изпратена!", { + onClose: () => { + window.location.reload(); + } + }); + }).catch(error => { + console.log("error", error); + }); + } + return ( @@ -101,14 +118,14 @@ export default function MySchedulePage({ assignments }) { setIsModalOpen(true) }} > - Заместник + Избери Заместник - {/* */} + diff --git a/pages/message.tsx b/pages/message.tsx new file mode 100644 index 0000000..a3502a3 --- /dev/null +++ b/pages/message.tsx @@ -0,0 +1,27 @@ +// pages/message.js + +import { useRouter } from 'next/router'; +import Layout from "../components/layout"; + +export default function MessagePage() { + const router = useRouter(); + const messageStyles = { + error: "text-red-500", + warning: "text-yellow-500", + info: "text-blue-500", + }; + const { message, type = messageStyles.info, caption } = router.query; + + return ( + +
+
+

{caption || 'Информация'}

+

+ {message || 'Така ще получавате различни съобщения.'} +

+
+
+
+ ); +} diff --git a/src/helpers/common.js b/src/helpers/common.js index 210119e..a9a8b24 100644 --- a/src/helpers/common.js +++ b/src/helpers/common.js @@ -525,7 +525,9 @@ exports.getCurrentYearMonth = () => { const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Month is 0-indexed return `${year}-${month}`; } - +exports.getTimeFormated = function (date) { + return this.formatTimeHHmm(date); +} // format date to 'HH:mm' time string required by the time picker exports.formatTimeHHmm = function (input) { // Check if the input is a string or a Date object @@ -729,3 +731,7 @@ exports.getLocalStorage = function (key, defaultValue) { } return defaultValue; }; + +exports.root = function (req) { + return process.env.NEXT_PUBLIC_PUBLIC_URL; +} diff --git a/src/helpers/email.js b/src/helpers/email.js index 09c4e82..288dc4b 100644 --- a/src/helpers/email.js +++ b/src/helpers/email.js @@ -1,10 +1,12 @@ // helper module to send emails with nodemailer const fs = require("fs"); +const path = require('path'); const { MailtrapClient } = require("mailtrap"); const nodemailer = require("nodemailer"); const CON = require("./const"); const CAL = require("./calendar"); +const Handlebars = require('handlebars'); // const { google } = require("googleapis"); // const OAuth2 = google.auth.OAuth2; @@ -12,14 +14,42 @@ const CAL = require("./calendar"); const { Shift, Publisher, PrismaClient } = require("@prisma/client"); const TOKEN = process.env.TOKEN || "a7d7147a530235029d74a4c2f228e6ad"; -const SENDER_EMAIL = "pw@d-popov.com"; -const sender = { name: "JW Cart: Shift Info", email: SENDER_EMAIL }; +const SENDER_EMAIL = "sofia@mwitnessing.com"; +const sender = { name: "Специално Свидетелстване София", email: SENDER_EMAIL }; const client = new MailtrapClient({ token: TOKEN }); -const mailtrapTestClient = new MailtrapClient({ - username: '8ec69527ff2104',//not working now - password: 'c7bc05f171c96c' -}); +let mailtrapTestClient = null; +// const mailtrapTestClient = new MailtrapClient({ +// username: '8ec69527ff2104',//not working now +// password: 'c7bc05f171c96c' +// }); +//test +var transporter = nodemailer.createTransport({ + host: "sandbox.smtp.mailtrap.io", + port: 2525, + auth: { + user: "8ec69527ff2104", + pass: "c7bc05f171c96c" + } +}); +// production +// var transporter = nodemailer.createTransport({ +// host: "live.smtp.mailtrap.io", +// port: 587, +// auth: { +// user: "api", +// pass: "1cfe82e747b8dc3390ed08bb16e0f48d" +// } +// }); + +var transporterBulk = nodemailer.createTransport({ + host: "bulk.smtp.mailtrap.io", + port: 587, + auth: { + user: "api", + pass: "1cfe82e747b8dc3390ed08bb16e0f48d" + } +}); // ------------------ Email sending ------------------ var lastResult = null; function setResult(result) { @@ -29,91 +59,173 @@ exports.GetLastResult = function () { return lastResult; }; -exports.SendEmail = async function (to, subject, text, html) { +exports.SendEmail = async function (to, subject, text, html, attachments = []) { + let sender = '"Специално Свидетелстване София - тест" '; + to = Array.isArray(to) ? to : [to]; + const emailAddresses = to.map(item => `"${item.name}" <${item.email}>`).join(', '); + const message = { from: sender, - to, + to: emailAddresses, subject, text, html, + attachments }; + + if (mailtrapTestClient !== null) { + // Assuming mailtrapTestClient is correctly set up to send emails + await mailtrapTestClient + .send(message) + .then(console.log) + .catch(console.error); + + } else { + + let result = await transporter + .sendMail(message) + .then(console.log) + .catch(console.error); + return result; + } + }; -exports.SendEmailHandlebars = async function (to, templateName, model) { - // Ensure the sender and mailtrapTestClient are correctly defined or imported +exports.SendEmailHandlebars = async function (to, templateName, model, attachments = []) { + try { + // Ensure the sender and mailtrapTestClient are correctly defined or imported - // Load and compile the main template - const mainTemplateSource = fs.readFileSync(path.join(__dirname, 'src', 'templates', 'emails', 'main.hbs'), 'utf8'); - const mainTemplate = Handlebars.compile(mainTemplateSource); + // Load and compile the main template + const mainTemplateSource = fs.readFileSync(path.join(process.cwd(), 'src', 'templates', 'emails', 'main.hbs'), 'utf8'); + const mainTemplate = Handlebars.compile(mainTemplateSource); - // Dynamically load and compile the specified template - const templateSource = fs.readFileSync(path.join(__dirname, 'src', 'templates', 'emails', `${templateName}.hbs`), 'utf8'); + // Dynamically load and compile the specified template + const templateSource = fs.readFileSync(path.join(process.cwd(), 'src', 'templates', 'emails', `${templateName}.hbs`), 'utf8'); - // Extract subject and optional text version from the template source - const subjectMatch = templateSource.match(/{{!-- Subject: (.*) --}}/); - const textMatch = templateSource.match(/{{!-- Text: ([\s\S]*?) --}}/); + // Extract subject and optional text version from the template source + const subjectMatch = templateSource.match(/{{!-- Subject: (.*) --}}/); + const textMatch = templateSource.match(/{{!-- Text: ([\s\S]*?) --}}/); - const subject = subjectMatch ? subjectMatch[1].trim() : 'Default Subject'; - const textVersion = textMatch ? textMatch[1].trim() : null; + const subject = subjectMatch ? subjectMatch[1].trim() : 'Default Subject'; + const textVersion = textMatch ? textMatch[1].trim() : null; - // Remove the subject and text annotations from the template source - const cleanTemplateSource = templateSource.replace(/{{!-- Subject: .* --}}/, '').replace(/{{!-- Text: [\s\S]*? --}}/, ''); + // Remove the subject and text annotations from the template source + const cleanTemplateSource = templateSource.replace(/{{!-- Subject: .* --}}/, '').replace(/{{!-- Text: [\s\S]*? --}}/, ''); - // Compile the cleaned template - const template = Handlebars.compile(cleanTemplateSource); + // Compile the cleaned template + const template = Handlebars.compile(cleanTemplateSource); - // Render the specified template with the provided model - const templateHtml = template(model); + // Render the specified template with the provided model + const templateHtml = template(model); - // Render the main template, inserting the specific template HTML - const html = mainTemplate({ body: templateHtml }); + // Render the main template, inserting the specific template HTML + const html = mainTemplate({ body: templateHtml }); - // Generate a plain text version if not explicitly provided - const text = textVersion || html.replace(/<[^>]*>?/gm, ''); // Simple regex to strip HTML tags. Might need refinement. + // Generate a plain text version if not explicitly provided + const text = textVersion || html.replace(/<[^>]*>?/gm, ''); // Simple regex to strip HTML tags. Might need refinement. - const message = { - from: sender, // Ensure 'sender' is defined - to, - subject, - text, - html, - }; + let results = this.SendEmail(to, subject, text, html, attachments); + return results; - // Assuming mailtrapTestClient is correctly set up to send emails - await mailtrapTestClient - .send(message) - .then(console.log) - .catch(console.error); + } catch (error) { + console.error(error); + return new Error('Error sending email'); + } }; -exports.SendEmail_Test = async function (to, subject, text, html) { - const message = { - from: sender, - to, - subject, - text, - html, + +exports.SendEmail_NewShifts = async function (publisher, shifts) { + if (shifts.length === 0) return; + + var date = new Date(shifts[0].startTime); + + // Generate ICS calendar links for all shifts + const icsLink = CAL.GenerateICS(shifts); + + // Prepare the shifts string + const shiftStr = shifts.map((s) => { + return `${CON.weekdaysBG[s.startTime.getDay()]} ${CON.GetDateFormat(s.startTime)} at ${s.cartEvent.location.name} from ${CON.GetTimeFormat(s.startTime)} to ${CON.GetTimeFormat(s.endTime)}`; + }).join("
"); + + // Define the model for the Handlebars template + const model = { + publisherFirstName: publisher.firstName, + publisherLastName: publisher.lastName, + month: CON.monthNamesBG[date.getMonth()], + shifts: shiftStr, + sentDate: new Date().toLocaleDateString() // Assuming you want to include the sent date in the email }; - await mailtrapTestClient - .send(message) - .then(console.log, console.error, setResult); -} + // Call the refactored function to send the email with Handlebars template rendering + await exports.SendEmailHandlebars( + publisher.email, // Assuming the publisher's email is to be used + "newShifts", // The name of your Handlebars template for new shifts notification + model, + [{ + filename: "calendar.ics", + content: icsLink, + contentType: 'text/calendar' // Ensure this is correctly set for the ICS file + }] + ).then(console.log).catch(console.error); +}; + + + + + + + +//----------------------- OLD ----------------------------- + +// exports.SendEmail_NewShifts = async function (publisher, shifts) { +// if (shifts.length == 0) return; + +// var date = new Date(shifts[0].startTime); + +// //generate ICS calendar links for all shifts +// const icsLink = CAL.GenerateICS(shifts); + +// const shftStr = shifts +// .map((s) => { +// return ` ${CON.weekdaysBG[s.startTime.getDay()] +// } ${CON.GetDateFormat(s.startTime)} ${s.cartEvent.location.name +// } ${CON.GetTimeFormat(s.startTime)} - ${CON.GetTimeFormat( +// s.endTime +// )}`; +// }) +// .join("\n"); + +// await client.send({ +// from: sender, +// to: [ +// { +// email: "dobromir.popov@gmail.com",//publisher.email, +// name: publisher.firstName + " " + publisher.lastName, +// }, +// ], +// subject: "[CCC]: вашите смени през " + CON.monthNamesBG[date.getMonth()], +// text: +// "Здравейте, " + publisher.firstName + " " + publisher.lastName + "!\n\n" + +// "Ти регистриран да получавате известия за нови смени на количка.\n" + +// `За месец ${CON.monthNamesBG[date.getMonth()]} имате следните смени:\n` + +// ` ${shftStr} \n\n\n` + +// "Поздрави,\n" + +// "Специално Свидетелстване София", +// attachments: [ +// { +// filename: "calendar.ics", +// content_id: "calendar.ics", +// disposition: "inline", +// content: icsLink, +// }, +// ], +// }) +// .then(console.log, console.error, setResult); +// }; // https://mailtrap.io/blog/sending-emails-with-nodemailer/ -exports.SendTestEmail = async function (to) { - // await client - // .send({ - // from: sender, - // to: [{ email: RECIPIENT_EMAIL }], - // subject: "Hello from Mailtrap!", - // text: "Welcome to Mailtrap Sending!",Shift Info" - // }) - // .then(console.log, console.error, setResult); - - // return lastResult; - +exports.SendEmail_Example = async function (to) { const welcomeImage = fs.readFileSync( path.join(CON.contentPath, "welcome.png") ); @@ -160,50 +272,3 @@ exports.SendTestEmail = async function (to) { }) .then(console.log, console.error, setResult); }; - - -exports.SendEmail_NewShifts = async function (publisher, shifts) { - if (shifts.length == 0) return; - - var date = new Date(shifts[0].startTime); - - //generate ICS calendar links for all shifts - const icsLink = CAL.GenerateICS(shifts); - - const shftStr = shifts - .map((s) => { - return ` ${CON.weekdaysBG[s.startTime.getDay()] - } ${CON.GetDateFormat(s.startTime)} ${s.cartEvent.location.name - } ${CON.GetTimeFormat(s.startTime)} - ${CON.GetTimeFormat( - s.endTime - )}`; - }) - .join("\n"); - - await client.send({ - from: sender, - to: [ - { - email: "dobromir.popov@gmail.com",//publisher.email, - name: publisher.firstName + " " + publisher.lastName, - }, - ], - subject: "[CCC]: вашите смени през " + CON.monthNamesBG[date.getMonth()], - text: - "Здравейте, " + publisher.firstName + " " + publisher.lastName + "!\n\n" + - "Ти регистриран да получавате известия за нови смени на количка.\n" + - `За месец ${CON.monthNamesBG[date.getMonth()]} имате следните смени:\n` + - ` ${shftStr} \n\n\n` + - "Поздрави,\n" + - "Специално Свидетелстване София", - attachments: [ - { - filename: "calendar.ics", - content_id: "calendar.ics", - disposition: "inline", - content: icsLink, - }, - ], - }) - .then(console.log, console.error, setResult); -}; diff --git a/src/templates/emails/coverMe copy.hbs b/src/templates/emails/coverMe copy.hbs new file mode 100644 index 0000000..e300167 --- /dev/null +++ b/src/templates/emails/coverMe copy.hbs @@ -0,0 +1,24 @@ +{{!-- Subject: ССС: Нужен е заместник--}} +{{!-- Text: Plain text version of your email. If not provided, HTML tags will be stripped from the HTML version for the +text version. --}} + +
+

Търси се зместник за смяна на {{placeName}} за {{dateStr}}!

+

Здравейте,

+

{{prefix}} {{firstName}} {{lastName}} търси заместник.

+ {{!--

Shift Details:

--}} +

Дата: {{dateStr}}
Час: {{time}}
Място: {{placeName}}

+

С натискането на бутона по-долу можете да премете да го замествате. Вие, той/тя и останалите участници в смяната + ще бъдат уведумени чрез имейл за промяната. Вашата помощ е много ценна.

+

+ Ще + поема смяната +

+ {{!--

Thank you very much for considering my request.

+

Best regards,
{{name}}

--}} +
+
+

Изпратено на: {{sentDate}}

+
\ No newline at end of file diff --git a/src/templates/emails/coverMe.hbs b/src/templates/emails/coverMe.hbs index 90a58e8..8f238fe 100644 --- a/src/templates/emails/coverMe.hbs +++ b/src/templates/emails/coverMe.hbs @@ -1,24 +1,24 @@ -{{!-- Subject: Your email subject here --}} -{{!-- Text: Plain text version of your email. If not provided, HTML tags will be stripped from the HTML version for the -text version. --}} +{{!-- Subject: ССС: Нужен е заместник--}}
-

{{firstName}} {{lastName}} търси зместник за {{placeName}}!

-

Здравейте,

-

{{prefix}} {{firstName}} {{lastName}} търси заместник за своята смяна на {{dateStr}} на {{placeName}}.

+

Търси се зместник за смяна на {{placeName}} за {{dateStr}}!

+

Здравей {{firstName}},

+

{{prefix}} {{user.firstName}} {{user.lastName}} търси заместник.

{{!--

Shift Details:

--}} - {{!--

Date: {{date}}
Time: {{time}}
Location: {{placeName}}

--}} -

С натискането на бутона по-долу можете да премете да го замествате. Вие, той/тя и останалите участници в смяната - ще бъдат уведумени чрез имейл за промяната. Вашата помощ е високо ценена.

+

Дата: {{dateStr}}
Час: {{time}}
Място: {{placeName}}

+

С натискането на бутона по-долу можеш да премеш да го заместваш. + {{!-- Ти, той/тя и останалите участници в смяната ще + получат имейл за промяната. Твоята помощ е много ценна. --}} +

Приеми - смяната + style="background-color: #4CAF50; color: white; padding: 10px 20px; text-decoration: none; display: inline-block; border-radius: 5px;">Ще + поема смяната

{{!--

Thank you very much for considering my request.

Best regards,
{{name}}

--}}
-

Изпратено на: {{sentDate}}

+

Изпратено до {{firstName}} {{lastName}} на {{sentDate}}

\ No newline at end of file diff --git a/src/templates/emails/main.hbs b/src/templates/emails/main.hbs index 80249dd..d9959a4 100644 --- a/src/templates/emails/main.hbs +++ b/src/templates/emails/main.hbs @@ -10,7 +10,7 @@
-

Company Name

+

Cпециално Свидетелстване София

@@ -18,7 +18,7 @@
- © 2024 Company Name. All rights reserved. + © 2024 ССС. All rights reserved.
diff --git a/src/templates/emails/newShifts.hbs b/src/templates/emails/newShifts.hbs new file mode 100644 index 0000000..32a1508 --- /dev/null +++ b/src/templates/emails/newShifts.hbs @@ -0,0 +1,14 @@ +{{!-- Subject: ССС: Нужен е заместник--}} + +
+

Здравейте, {{publisherFirstName}} {{publisherLastName}}!

+

Ти регистриран да получавате известия за нови смени на количка.

+

За месец {{month}} имате следните смени:

+
+ {{{shifts}}} +
+
+ +
+ Изпратено на: {{sentDate}} +
\ No newline at end of file From fa786485c208635c8ccd7855b9f1198af2b13f92 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 6 Apr 2024 11:09:32 +0300 Subject: [PATCH 13/19] email tweaks --- src/helpers/email.js | 43 +++++++++++++++++++++++++----- src/templates/emails/coverMe.hbs | 2 +- src/templates/emails/main.hbs | 2 +- src/templates/emails/newShifts.hbs | 2 +- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/helpers/email.js b/src/helpers/email.js index 288dc4b..8f5d2ff 100644 --- a/src/helpers/email.js +++ b/src/helpers/email.js @@ -58,11 +58,36 @@ function setResult(result) { exports.GetLastResult = function () { return lastResult; }; +function normalizeEmailAddresses(to) { + // If 'to' is already a string, it could be a single email or a CSV of emails + if (typeof to === 'string') { + // Check if 'to' is a CSV string of emails; split it into an array if true + if (to.includes(',')) { + return to.split(/\s*,\s*/); // Split by comma and trim spaces around emails + } + // Otherwise, return it as a single-element array + return [to]; + } + // If 'to' is an array, determine if it's an array of strings or objects + if (Array.isArray(to)) { + return to.map(item => { + // If the item is a string, return it directly + if (typeof item === 'string') return item; + // If the item is an object with name and email, format it + if (item.name && item.email) return `"${item.name}" <${item.email}>`; + // If the item is an object but doesn't match expected structure, stringify it + return JSON.stringify(item); + }).join(', '); + } + + // Fallback for any other types (unlikely but safe) + return String(to); +} exports.SendEmail = async function (to, subject, text, html, attachments = []) { let sender = '"Специално Свидетелстване София - тест" '; to = Array.isArray(to) ? to : [to]; - const emailAddresses = to.map(item => `"${item.name}" <${item.email}>`).join(', '); + const emailAddresses = normalizeEmailAddresses(to) const message = { from: sender, @@ -103,15 +128,17 @@ exports.SendEmailHandlebars = async function (to, templateName, model, attachmen const templateSource = fs.readFileSync(path.join(process.cwd(), 'src', 'templates', 'emails', `${templateName}.hbs`), 'utf8'); // Extract subject and optional text version from the template source - const subjectMatch = templateSource.match(/{{!-- Subject: (.*) --}}/); - const textMatch = templateSource.match(/{{!-- Text: ([\s\S]*?) --}}/); + const subjectMatch = templateSource.match(/{{!--\s*Subject:\s*(.*?)\s*--}}/); + const textMatch = templateSource.match(/{{!--\s*Text:\s*([\s\S]*?)\s*--}}/); - const subject = subjectMatch ? subjectMatch[1].trim() : 'Default Subject'; - const textVersion = textMatch ? textMatch[1].trim() : null; + let subject = subjectMatch ? subjectMatch[1].trim() : 'ССС: Известие'; + let textVersion = textMatch ? textMatch[1].trim() : null; // Remove the subject and text annotations from the template source const cleanTemplateSource = templateSource.replace(/{{!-- Subject: .* --}}/, '').replace(/{{!-- Text: [\s\S]*? --}}/, ''); - + // const cleanTemplateSource = templateSource + // .replace(/{{!--\s*Subject:.*?--}}\s*/, '') + // .replace(/{{!--\s*Text:.*?--}}\s*/, ''); // Compile the cleaned template const template = Handlebars.compile(cleanTemplateSource); @@ -122,7 +149,9 @@ exports.SendEmailHandlebars = async function (to, templateName, model, attachmen const html = mainTemplate({ body: templateHtml }); // Generate a plain text version if not explicitly provided - const text = textVersion || html.replace(/<[^>]*>?/gm, ''); // Simple regex to strip HTML tags. Might need refinement. + let text = textVersion || html.replace(/<[^>]*>?/gm, ''); // Simple regex to strip HTML tags. Might need refinement. + subject = Handlebars.compile(subject)(model); + text = Handlebars.compile(text)(model); let results = this.SendEmail(to, subject, text, html, attachments); return results; diff --git a/src/templates/emails/coverMe.hbs b/src/templates/emails/coverMe.hbs index 8f238fe..f95ecd1 100644 --- a/src/templates/emails/coverMe.hbs +++ b/src/templates/emails/coverMe.hbs @@ -1,4 +1,4 @@ -{{!-- Subject: ССС: Нужен е заместник--}} +{{!--Subject: ССС: Нужен е заместник --}}

Търси се зместник за смяна на {{placeName}} за {{dateStr}}!

diff --git a/src/templates/emails/main.hbs b/src/templates/emails/main.hbs index d9959a4..1669557 100644 --- a/src/templates/emails/main.hbs +++ b/src/templates/emails/main.hbs @@ -5,7 +5,7 @@ - Email Template + ССС известия diff --git a/src/templates/emails/newShifts.hbs b/src/templates/emails/newShifts.hbs index 32a1508..5f52bc8 100644 --- a/src/templates/emails/newShifts.hbs +++ b/src/templates/emails/newShifts.hbs @@ -1,4 +1,4 @@ -{{!-- Subject: ССС: Нужен е заместник--}} +{{!-- Subject: ССС: Нови назначени смени--}}

Здравейте, {{publisherFirstName}} {{publisherLastName}}!

From c480a4821a16817d953f36693d2ede0f90445d43 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 6 Apr 2024 11:10:12 +0300 Subject: [PATCH 14/19] optimize normalization --- src/helpers/email.js | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/helpers/email.js b/src/helpers/email.js index 8f5d2ff..0dff56a 100644 --- a/src/helpers/email.js +++ b/src/helpers/email.js @@ -58,35 +58,34 @@ function setResult(result) { exports.GetLastResult = function () { return lastResult; }; + function normalizeEmailAddresses(to) { - // If 'to' is already a string, it could be a single email or a CSV of emails + let emails = []; + if (typeof to === 'string') { - // Check if 'to' is a CSV string of emails; split it into an array if true - if (to.includes(',')) { - return to.split(/\s*,\s*/); // Split by comma and trim spaces around emails - } - // Otherwise, return it as a single-element array - return [to]; - } - - // If 'to' is an array, determine if it's an array of strings or objects - if (Array.isArray(to)) { - return to.map(item => { - // If the item is a string, return it directly + // Handle CSV string by splitting into an array + if (to.includes(',')) emails = to.split(/\s*,\s*/); + else emails = [to]; // Handle single email string + } else if (Array.isArray(to)) { + emails = to.map(item => { if (typeof item === 'string') return item; - // If the item is an object with name and email, format it if (item.name && item.email) return `"${item.name}" <${item.email}>`; - // If the item is an object but doesn't match expected structure, stringify it - return JSON.stringify(item); - }).join(', '); + return JSON.stringify(item); // Handle unexpected object format + }); + } else if (typeof to === 'object' && to.email) { + // Handle single object + emails = [`"${to.name}" <${to.email}>`]; + } else { + // Fallback for other types + emails = [String(to)]; } - // Fallback for any other types (unlikely but safe) - return String(to); + return emails; // Always returns an array } + + exports.SendEmail = async function (to, subject, text, html, attachments = []) { let sender = '"Специално Свидетелстване София - тест" '; - to = Array.isArray(to) ? to : [to]; const emailAddresses = normalizeEmailAddresses(to) const message = { From aa4e607866bbfa9c603864ff3fe982e5cdbc397e Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 6 Apr 2024 12:12:01 +0300 Subject: [PATCH 15/19] coverMeRourine completed :) --- pages/api/email.ts | 80 ++++++++++++++++++- pages/cart/publishers/myschedule.tsx | 5 +- src/helpers/email.js | 9 +-- src/templates/emails/coverMe.hbs | 6 +- src/templates/emails/coverMeAccepted.hbs | 15 ++++ .../emails/{coverMe copy.hbs => example.hbs} | 0 6 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 src/templates/emails/coverMeAccepted.hbs rename src/templates/emails/{coverMe copy.hbs => example.hbs} (100%) diff --git a/pages/api/email.ts b/pages/api/email.ts index cb84196..1988183 100644 --- a/pages/api/email.ts +++ b/pages/api/email.ts @@ -6,6 +6,7 @@ import { createRouter, expressWrapper } from "next-connect"; const common = require('../../src/helpers/common'); const emailHelper = require('../../src/helpers/email'); const { v4: uuidv4 } = require('uuid'); +const CON = require("../../src/helpers/const"); import fs from 'fs'; import path from 'path'; @@ -36,13 +37,13 @@ export default async function handler(req, res) { //validate shiftId and assignmentId let shiftId = req.query.shiftId; let userId = req.query.userId; - let user = await prisma.publisher.findUnique({ + let publisher = await prisma.publisher.findUnique({ where: { id: userId } }); // Update the user status to accepted - console.log("User: " + user.firstName + " " + user.lastName + " accepted the CoverMe request"); + console.log("User: " + publisher.firstName + " " + publisher.lastName + " accepted the CoverMe request"); let assignmentPID = req.query.assignmentPID; if (!shiftId) { @@ -65,9 +66,22 @@ export default async function handler(req, res) { include: { location: true } + }, + assignments: { + include: { + publisher: true + // { + // include: { + // email: true, + // firstName: true, + // lastName: true + // } + // } + } } } - } + }, + publisher: true } }); @@ -77,7 +91,64 @@ export default async function handler(req, res) { return; } - emailHelper.SendEmail_NewShifts(user, [assignment.shift]); + let to = assignment.shift.assignments.map(a => a.publisher.email); + to.push(publisher.email); + + // update the assignment. clear the guid, isConfirmed to true + await prisma.assignment.update({ + where: { + id: assignment.id + }, + data: { + publisherId: userId, + publicGuid: null, // if this exists, we consider the request open + isConfirmed: true + } + }); + const newAssignment = await prisma.assignment.findFirst({ + where: { + shiftId: parseInt(shiftId), + isConfirmed: true + }, + include: { + shift: { + include: { + cartEvent: { + include: { + location: true + } + }, + assignments: { + include: { + publisher: true + } + } + } + } + } + }); + + + const shiftStr = `${CON.weekdaysBG[assignment.shift.startTime.getDay()]} ${CON.GetDateFormat(assignment.shift.startTime)} at ${assignment.shift.cartEvent.location.name} from ${CON.GetTimeFormat(assignment.shift.startTime)} to ${CON.GetTimeFormat(assignment.shift.endTime)}`; + + const allNewPubNames = newAssignment.shift.assignments.map(a => a.publisher.firstName + " " + a.publisher.lastName + "\t\t" + a.publisher.phone + ).join("
"); + + let model = { + user: publisher, + shiftStr: shiftStr, + shiftId: assignment.shiftId, + prefix: publisher.isMale ? "Брат" : "Сестра", + oldPubName: assignment.publisher.firstName + " " + assignment.publisher.lastName, + firstName: publisher.firstName, + lastName: publisher.lastName, + newPubNames: allNewPubNames, + placeName: assignment.shift.cartEvent.location.name, + dateStr: common.getDateFormated(assignment.shift.startTime), + time: common.formatTimeHHmm(assignment.shift.startTime), + sentDate: common.getDateFormated(new Date()) + }; + + emailHelper.SendEmailHandlebars(to, "coverMeAccepted", model); // await prisma.user.update({ // where: { @@ -200,6 +271,7 @@ export default async function handler(req, res) { prefix: user.isMale ? "Брат" : "Сестра", firstName: subscribedPublishers[i].firstName, lastName: subscribedPublishers[i].lastName, + email: subscribedPublishers[i].email, placeName: assignment.shift.cartEvent.location.name, dateStr: common.getDateFormated(assignment.shift.startTime), time: common.formatTimeHHmm(assignment.shift.startTime), diff --git a/pages/cart/publishers/myschedule.tsx b/pages/cart/publishers/myschedule.tsx index ad93c97..c7c4ee2 100644 --- a/pages/cart/publishers/myschedule.tsx +++ b/pages/cart/publishers/myschedule.tsx @@ -92,8 +92,9 @@ export default function MySchedulePage({ assignments }) {
{assignment.shift.assignments.map((a, index) => { return ( - - {a.publisher.firstName} {a.publisher.lastName}{a.isWithTransport && } + + {a.publisher.firstName} {a.publisher.lastName} + {a.isWithTransport && } ) } diff --git a/src/helpers/email.js b/src/helpers/email.js index 0dff56a..a69db91 100644 --- a/src/helpers/email.js +++ b/src/helpers/email.js @@ -184,11 +184,7 @@ exports.SendEmail_NewShifts = async function (publisher, shifts) { sentDate: new Date().toLocaleDateString() // Assuming you want to include the sent date in the email }; - // Call the refactored function to send the email with Handlebars template rendering - await exports.SendEmailHandlebars( - publisher.email, // Assuming the publisher's email is to be used - "newShifts", // The name of your Handlebars template for new shifts notification - model, + await exports.SendEmailHandlebars(publisher.email, "newShifts", model, [{ filename: "calendar.ics", content: icsLink, @@ -200,9 +196,6 @@ exports.SendEmail_NewShifts = async function (publisher, shifts) { - - - //----------------------- OLD ----------------------------- // exports.SendEmail_NewShifts = async function (publisher, shifts) { diff --git a/src/templates/emails/coverMe.hbs b/src/templates/emails/coverMe.hbs index f95ecd1..2a954f8 100644 --- a/src/templates/emails/coverMe.hbs +++ b/src/templates/emails/coverMe.hbs @@ -1,7 +1,9 @@ {{!--Subject: ССС: Нужен е заместник --}}
-

Търси се зместник за смяна на {{placeName}} за {{dateStr}}!

+

Търси се зместник + {{!-- за смяна на {{placeName}} за {{dateStr}}! --}} +

Здравей {{firstName}},

{{prefix}} {{user.firstName}} {{user.lastName}} търси заместник.

{{!--

Shift Details:

--}} @@ -20,5 +22,5 @@

Best regards,
{{name}}

--}}
-

Изпратено до {{firstName}} {{lastName}} на {{sentDate}}

+

Изпратено до {{firstName}} {{lastName}} {{email}} {{sentDate}}

\ No newline at end of file diff --git a/src/templates/emails/coverMeAccepted.hbs b/src/templates/emails/coverMeAccepted.hbs new file mode 100644 index 0000000..a7fc71f --- /dev/null +++ b/src/templates/emails/coverMeAccepted.hbs @@ -0,0 +1,15 @@ +{{!-- Subject: ССС: Нови назначени смени--}} + +
+

Промяна твоята смяна на {{placeName}} {{dateStr}}

+

Здравейте {{firstName}},

+

{{firstName}} {{lastName}} ще замести {{oldPubName}} на смяната ви на {{dateStr}}

+

Новаия списък с участници за тази смяна е:

+ {{{newPubNames}}} +
+
+
+ +
+ Изпратено на: {{sentDate}} +
\ No newline at end of file diff --git a/src/templates/emails/coverMe copy.hbs b/src/templates/emails/example.hbs similarity index 100% rename from src/templates/emails/coverMe copy.hbs rename to src/templates/emails/example.hbs From fdbf2642ccf7588b394922c446fce6674aa5a0f6 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 6 Apr 2024 12:20:34 +0300 Subject: [PATCH 16/19] typo fix --- pages/api/email.ts | 2 +- src/templates/emails/coverMeAccepted.hbs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/api/email.ts b/pages/api/email.ts index 1988183..df2002c 100644 --- a/pages/api/email.ts +++ b/pages/api/email.ts @@ -131,7 +131,7 @@ export default async function handler(req, res) { const shiftStr = `${CON.weekdaysBG[assignment.shift.startTime.getDay()]} ${CON.GetDateFormat(assignment.shift.startTime)} at ${assignment.shift.cartEvent.location.name} from ${CON.GetTimeFormat(assignment.shift.startTime)} to ${CON.GetTimeFormat(assignment.shift.endTime)}`; - const allNewPubNames = newAssignment.shift.assignments.map(a => a.publisher.firstName + " " + a.publisher.lastName + "\t\t" + a.publisher.phone + ).join("
"); + const allNewPubNames = newAssignment.shift.assignments.map(a => a.publisher.firstName + " " + a.publisher.lastName + "\t\t" + a.publisher.phone + "").join("
"); let model = { user: publisher, diff --git a/src/templates/emails/coverMeAccepted.hbs b/src/templates/emails/coverMeAccepted.hbs index a7fc71f..accba5e 100644 --- a/src/templates/emails/coverMeAccepted.hbs +++ b/src/templates/emails/coverMeAccepted.hbs @@ -5,7 +5,7 @@

Здравейте {{firstName}},

{{firstName}} {{lastName}} ще замести {{oldPubName}} на смяната ви на {{dateStr}}

Новаия списък с участници за тази смяна е:

- {{{newPubNames}}} +

{{{newPubNames}}}

From 7f4ba3545cd693fe2fc78ee1c6ef9d7f91730fa2 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 6 Apr 2024 12:38:02 +0300 Subject: [PATCH 17/19] mailing UI tweaks and bugs fixed --- pages/api/email.ts | 19 ++++++++----------- src/templates/emails/coverMe.hbs | 2 +- src/templates/emails/coverMeAccepted.hbs | 10 +++++++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pages/api/email.ts b/pages/api/email.ts index df2002c..f565e5f 100644 --- a/pages/api/email.ts +++ b/pages/api/email.ts @@ -131,7 +131,10 @@ export default async function handler(req, res) { const shiftStr = `${CON.weekdaysBG[assignment.shift.startTime.getDay()]} ${CON.GetDateFormat(assignment.shift.startTime)} at ${assignment.shift.cartEvent.location.name} from ${CON.GetTimeFormat(assignment.shift.startTime)} to ${CON.GetTimeFormat(assignment.shift.endTime)}`; - const allNewPubNames = newAssignment.shift.assignments.map(a => a.publisher.firstName + " " + a.publisher.lastName + "\t\t" + a.publisher.phone + "").join("
"); + const newPubs = newAssignment.shift.assignments.map(a => ({ + name: `${a.publisher.firstName} ${a.publisher.lastName}`, + phone: a.publisher.phone + })); let model = { user: publisher, @@ -141,7 +144,7 @@ export default async function handler(req, res) { oldPubName: assignment.publisher.firstName + " " + assignment.publisher.lastName, firstName: publisher.firstName, lastName: publisher.lastName, - newPubNames: allNewPubNames, + newPubs: newPubs, placeName: assignment.shift.cartEvent.location.name, dateStr: common.getDateFormated(assignment.shift.startTime), time: common.formatTimeHHmm(assignment.shift.startTime), @@ -150,15 +153,9 @@ export default async function handler(req, res) { emailHelper.SendEmailHandlebars(to, "coverMeAccepted", model); - // await prisma.user.update({ - // where: { - // id: parseInt(userId) - // }, - // data: { - // status: "accepted", - // acceptedAt: date - // } - // }); + const messagePageUrl = `/message?message=${encodeURIComponent('Вашата заявка за замстване е обработена успешно')}&type=info&caption=${encodeURIComponent('Благодарим ви!')}`; + res.redirect(messagePageUrl); + break; //POST diff --git a/src/templates/emails/coverMe.hbs b/src/templates/emails/coverMe.hbs index 2a954f8..2855a11 100644 --- a/src/templates/emails/coverMe.hbs +++ b/src/templates/emails/coverMe.hbs @@ -1,7 +1,7 @@ {{!--Subject: ССС: Нужен е заместник --}}
-

Търси се зместник +

Търси се зместник: {{!-- за смяна на {{placeName}} за {{dateStr}}! --}}

Здравей {{firstName}},

diff --git a/src/templates/emails/coverMeAccepted.hbs b/src/templates/emails/coverMeAccepted.hbs index accba5e..62cbb6c 100644 --- a/src/templates/emails/coverMeAccepted.hbs +++ b/src/templates/emails/coverMeAccepted.hbs @@ -1,11 +1,15 @@ -{{!-- Subject: ССС: Нови назначени смени--}} +{{!-- Subject: ССС: Промени в твоята смяна --}}

Промяна твоята смяна на {{placeName}} {{dateStr}}

Здравейте {{firstName}},

-

{{firstName}} {{lastName}} ще замести {{oldPubName}} на смяната ви на {{dateStr}}

+

{{firstName}} {{lastName}} ще замести {{oldPubName}} на смяната ви на {{dateStr}} от {{time}}

Новаия списък с участници за тази смяна е:

-

{{{newPubNames}}}

+
    + {{#each newPubs}} +
  • {{this.name}} - {{this.phone}}
  • + {{/each}} +
From 1e9b7088d9e77a359012cfd4b904c8914a330004 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 6 Apr 2024 12:40:59 +0300 Subject: [PATCH 18/19] sync ymls, edit startup script --- _deploy/deoloy.azure.demo.yml | 15 +++++++-------- _deploy/deoloy.azure.production.yml | 10 +++------- package.json | 8 ++++---- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/_deploy/deoloy.azure.demo.yml b/_deploy/deoloy.azure.demo.yml index 5e868fa..dcb1a7f 100644 --- a/_deploy/deoloy.azure.demo.yml +++ b/_deploy/deoloy.azure.demo.yml @@ -1,14 +1,15 @@ version: "3" services: - nextjs-app: # https://sofia.mwitnessing.com/ + nextjs-app: # https://sofia.mwhitnessing.com/ hostname: jwpw-app-staging # jwpw-nextjs-app-1 image: docker.d-popov.com/jwpw:latest volumes: - /mnt/docker_volumes/pw-demo/app/public/content/uploads/:/app/public/content/uploads environment: - - NODE_ENV=demo + - APP_ENV=test + - NODE_ENV=test - TZ=Europe/Sofia - - DATABASE=mysql://jwpwsofia_demo:dwxhns9p9vp248@jwpwsofia:3306/jwpwsofia_demo + - DATABASE_URL=mysql://jwpwsofia_demo:dwxhns9p9vp248@mariadb-demo:3306/jwpwsofia_demo - UPDATE_CODE_FROM_GIT=true # Set to true to pull latest code from Git - GIT_BRANCH=main - GIT_USERNAME=deploy @@ -21,19 +22,17 @@ services: - infrastructure_default mariadb: deploy: - replicas: 0 + replicas: 1 hostname: mariadb-demo - image: mariadb:latest #mariadb:10.4 + image: mysql:latest #mariadb:10.4 volumes: - - /mnt/docker_volumes/pw-demo/data/mysql:/var/lib/mysql + - /mnt/docker_volumes/pw-demo2/data/mysql:/var/lib/mysql environment: MARIADB_ROOT_PASSWORD: i4966cWBtP3xJ7BLsbsgo93 MYSQL_ROOT_PASSWORD: i4966cWBtP3xJ7BLsbsgo93 MYSQL_DATABASE: jwpwsofia_demo MYSQL_USER: jwpwsofia_demo MYSQL_PASSWORD: dwxhns9p9vp248 - networks: - - infrastructure_default networks: infrastructure_default: external: true diff --git a/_deploy/deoloy.azure.production.yml b/_deploy/deoloy.azure.production.yml index 7f8bcdb..0236c1d 100644 --- a/_deploy/deoloy.azure.production.yml +++ b/_deploy/deoloy.azure.production.yml @@ -1,6 +1,6 @@ version: "3" services: - nextjs-app: # https://sofia.mwitnessing.com/ + nextjs-app: # https://sofia.mwhitnessing.com/ hostname: jwpw-app # jwpw-nextjs-app-1 image: docker.d-popov.com/jwpw:latest deploy: @@ -12,8 +12,8 @@ services: environment: - NODE_ENV=production - TZ=Europe/Sofia - - DATABASE=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia - #- DATABASE=postgres://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia + - DATABASE_URL=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia + #- DATABASE_URL=postgres://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia - UPDATE_CODE_FROM_GIT=true # Set to true to pull latest code from Git - GIT_BRANCH=production - GIT_USERNAME=deploy @@ -59,10 +59,6 @@ services: apk add --no-cache mariadb-client mariadb-connector-c && \ echo '0 2 * * * mysqldump -h $$MYSQL_HOST -P 3306 -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql' > /etc/crontabs/root && \ crond -f -d 8" - # wget -q https://github.com/prasmussen/gdrive/releases/download/2.1.0/gdrive-linux-x64 -O /usr/bin/gdrive && \ - # chmod +x /usr/bin/gdrive && \ - # gdrive about --service-account /root/.gdrive_service_account.json && \ - # echo '0 * * * * /usr/bin/mysqldump -h $$MYSQL_HOST -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE | gzip > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz && gdrive upload --parent $$GOOGLE_DRIVE_FOLDER_ID --service-account /root/.gdrive_service_account.json /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz' > /etc/crontabs/root && crond -f -d 8" networks: infrastructure_default: external: true diff --git a/package.json b/package.json index cedd7b8..370ffd0 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,13 @@ "scripts": { "debug": "node server.js", "debug-env": "dotenv -e .env.$APP_ENV -- nodemon --inspect server.js", + "nodeenv": "dotenv -e .env.$APP_ENV -- node server.js", + "prod": "npx next build && dotenv -e .env.production -- node server.js", "build": "next build", "buildWin": "npm run build", "start": "next start", "devNext": "next dev --port 3003 --experimental-https", - "test": "dotenv -e .env.$NODE_ENV -- nodemon --inspect server.js", - "nodeenv": "dotenv -e .env.$NODE_ENV -- node server.js", - "prod": "npx next build && dotenv -e .env.production -- node server.js" + "test": "dotenv -e .env.$NODE_ENV -- nodemon --inspect server.js" }, "author": "Dobromir Popov ", "_moduleAliases": { @@ -110,4 +110,4 @@ "depcheck": "^1.4.7", "prisma": "^5.11.0" } -} +} \ No newline at end of file From 0717433acbed4c1b3051f7ceec6c8d2913cc9441 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 6 Apr 2024 16:34:19 +0300 Subject: [PATCH 19/19] revert & tweak deployment settings --- _deploy/deoloy.azure.demo.yml | 2 +- _deploy/deoloy.azure.production.yml | 10 +++++++--- src/templates/emails/coverMeAccepted.hbs | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/_deploy/deoloy.azure.demo.yml b/_deploy/deoloy.azure.demo.yml index dcb1a7f..8dac626 100644 --- a/_deploy/deoloy.azure.demo.yml +++ b/_deploy/deoloy.azure.demo.yml @@ -9,7 +9,7 @@ services: - APP_ENV=test - NODE_ENV=test - TZ=Europe/Sofia - - DATABASE_URL=mysql://jwpwsofia_demo:dwxhns9p9vp248@mariadb-demo:3306/jwpwsofia_demo + - DATABASE=mysql://jwpwsofia_demo:dwxhns9p9vp248@mariadb-demo:3306/jwpwsofia_demo - UPDATE_CODE_FROM_GIT=true # Set to true to pull latest code from Git - GIT_BRANCH=main - GIT_USERNAME=deploy diff --git a/_deploy/deoloy.azure.production.yml b/_deploy/deoloy.azure.production.yml index 0236c1d..7f8bcdb 100644 --- a/_deploy/deoloy.azure.production.yml +++ b/_deploy/deoloy.azure.production.yml @@ -1,6 +1,6 @@ version: "3" services: - nextjs-app: # https://sofia.mwhitnessing.com/ + nextjs-app: # https://sofia.mwitnessing.com/ hostname: jwpw-app # jwpw-nextjs-app-1 image: docker.d-popov.com/jwpw:latest deploy: @@ -12,8 +12,8 @@ services: environment: - NODE_ENV=production - TZ=Europe/Sofia - - DATABASE_URL=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia - #- DATABASE_URL=postgres://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia + - DATABASE=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia + #- DATABASE=postgres://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia - UPDATE_CODE_FROM_GIT=true # Set to true to pull latest code from Git - GIT_BRANCH=production - GIT_USERNAME=deploy @@ -59,6 +59,10 @@ services: apk add --no-cache mariadb-client mariadb-connector-c && \ echo '0 2 * * * mysqldump -h $$MYSQL_HOST -P 3306 -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql' > /etc/crontabs/root && \ crond -f -d 8" + # wget -q https://github.com/prasmussen/gdrive/releases/download/2.1.0/gdrive-linux-x64 -O /usr/bin/gdrive && \ + # chmod +x /usr/bin/gdrive && \ + # gdrive about --service-account /root/.gdrive_service_account.json && \ + # echo '0 * * * * /usr/bin/mysqldump -h $$MYSQL_HOST -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE | gzip > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz && gdrive upload --parent $$GOOGLE_DRIVE_FOLDER_ID --service-account /root/.gdrive_service_account.json /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz' > /etc/crontabs/root && crond -f -d 8" networks: infrastructure_default: external: true diff --git a/src/templates/emails/coverMeAccepted.hbs b/src/templates/emails/coverMeAccepted.hbs index 62cbb6c..16bb198 100644 --- a/src/templates/emails/coverMeAccepted.hbs +++ b/src/templates/emails/coverMeAccepted.hbs @@ -3,7 +3,7 @@

Промяна твоята смяна на {{placeName}} {{dateStr}}

Здравейте {{firstName}},

-

{{firstName}} {{lastName}} ще замести {{oldPubName}} на смяната ви на {{dateStr}} от {{time}}

+

{{firstName}} {{lastName}} ще замести {{oldPubName}} на смяната ви в {{dateStr}} от {{time}}

Новаия списък с участници за тази смяна е:

    {{#each newPubs}} @@ -14,6 +14,6 @@
- --}} \ No newline at end of file