From b8622cc776403b5f6c19a5267911a0cba23b016b Mon Sep 17 00:00:00 2001 From: Tadej Vengust Date: Wed, 14 Feb 2024 15:43:28 +0100 Subject: [PATCH 01/36] deploy script --- .github/workflows/deploy.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..b2ffae3 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,34 @@ +name: Deploy Website + +on: + push: + branches: + - staging + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install dependencies + run: npm install + + - name: Build application + run: npm run generate + + - name: Deploy website + env: + APILLON_API_KEY: ${{ secrets.APILLON_API_KEY }} + APILLON_API_SECRET: ${{ secrets.APILLON_API_SECRET }} + WEBSITE_UUID: ${{ secrets.WEBSITE_UUID_STAGING }} + run: | + npm i -g @apillon/cli + apillon hosting deploy-website ./.output/public --uuid $WEBSITE_UUID --key $APILLON_API_KEY --secret $APILLON_API_SECRET From 4d3cd8f3151a02e6876fad9ae29a95b7992b3a68 Mon Sep 17 00:00:00 2001 From: Tadej Vengust Date: Wed, 14 Feb 2024 15:47:28 +0100 Subject: [PATCH 02/36] update workflow --- .github/workflows/deploy.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b2ffae3..8a1ab91 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,6 +5,10 @@ on: branches: - staging +defaults: + run: + working-directory: ./frontend + jobs: deploy: runs-on: ubuntu-latest From 581bcc889d1dc267f05a516897459d3fc447e5a4 Mon Sep 17 00:00:00 2001 From: Tine Mlakar Date: Thu, 15 Feb 2024 10:05:30 +0100 Subject: [PATCH 03/36] dockerization --- backend/bin/create-database.sh | 31 +++++++++ backend/bin/docker-start.sh | 14 ++++ backend/build-image-beta.sh | 4 ++ backend/build-image.sh | 4 ++ backend/docker-compose.beta.yml | 47 ++++++++++++++ backend/docker-compose.yml | 47 ++++++++++++++ backend/dockerfile | 49 ++++++++++++++ backend/package.json | 4 +- backend/pm2.config.js | 14 ++++ backend/src/cron.ts | 95 +++++++++++++++------------- backend/src/scripts/db/upgrade-db.ts | 39 ++++++++---- 11 files changed, 291 insertions(+), 57 deletions(-) create mode 100644 backend/bin/create-database.sh create mode 100644 backend/bin/docker-start.sh create mode 100755 backend/build-image-beta.sh create mode 100755 backend/build-image.sh create mode 100644 backend/docker-compose.beta.yml create mode 100644 backend/docker-compose.yml create mode 100644 backend/dockerfile create mode 100644 backend/pm2.config.js diff --git a/backend/bin/create-database.sh b/backend/bin/create-database.sh new file mode 100644 index 0000000..bcefb80 --- /dev/null +++ b/backend/bin/create-database.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +if [ "$#" -ne 4 ]; then + echo "Usage: $0 " + exit 1 +fi + +DATABASE_HOST=$1 +DATABASE_NAME=$2 +MYSQL_USER=$3 +MYSQL_PASSWORD=$4 + +echo "Connecting to host: $DATABASE_HOST" +ping -c 3 $DATABASE_HOST + +export MYSQL_PWD=$MYSQL_PASSWORD + +# Check if the database exists +DB_EXISTS=$(mysql -h $DATABASE_HOST -u $MYSQL_USER -e "SHOW DATABASES LIKE '$DATABASE_NAME';" | grep "$DATABASE_NAME") + +if [ "$DB_EXISTS" == "$DATABASE_NAME" ]; then + echo "Database $DATABASE_NAME already exists. Exiting." + exit 0 +fi + +# Create the database +mysql -h $DATABASE_HOST -u $MYSQL_USER -e "CREATE DATABASE $DATABASE_NAME;" + +unset MYSQL_PWD + +echo "Database $DATABASE_NAME created successfully." diff --git a/backend/bin/docker-start.sh b/backend/bin/docker-start.sh new file mode 100644 index 0000000..62c2f40 --- /dev/null +++ b/backend/bin/docker-start.sh @@ -0,0 +1,14 @@ +#!/bin/sh +printenv + +echo "Waiting 1m for DB image to start" +sleep 1m + +echo "Crating database..." +./bin/create-database.sh "$MYSQL_HOST" "$MYSQL_DB" "$MYSQL_USER" "$MYSQL_PASSWORD" + +# migrate DB +echo "Starting migration!" +npm run db-upgrade:ci + +pm2 start pm2.config.js --attach diff --git a/backend/build-image-beta.sh b/backend/build-image-beta.sh new file mode 100755 index 0000000..e2a7ae8 --- /dev/null +++ b/backend/build-image-beta.sh @@ -0,0 +1,4 @@ +aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/i0e4n2k8 +docker build -t ment-airdrop:beta.0 . +docker tag ment-airdrop:beta.0 public.ecr.aws/i0e4n2k8/ment-airdrop:beta.0 +docker push public.ecr.aws/i0e4n2k8/ment-airdrop:beta.0 \ No newline at end of file diff --git a/backend/build-image.sh b/backend/build-image.sh new file mode 100755 index 0000000..e9dd9d6 --- /dev/null +++ b/backend/build-image.sh @@ -0,0 +1,4 @@ +aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/i0e4n2k8 +docker build -t ment-airdrop:latest . +docker tag ment-airdrop:latest public.ecr.aws/i0e4n2k8/ment-airdrop:latest +# docker push public.ecr.aws/i0e4n2k8/ment-airdrop:latest \ No newline at end of file diff --git a/backend/docker-compose.beta.yml b/backend/docker-compose.beta.yml new file mode 100644 index 0000000..b95068e --- /dev/null +++ b/backend/docker-compose.beta.yml @@ -0,0 +1,47 @@ +version: '3.8' + +services: + airdrop_db: + image: mysql + container_name: airdrop_db + env_file: + - .env.sql.deploy + # environment: + # MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + # MYSQL_DATABASE: ${MYSQL_DATABASE} + ports: + - '3306:3306' + restart: always + volumes: + - mysql-data:/var/lib/mysql + + airdrop_app: + image: public.ecr.aws/i0e4n2k8/ment-airdrop:beta.0 + container_name: airdrop_app + depends_on: + - airdrop_db + env_file: + - .env.deploy + # environment: + # APP_SECRET: ${APP_SECRET} + # APP_URL: ${APP_URL} + # ADMIN_WALLET: ${ADMIN_WALLET} + # APILLON_KEY: ${APILLON_KEY} + # APILLON_SECRET: ${APILLON_SECRET} + # COLLECTION_UUID: ${COLLECTION_UUID} + # SMTP_HOST: ${SMTP_HOST} + # SMTP_PORT: ${SMTP_PORT} + # SMTP_USERNAME: ${SMTP_USERNAME} + # SMTP_PASSWORD: ${SMTP_PASSWORD} + # SMTP_EMAIL_FROM: ${SMTP_EMAIL_FROM} + # SMTP_NAME_FROM: ${SMTP_NAME_FROM} + # API_HOST: ${API_HOST} + # API_PORT: ${API_PORT} + # CAPTCHA_SECRET: ${CAPTCHA_SECRET} + # CLAIM_EXPIRES_IN: ${CLAIM_EXPIRES_IN} + ports: + - '8080:${API_PORT}' + restart: always + +volumes: + mysql-data: diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 0000000..d5b896c --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,47 @@ +version: '3.8' + +services: + airdrop_db: + image: mysql + container_name: airdrop_db + env_file: + - .env.sql.deploy + # environment: + # MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + # MYSQL_DATABASE: ${MYSQL_DATABASE} + ports: + - '3306:3306' + restart: always + volumes: + - mysql-data:/var/lib/mysql + + airdrop_app: + image: public.ecr.aws/i0e4n2k8/ment-airdrop:latest + container_name: airdrop_app + depends_on: + - airdrop_db + env_file: + - .env.deploy + # environment: + # APP_SECRET: ${APP_SECRET} + # APP_URL: ${APP_URL} + # ADMIN_WALLET: ${ADMIN_WALLET} + # APILLON_KEY: ${APILLON_KEY} + # APILLON_SECRET: ${APILLON_SECRET} + # COLLECTION_UUID: ${COLLECTION_UUID} + # SMTP_HOST: ${SMTP_HOST} + # SMTP_PORT: ${SMTP_PORT} + # SMTP_USERNAME: ${SMTP_USERNAME} + # SMTP_PASSWORD: ${SMTP_PASSWORD} + # SMTP_EMAIL_FROM: ${SMTP_EMAIL_FROM} + # SMTP_NAME_FROM: ${SMTP_NAME_FROM} + # API_HOST: ${API_HOST} + # API_PORT: ${API_PORT} + # CAPTCHA_SECRET: ${CAPTCHA_SECRET} + # CLAIM_EXPIRES_IN: ${CLAIM_EXPIRES_IN} + ports: + - '8080:${API_PORT}' + restart: always + +volumes: + mysql-data: diff --git a/backend/dockerfile b/backend/dockerfile new file mode 100644 index 0000000..037f1e7 --- /dev/null +++ b/backend/dockerfile @@ -0,0 +1,49 @@ +FROM node:20-alpine + +ENV APP_ENV=production +ARG APP_SECRET +#should be overriden +ENV APP_URL=http://localhost:3000 +ENV API_PORT=3000 + +ENV MYSQL_HOST=airdrop_db +ENV MYSQL_PORT=3306 +ENV MYSQL_DB=airdrop +ENV MYSQL_USER=root +ARG MYSQL_PASSWORD +ENV MYSQL_POOL=5 + +ARG ADMIN_WALLET +ARG APILLON_KEY +ARG APILLON_SECRET +ARG COLLECTION_UUID + +ARG SMTP_HOST +ARG SMTP_PORT +ARG SMTP_USERNAME +ARG SMTP_PASSWORD +ARG SMTP_EMAIL_FROM +ARG SMTP_NAME_FROM + +RUN echo $MYSQL_HOST + +ENV appDir /app +RUN mkdir -p /app + +WORKDIR ${appDir} + +# Install MySQL client +RUN apk --no-cache add mysql-client + +RUN npm install -g typescript pm2@latest + +ADD ./package-lock.json ${appDir} +ADD ./package.json ${appDir} +ADD ./ ${appDir}/ + +RUN npm install +RUN npm run build + +EXPOSE 3000 +RUN chmod +x ./bin/docker-start.sh ./bin/create-database.sh +CMD ["./bin/docker-start.sh"] \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 55d3292..32abc08 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,7 +9,9 @@ "cron": "node -r ts-node/register --inspect ./src/scripts/start-cron", "dev": "npm run development:run", "tsc": "tsc", + "build": "tsc", "db-upgrade": "node -r ts-node/register ./src/scripts/db/upgrade-db", + "db-upgrade:ci": "node -r ts-node/register ./src/scripts/db/upgrade-db --F", "db-downgrade": "node -r ts-node/register ./src/scripts/db/downgrade-db", "db-rebuild": "node -r ts-node/register ./src/scripts/db/rebuild-db", "db-drop": "node -r ts-node/register ./src/scripts/db/drop-db" @@ -50,4 +52,4 @@ }, "author": "Apillon", "license": "ISC" -} +} \ No newline at end of file diff --git a/backend/pm2.config.js b/backend/pm2.config.js new file mode 100644 index 0000000..daaa169 --- /dev/null +++ b/backend/pm2.config.js @@ -0,0 +1,14 @@ +module.exports = { + apps: [ + { + name: 'http-server', + script: './dist/scripts/start-http.js', + restart_delay: 3000, + }, + { + name: 'cron', + script: './dist/scripts/start-cron.js', + restart_delay: 3000, + }, + ], +}; diff --git a/backend/src/cron.ts b/backend/src/cron.ts index 666b067..69705a0 100644 --- a/backend/src/cron.ts +++ b/backend/src/cron.ts @@ -1,22 +1,22 @@ -import { CronJob } from "cron"; -import { AirdropStatus } from "./models/user"; -import { dateToSqlString } from "./lib/sql-utils"; -import { SqlModelStatus } from "./models/base-sql-model"; -import { MysqlConnectionManager } from "./lib/mysql-connection-manager"; -import { SmtpSendTemplate } from "./lib/node-mailer"; -import { env } from "./config/env"; -import { generateEmailAirdropToken } from "./lib/jwt"; -import { LogType, writeLog } from "./lib/logger"; -import { LogLevel, Nft } from "@apillon/sdk"; +import { CronJob } from 'cron'; +import { AirdropStatus } from './models/user'; +import { dateToSqlString } from './lib/sql-utils'; +import { SqlModelStatus } from './models/base-sql-model'; +import { MysqlConnectionManager } from './lib/mysql-connection-manager'; +import { SmtpSendTemplate } from './lib/node-mailer'; +import { env } from './config/env'; +import { generateEmailAirdropToken } from './lib/jwt'; +import { LogType, writeLog } from './lib/logger'; +import { LogLevel, Nft } from '@apillon/sdk'; export class Cron { private cronJobs: CronJob[] = []; constructor() { - this.cronJobs.push(new CronJob("* * * * *", this.sendEmail, null, false)); + this.cronJobs.push(new CronJob('* * * * *', this.sendEmail, null, false)); if (env.MAX_SUPPLY > 0) { this.cronJobs.push( - new CronJob("* * * * *", this.processExpiredClaims, null, false) + new CronJob('* * * * *', this.processExpiredClaims, null, false) ); } } @@ -56,19 +56,20 @@ export class Cron { } const conn = await mysql.start(); - await conn.beginTransaction(); try { - const res = await conn.execute( + const users = await mysql.paramExecute( `SELECT * FROM user WHERE airdrop_status = ${AirdropStatus.PENDING} AND status = ${SqlModelStatus.ACTIVE} AND email_start_send_time < '${dateToSqlString(new Date())}' FOR UPDATE ; - ` + `, + null, + conn ); - const users = res[0] as Array; + const updates = []; for (let i = 0; i < users.length; i++) { @@ -77,8 +78,8 @@ export class Cron { const token = await generateEmailAirdropToken(users[i].email); await SmtpSendTemplate( [users[i].email], - "Claim your NFT", - "en-airdrop-claim", + 'Claim your NFT', + 'en-airdrop-claim', { link: `${env.APP_URL}/claim?token=${token}`, } @@ -92,8 +93,8 @@ export class Cron { //Currently, waiting line for airdrop is full.Send info email and set appropriate status await SmtpSendTemplate( [users[i].email], - "You are in waiting line for NFT claim", - "en-airdrop-waiting-line", + 'You are in waiting line for NFT claim', + 'en-airdrop-waiting-line', {} ); updates.push( @@ -103,7 +104,7 @@ export class Cron { ); } } catch (e) { - writeLog(LogType.ERROR, e, "cron.ts", "sendEmail"); + writeLog(LogType.ERROR, e, 'cron.ts', 'sendEmail'); updates.push( `(${users[i].id}, '${users[i].email}', ${ AirdropStatus.EMAIL_ERROR @@ -115,76 +116,84 @@ export class Cron { if (updates.length > 0) { const sql = ` INSERT INTO user (id, email, airdrop_status, email_sent_time) - VALUES ${updates.join(",")} + VALUES ${updates.join(',')} ON DUPLICATE KEY UPDATE airdrop_status = VALUES(airdrop_status), email_sent_time = VALUES(email_sent_time)`; - await conn.execute(sql); + await mysql.paramExecute(sql, null, conn); } - await conn.commit(); + await mysql.commit(conn); } catch (e) { - writeLog(LogType.ERROR, e, "cron.ts", "sendEmail"); - await conn.rollback(); + writeLog(LogType.ERROR, e, 'cron.ts', 'sendEmail'); + await mysql.rollback(conn); } } async processExpiredClaims() { const mysql = await MysqlConnectionManager.getInstance(); const conn = await mysql.start(); - await conn.beginTransaction(); try { - const res = await conn.execute( - `SELECT * FROM user WHERE + const usersWithExpiredClaim = ( + await mysql.paramExecute( + `SELECT * FROM user WHERE airdrop_status = ${AirdropStatus.EMAIL_SENT} AND status = ${SqlModelStatus.ACTIVE} AND DATE_ADD(email_sent_time, INTERVAL ${env.CLAIM_EXPIRES_IN} HOUR) < NOW() FOR UPDATE ; - ` - ); - const usersWithExpiredClaim = (res[0] as Array).map((x) => x.id); + `, + null, + conn + ) + ).map((x) => x.id); if (usersWithExpiredClaim.length) { //Update those users to claim expired - await conn.execute( + await mysql.paramExecute( `UPDATE user SET airdrop_status = ${AirdropStatus.AIRDROP_CLAIM_EXPIRED} - WHERE id IN (${usersWithExpiredClaim.join(",")}) + WHERE id IN (${usersWithExpiredClaim.join(',')}) ; - ` + `, + null, + conn ); //Get users in waiting line and set their airdrop status to PENDING, so that they will recieve email for claim const usersInWaitingLine = ( - await conn.execute( + await mysql.paramExecute( `SELECT * FROM user WHERE airdrop_status = ${AirdropStatus.IN_WAITING_LINE} AND status = ${SqlModelStatus.ACTIVE} LIMIT ${usersWithExpiredClaim.length} FOR UPDATE ; - ` + `, + null, + conn ) )[0] as Array; if (usersInWaitingLine.length) { - await conn.query( + await mysql.paramExecute( `UPDATE user SET airdrop_status = ${AirdropStatus.PENDING} - WHERE id IN (${usersInWaitingLine.map((x) => x.id).join(",")}) + WHERE id IN (${usersInWaitingLine.map((x) => x.id).join(',')}) ; - ` + `, + null, + conn ); } } - await conn.commit(); + await mysql.commit(conn); } catch (e) { - writeLog(LogType.ERROR, e, "cron.ts", "processExpiredClaims"); - await conn.rollback(); + writeLog(LogType.ERROR, e, 'cron.ts', 'processExpiredClaims'); + await mysql.rollback(conn); } } } diff --git a/backend/src/scripts/db/upgrade-db.ts b/backend/src/scripts/db/upgrade-db.ts index 2b960c0..fe3bab7 100644 --- a/backend/src/scripts/db/upgrade-db.ts +++ b/backend/src/scripts/db/upgrade-db.ts @@ -5,19 +5,18 @@ import { bgYellow, black } from 'colors/safe'; const rl = readline.createInterface({ input: process.stdin, - output: process.stdout + output: process.stdout, }); let steps = 0; +const showDialog = !process.argv.includes('--F'); + const run = async () => { await upgradeDatabase(steps); }; -rl.question(`You are about to upgrade database ${bgYellow(black(` ${env.MYSQL_DB} @ ${env.MYSQL_HOST} `))}. - -Set number of versions to upgrade ('Y' for all, '' for number of versions, 'N' to exit):`, (answer) => { - +const executeFn = (answer) => { steps = parseInt(answer); if (answer.toUpperCase() === 'Y') { steps = 0; @@ -31,12 +30,26 @@ Set number of versions to upgrade ('Y' for all, '' for number of version rl.close(); - run().then(() => { - console.log('Complete!'); - process.exit(0); - }).catch((err) => { - console.log(err); - process.exit(1); - }); + run() + .then(() => { + console.log('Complete!'); + process.exit(0); + }) + .catch((err) => { + console.log(err); + process.exit(1); + }); +}; -}); +if (showDialog) { + rl.question( + `You are about to upgrade database ${bgYellow( + black(` ${env.MYSQL_DB} @ ${env.MYSQL_HOST} `) + )}. + +Set number of versions to upgrade ('Y' for all, '' for number of versions, 'N' to exit):`, + (answer) => executeFn(answer) + ); +} else { + executeFn('Y'); +} From 4d6f75b07eda2fdf3a1cd2523ef86090e2e0eb99 Mon Sep 17 00:00:00 2001 From: Tadej Vengust Date: Thu, 15 Feb 2024 11:09:29 +0100 Subject: [PATCH 04/36] production config --- frontend/lib/config/production.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/lib/config/production.ts b/frontend/lib/config/production.ts index 640b73a..4968cc9 100644 --- a/frontend/lib/config/production.ts +++ b/frontend/lib/config/production.ts @@ -2,9 +2,9 @@ import { ConfigInterface } from '~/lib/types/general.types'; const config: ConfigInterface = { APP_URL: 'https://app.apillon.io', - API_BASE: 'https://api.apillon.io', + API_BASE: 'https://ment-api.apillon.io', CAPTCHA_KEY: 'f363ce6d-7543-4284-9caa-cf3219723f04', - CHAIN_ID: 1284, + CHAIN_ID: 1287, }; export default config; From 1792baa72f8892c645f240a51eac97a9c2d909fa Mon Sep 17 00:00:00 2001 From: ZanJereb Date: Thu, 15 Feb 2024 12:09:58 +0100 Subject: [PATCH 05/36] Created modal for Terms and conditions, privacy policy --- .../components/parts/TermsAndConditions.vue | 52 +++++++++++++++++++ frontend/components/parts/form/SighUp.vue | 22 +++++++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 frontend/components/parts/TermsAndConditions.vue diff --git a/frontend/components/parts/TermsAndConditions.vue b/frontend/components/parts/TermsAndConditions.vue new file mode 100644 index 0000000..4bd188a --- /dev/null +++ b/frontend/components/parts/TermsAndConditions.vue @@ -0,0 +1,52 @@ + + + diff --git a/frontend/components/parts/form/SighUp.vue b/frontend/components/parts/form/SighUp.vue index 5516c5c..319f981 100644 --- a/frontend/components/parts/form/SighUp.vue +++ b/frontend/components/parts/form/SighUp.vue @@ -6,11 +6,14 @@ import { ruleRequired } from '~/lib/utils/validation'; type SignupForm = { email: string | null; + termsAndConditions: boolean; token?: any; }; const router = useRouter(); +const modalTermsAndConditionsVisible = ref(false); + const message = useMessage(); const emit = defineEmits(['submitSuccess']); @@ -28,6 +31,7 @@ const { handleError } = useErrors(); const formRef = ref(null); const formData = ref({ email: null, + termsAndConditions: false, token: null as any, }); @@ -89,6 +93,14 @@ function onCaptchaVerify(token: string) { /> +
+
+ +
+ +

I have read and agree to Terms and Conditions and Privacy Policy.

+
+ Sign up + + + + From 7d146ad369479c2207e93d096fecc00c115155ea Mon Sep 17 00:00:00 2001 From: ZanJereb Date: Thu, 15 Feb 2024 12:14:37 +0100 Subject: [PATCH 06/36] Fixes --- frontend/components/parts/TermsAndConditions.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/parts/TermsAndConditions.vue b/frontend/components/parts/TermsAndConditions.vue index 4bd188a..e08f834 100644 --- a/frontend/components/parts/TermsAndConditions.vue +++ b/frontend/components/parts/TermsAndConditions.vue @@ -1,6 +1,6 @@ From 441cf6e837c70d3b0c2d222107835ae8bb02dd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Urban=20Kova=C4=8D?= Date: Fri, 16 Feb 2024 08:57:58 +0100 Subject: [PATCH 16/36] FE (import type fixes) --- frontend/components/parts/Statistics.vue | 2 +- frontend/components/parts/form/Upload.vue | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/components/parts/Statistics.vue b/frontend/components/parts/Statistics.vue index a55ed1b..0ddfb05 100644 --- a/frontend/components/parts/Statistics.vue +++ b/frontend/components/parts/Statistics.vue @@ -21,7 +21,7 @@ diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index cc4b571..ead97d9 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -24,7 +24,7 @@ const containerStyle = computed(() => { const hHeight = headerRef.value?.clientHeight || 0; const fHeight = footerRef.value?.clientHeight || 0; return { - minHeight: `calc(100dvh - ${hHeight + fHeight}px)`, + minHeight: `calc(100dvh - ${hHeight + fHeight + 100}px)`, }; }); diff --git a/frontend/pages/admin.vue b/frontend/pages/admin.vue index 7b16fdc..b521594 100644 --- a/frontend/pages/admin.vue +++ b/frontend/pages/admin.vue @@ -1,5 +1,6 @@ diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue index cc4b571..d19f3d2 100644 --- a/frontend/layouts/default.vue +++ b/frontend/layouts/default.vue @@ -4,7 +4,7 @@
@@ -16,15 +16,30 @@ diff --git a/frontend/lib/config/naive.ts b/frontend/lib/config/naive.ts index 9224dd0..5429261 100644 --- a/frontend/lib/config/naive.ts +++ b/frontend/lib/config/naive.ts @@ -164,7 +164,7 @@ export const NaiveTheme: GlobalThemeOverrides = { borderColor: colors.bg.lighter, tdColor: colors.bg.DEFAULT, tdColorHover: colors.bg.dark, - tdTextColor: colors.body, + tdTextColor: colors.white, thColor: colors.bg.DEFAULT, thColorHover: colors.bg.DEFAULT, thFontWeight: '700', diff --git a/frontend/lib/types/general.types.ts b/frontend/lib/types/general.types.ts index 4149b76..5281759 100644 --- a/frontend/lib/types/general.types.ts +++ b/frontend/lib/types/general.types.ts @@ -1,4 +1,4 @@ -export {} +export {}; declare global { interface ConfigInterface { @@ -6,9 +6,9 @@ declare global { API_BASE: string; CHAIN_ID: number; CAPTCHA_KEY: string; - } + } - type AuthResponseProfile = { + type AuthResponseProfile = { id: number; authUser: { id: number; @@ -20,17 +20,17 @@ declare global { }; }; - type AuthResponse = { + type AuthResponse = { profile: AuthResponseProfile; authToken: { status: boolean; data: string; }; }; - + type CsvItem = { email: string; - email_start_send_time: string; + email_start_send_time: string | null; email_send_time?: string | null; tx_hash?: string | null; wallet: string; diff --git a/frontend/pages/admin.vue b/frontend/pages/admin.vue index 7b16fdc..f1f350c 100644 --- a/frontend/pages/admin.vue +++ b/frontend/pages/admin.vue @@ -51,7 +51,7 @@ function onFileUploaded(csvData: CsvItem[]) { airdrop_status: AirdropStatus.PENDING, email: item.email, email_sent_time: null, - email_start_send_time: item.email_start_send_time, + email_start_send_time: null, wallet: null, } as UserInterface; }); @@ -172,14 +172,12 @@ function checkUnfinishedRecipients() {

NFT Recipient Stock

- - - Refresh - + + Refresh Upload CSV Add recipient diff --git a/frontend/pages/claim.vue b/frontend/pages/claim.vue index 051b324..36d5542 100644 --- a/frontend/pages/claim.vue +++ b/frontend/pages/claim.vue @@ -2,9 +2,7 @@ type Address = `0x${string}`; import SuccessSVG from '~/assets/images/success.svg'; import colors from '~/tailwind.colors'; -import { useAccount, useConnect, useContractRead, useWalletClient } from 'use-wagmi'; -import { abi } from '~/lib/config/abi'; -import { getContractAddress } from 'viem'; +import { useAccount, useConnect, useWalletClient } from 'use-wagmi'; definePageMeta({ layout: 'claim', diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue index 5defc3a..2998a50 100644 --- a/frontend/pages/index.vue +++ b/frontend/pages/index.vue @@ -7,7 +7,7 @@ useHead({ diff --git a/frontend/public/files/example.csv b/frontend/public/files/example.csv new file mode 100644 index 0000000..b0efd41 --- /dev/null +++ b/frontend/public/files/example.csv @@ -0,0 +1,3 @@ +email +user@mail.com +user2@mail.com \ No newline at end of file From 1fc3137d7a1ce2b8f67b584c8fa53fb2b8926dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinko=20=C5=A0mid?= Date: Fri, 16 Feb 2024 10:58:39 +0100 Subject: [PATCH 19/36] changed logic in cron - when user is removed from waiting line, email for claim is directly sent --- backend/src/cron.ts | 43 +++++++++++++++++++++++++++++++++++--- backend/src/models/user.ts | 2 ++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/backend/src/cron.ts b/backend/src/cron.ts index dc341ab..80caa16 100644 --- a/backend/src/cron.ts +++ b/backend/src/cron.ts @@ -44,7 +44,8 @@ export class Cron { ${AirdropStatus.EMAIL_SENT}, ${AirdropStatus.WALLET_LINKED}, ${AirdropStatus.TRANSACTION_CREATED}, - ${AirdropStatus.AIRDROP_COMPLETED} + ${AirdropStatus.AIRDROP_COMPLETED}, + ${AirdropStatus.IN_WAITING_LINE} ) AND status = ${SqlModelStatus.ACTIVE} ; @@ -153,6 +154,10 @@ export class Cron { ; ` ); + console.info( + usersWithExpiredClaim.length + + " users updated to AIRDROP_CLAIM_EXPIRED" + ); //Get users in waiting line and set their airdrop status to PENDING, so that they will recieve email for claim const usersInWaitingLine = ( @@ -168,14 +173,46 @@ export class Cron { ) )[0] as Array; + console.info( + "Num of users in waiting line: " + usersInWaitingLine.length + ); + if (usersInWaitingLine.length) { - await conn.query( + await conn.execute( `UPDATE user - SET airdrop_status = ${AirdropStatus.PENDING} + SET + airdrop_status = ${AirdropStatus.EMAIL_SENT}, + email_sent_time = NOW() WHERE id IN (${usersInWaitingLine.map((x) => x.id).join(",")}) ; ` ); + console.info( + usersInWaitingLine.map((x) => x.id).join(",") + + " should me moved from waiting line. Sending emails...." + ); + + for (const user of usersInWaitingLine) { + try { + const token = await generateEmailAirdropToken(user.email); + await SmtpSendTemplate( + [user.email], + "Claim your NFT", + "en-airdrop-claim", + { + link: `${env.APP_URL}/claim?token=${token}`, + } + ); + } catch (err) { + await conn.execute( + `UPDATE user + SET airdrop_status = ${AirdropStatus.EMAIL_ERROR}, + WHERE id = ${user.id}) + ; + ` + ); + } + } } } diff --git a/backend/src/models/user.ts b/backend/src/models/user.ts index d0a26d2..dceb74f 100644 --- a/backend/src/models/user.ts +++ b/backend/src/models/user.ts @@ -12,6 +12,7 @@ import { dateParser, integerParser, stringParser } from "@rawmodel/parsers"; import { Context } from "../context"; import { ResourceError, SqlError } from "../lib/errors"; import { getQueryParams, selectAndCountQuery } from "../lib/sql-utils"; +import { env } from "../config/env"; export enum AirdropStatus { PENDING = 1, @@ -201,6 +202,7 @@ export class User extends BaseSqlModel { ` SELECT count(*) as total, + ${env.MAX_SUPPLY} as maxSupply, SUM(IF(airdrop_status = 1, 1, 0)) as pending, SUM(IF(airdrop_status in (2,4,5,6,7), 1, 0)) as emailSent, SUM(IF(airdrop_status in (4,5,6,7), 1, 0)) as walletLinked, From ada13c1f7a285926bfba554efcf75ca10b837e8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:03:50 +0000 Subject: [PATCH 20/36] Bump nodemailer from 6.9.7 to 6.9.9 in /backend Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.7 to 6.9.9. - [Release notes](https://github.com/nodemailer/nodemailer/releases) - [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.7...v6.9.9) --- updated-dependencies: - dependency-name: nodemailer dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- backend/package-lock.json | 8 ++++---- backend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 0cbd99f..d5b9091 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -24,7 +24,7 @@ "jsonwebtoken": "^9.0.2", "moment": "^2.29.4", "mysql2": "^3.6.5", - "nodemailer": "^6.9.7", + "nodemailer": "^6.9.9", "reflect-metadata": "^0.1.13", "sqlstring": "^2.3.3", "ts-node": "^10.9.1" @@ -4960,9 +4960,9 @@ "dev": true }, "node_modules/nodemailer": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz", - "integrity": "sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==", + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz", + "integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==", "engines": { "node": ">=6.0.0" } diff --git a/backend/package.json b/backend/package.json index 32abc08..4f070bd 100644 --- a/backend/package.json +++ b/backend/package.json @@ -32,7 +32,7 @@ "jsonwebtoken": "^9.0.2", "moment": "^2.29.4", "mysql2": "^3.6.5", - "nodemailer": "^6.9.7", + "nodemailer": "^6.9.9", "reflect-metadata": "^0.1.13", "sqlstring": "^2.3.3", "ts-node": "^10.9.1" From bd5255218469474b3b9de7ae6d038a7beb6a1f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Urban=20Kova=C4=8D?= Date: Fri, 16 Feb 2024 11:42:43 +0100 Subject: [PATCH 21/36] MENT (new copy) --- frontend/components/parts/Table/Users.vue | 2 +- frontend/components/parts/form/Upload.vue | 2 +- frontend/pages/claim.vue | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/components/parts/Table/Users.vue b/frontend/components/parts/Table/Users.vue index d70ce7d..3844eed 100644 --- a/frontend/components/parts/Table/Users.vue +++ b/frontend/components/parts/Table/Users.vue @@ -109,7 +109,7 @@ const columns = createColumns(); function addItem(user: UserInterface) { if (!validateEmail(newUser.value.email)) { - message.warning('Please enter a valid email address'); + message.warning('Please enter a valid email address.'); return; } diff --git a/frontend/components/parts/form/Upload.vue b/frontend/components/parts/form/Upload.vue index 5fe47ba..a60c120 100644 --- a/frontend/components/parts/form/Upload.vue +++ b/frontend/components/parts/form/Upload.vue @@ -63,7 +63,7 @@ const hasRequiredColumns = computed(() => function uploadFileRequest({ file, onError, onFinish }: UploadCustomRequestOptions) { if (file.type !== 'text/csv' && file.type !== 'application/vnd.ms-excel') { console.warn(file.type); - message.warning('File must be of type CSV'); + message.warning('File must be of CSV type.'); /** Mark file as failed */ onError(); diff --git a/frontend/pages/claim.vue b/frontend/pages/claim.vue index 0b82aae..b74244b 100644 --- a/frontend/pages/claim.vue +++ b/frontend/pages/claim.vue @@ -40,7 +40,7 @@ async function claimAirdrop() { await connect({ connector: connectors.value[0] }); if (!walletClient.value) { - message.error('Could not connect with wallet'); + message.error('Could not connect with your wallet.'); loading.value = false; return; } @@ -57,11 +57,11 @@ async function claimAirdrop() { txWait.hash.value = res.data.transactionHash as Address; console.debug('Transaction', txWait.hash.value); - message.info('Minting of your NFT has begun.'); + message.info('Minting of your MENT token has begun.'); const receipt = await txWait.wait(); console.debug(receipt); - message.success('You successfully claimed NFT'); + message.success("You've successfully claimed your MENT token."); if (receipt.data?.to && receipt.data?.logs[0].topics[3]) { const nftId = Number(receipt.data?.logs[0].topics[3]); From 931626c13a18e13b799a99df4a466dc7c6e37e10 Mon Sep 17 00:00:00 2001 From: Tine Mlakar Date: Fri, 16 Feb 2024 12:28:31 +0100 Subject: [PATCH 22/36] cron fix --- backend/src/cron.ts | 62 +++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/backend/src/cron.ts b/backend/src/cron.ts index 1dc8077..c19d99f 100644 --- a/backend/src/cron.ts +++ b/backend/src/cron.ts @@ -1,21 +1,21 @@ -import { CronJob } from "cron"; -import { AirdropStatus } from "./models/user"; -import { dateToSqlString } from "./lib/sql-utils"; -import { SqlModelStatus } from "./models/base-sql-model"; -import { MysqlConnectionManager } from "./lib/mysql-connection-manager"; -import { SmtpSendTemplate } from "./lib/node-mailer"; -import { env } from "./config/env"; -import { generateEmailAirdropToken } from "./lib/jwt"; -import { LogType, writeLog } from "./lib/logger"; +import { CronJob } from 'cron'; +import { AirdropStatus } from './models/user'; +import { dateToSqlString } from './lib/sql-utils'; +import { SqlModelStatus } from './models/base-sql-model'; +import { MysqlConnectionManager } from './lib/mysql-connection-manager'; +import { SmtpSendTemplate } from './lib/node-mailer'; +import { env } from './config/env'; +import { generateEmailAirdropToken } from './lib/jwt'; +import { LogType, writeLog } from './lib/logger'; export class Cron { private cronJobs: CronJob[] = []; constructor() { - this.cronJobs.push(new CronJob("* * * * *", this.sendEmail, null, false)); + this.cronJobs.push(new CronJob('* * * * *', this.sendEmail, null, false)); if (env.MAX_SUPPLY > 0) { this.cronJobs.push( - new CronJob("* * * * *", this.processExpiredClaims, null, false) + new CronJob('* * * * *', this.processExpiredClaims, null, false) ); } } @@ -78,8 +78,8 @@ export class Cron { const token = await generateEmailAirdropToken(users[i].email); await SmtpSendTemplate( [users[i].email], - "Claim your NFT", - "en-airdrop-claim", + 'Claim your NFT', + 'en-airdrop-claim', { appUrl: env.APP_URL, link: `${env.APP_URL}/claim?token=${token}`, @@ -95,8 +95,8 @@ export class Cron { //Currently, waiting line for airdrop is full.Send info email and set appropriate status await SmtpSendTemplate( [users[i].email], - "You are in waiting line for NFT claim", - "en-airdrop-waiting-line", + 'You are in waiting line for NFT claim', + 'en-airdrop-waiting-line', { appUrl: env.APP_URL, } @@ -108,7 +108,7 @@ export class Cron { ); } } catch (e) { - writeLog(LogType.ERROR, e, "cron.ts", "sendEmail"); + writeLog(LogType.ERROR, e, 'cron.ts', 'sendEmail'); updates.push( `(${users[i].id}, '${users[i].email}', ${ AirdropStatus.EMAIL_ERROR @@ -120,7 +120,7 @@ export class Cron { if (updates.length > 0) { const sql = ` INSERT INTO user (id, email, airdrop_status, email_sent_time) - VALUES ${updates.join(",")} + VALUES ${updates.join(',')} ON DUPLICATE KEY UPDATE airdrop_status = VALUES(airdrop_status), email_sent_time = VALUES(email_sent_time)`; @@ -130,7 +130,7 @@ export class Cron { await mysql.commit(conn); } catch (e) { - writeLog(LogType.ERROR, e, "cron.ts", "sendEmail"); + writeLog(LogType.ERROR, e, 'cron.ts', 'sendEmail'); await mysql.rollback(conn); } } @@ -159,7 +159,7 @@ export class Cron { await mysql.paramExecute( `UPDATE user SET airdrop_status = ${AirdropStatus.AIRDROP_CLAIM_EXPIRED} - WHERE id IN (${usersWithExpiredClaim.join(",")}) + WHERE id IN (${usersWithExpiredClaim.join(',')}) ; `, null, @@ -167,7 +167,7 @@ export class Cron { ); console.info( usersWithExpiredClaim.length + - " users updated to AIRDROP_CLAIM_EXPIRED" + ' users updated to AIRDROP_CLAIM_EXPIRED' ); //Get users in waiting line and set their airdrop status to PENDING, so that they will recieve email for claim @@ -187,24 +187,24 @@ export class Cron { )[0] as Array; console.info( - "Num of users in waiting line: " + usersInWaitingLine.length + 'Num of users in waiting line: ' + usersInWaitingLine.length ); if (usersInWaitingLine.length) { - await conn.execute( + await mysql.paramExecute( `UPDATE user SET airdrop_status = ${AirdropStatus.EMAIL_SENT}, email_sent_time = NOW() - WHERE id IN (${usersInWaitingLine.map((x) => x.id).join(",")}) + WHERE id IN (${usersInWaitingLine.map((x) => x.id).join(',')}) ; `, null, conn ); console.info( - usersInWaitingLine.map((x) => x.id).join(",") + - " should me moved from waiting line. Sending emails...." + usersInWaitingLine.map((x) => x.id).join(',') + + ' should me moved from waiting line. Sending emails....' ); for (const user of usersInWaitingLine) { @@ -212,19 +212,21 @@ export class Cron { const token = await generateEmailAirdropToken(user.email); await SmtpSendTemplate( [user.email], - "Claim your NFT", - "en-airdrop-claim", + 'Claim your NFT', + 'en-airdrop-claim', { link: `${env.APP_URL}/claim?token=${token}`, } ); } catch (err) { - await conn.execute( + await mysql.paramExecute( `UPDATE user SET airdrop_status = ${AirdropStatus.EMAIL_ERROR}, WHERE id = ${user.id}) ; - ` + `, + null, + conn ); } } @@ -233,7 +235,7 @@ export class Cron { await mysql.commit(conn); } catch (e) { - writeLog(LogType.ERROR, e, "cron.ts", "processExpiredClaims"); + writeLog(LogType.ERROR, e, 'cron.ts', 'processExpiredClaims'); await mysql.rollback(conn); } } From 02892451da0d8e5a441dc0bc6e07e9e75c58f706 Mon Sep 17 00:00:00 2001 From: ZanJereb Date: Fri, 16 Feb 2024 13:01:22 +0100 Subject: [PATCH 23/36] Changed texts --- frontend/components/parts/ConnectWallet.vue | 2 +- frontend/pages/claim.vue | 4 ++-- frontend/pages/index.vue | 2 +- frontend/pages/success.vue | 9 +++++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/components/parts/ConnectWallet.vue b/frontend/components/parts/ConnectWallet.vue index 24b6346..01a32c1 100644 --- a/frontend/components/parts/ConnectWallet.vue +++ b/frontend/components/parts/ConnectWallet.vue @@ -32,7 +32,7 @@ :loading="loading || isLoading" @click="modalWalletVisible = true" > - Connect wallet + Connect your wallet
diff --git a/frontend/pages/claim.vue b/frontend/pages/claim.vue index b74244b..d0fc9e3 100644 --- a/frontend/pages/claim.vue +++ b/frontend/pages/claim.vue @@ -100,8 +100,8 @@ async function loadNft(contract: Address, id: number, transactionHash: string) {

Almost there!

- But first, connect compatible digital wallet. This step is crucial for securely receiving - and managing the MENT token you’ll about to receive. + But first, connect a compatible digital wallet. This step is crucial + for securely receiving and managing the MENT token you’re about to receive.

diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue index 2998a50..7624fe5 100644 --- a/frontend/pages/index.vue +++ b/frontend/pages/index.vue @@ -12,7 +12,7 @@ useHead({ The Shape of MENT to Come -

Sign up and win your MENT token

+

Sign up and win one of 200 MENT tokens

By signing up, you’ll be in the game for the hottest perks in the form of MENT token diff --git a/frontend/pages/success.vue b/frontend/pages/success.vue index f90b747..cc9cf6b 100644 --- a/frontend/pages/success.vue +++ b/frontend/pages/success.vue @@ -1,8 +1,13 @@ From 2c959df5de12b4a95b09161c5e01fe0eb89ffc06 Mon Sep 17 00:00:00 2001 From: Tadej Vengust Date: Fri, 16 Feb 2024 13:47:55 +0100 Subject: [PATCH 24/36] update connect message --- backend/src/routes/admin-login.ts | 16 ++++---- backend/src/routes/claim-airdrop.ts | 39 ++++++++------------ backend/src/tests/routes/admin-login.test.ts | 18 ++++----- frontend/components/parts/ConnectWallet.vue | 2 +- frontend/pages/claim.vue | 2 +- 5 files changed, 34 insertions(+), 43 deletions(-) diff --git a/backend/src/routes/admin-login.ts b/backend/src/routes/admin-login.ts index 86bb0b6..6863e68 100644 --- a/backend/src/routes/admin-login.ts +++ b/backend/src/routes/admin-login.ts @@ -1,16 +1,16 @@ -import { Application } from "express"; -import { NextFunction, Request, Response } from "../http"; -import { RouteErrorCode } from "../config/values"; -import { ResourceError } from "../lib/errors"; -import { Identity } from "@apillon/sdk"; -import { generateAdminAuthToken } from "../lib/jwt"; +import { Application } from 'express'; +import { NextFunction, Request, Response } from '../http'; +import { RouteErrorCode } from '../config/values'; +import { ResourceError } from '../lib/errors'; +import { Identity } from '@apillon/sdk'; +import { generateAdminAuthToken } from '../lib/jwt'; /** * Installs new route on the provided application. * @param app ExpressJS application. */ export function inject(app: Application) { - app.post("/login", (req: Request, res: Response, next: NextFunction) => { + app.post('/login', (req: Request, res: Response, next: NextFunction) => { resolve(req, res).catch(next); }); } @@ -28,7 +28,7 @@ export async function resolve(req: Request, res: Response): Promise { walletAddress: context.env.ADMIN_WALLET, signature: body.signature, signatureValidityMinutes: 10, - message: `test\n${body.timestamp}`, + message: `Sign to verify and mint your free Ment NFT!\n${body.timestamp}`, timestamp: body.timestamp, }); diff --git a/backend/src/routes/claim-airdrop.ts b/backend/src/routes/claim-airdrop.ts index b7b2896..eb5cd84 100644 --- a/backend/src/routes/claim-airdrop.ts +++ b/backend/src/routes/claim-airdrop.ts @@ -1,24 +1,21 @@ -import { Application } from "express"; -import { NextFunction, Request, Response } from "../http"; -import { RouteErrorCode, ValidatorErrorCode } from "../config/values"; -import { ResourceError } from "../lib/errors"; -import { readEmailAirdropToken } from "../lib/jwt"; -import { AirdropStatus, User } from "../models/user"; -import { Identity, LogLevel, Nft } from "@apillon/sdk"; -import { LogType, writeLog } from "../lib/logger"; -import { env } from "../config/env"; +import { Application } from 'express'; +import { NextFunction, Request, Response } from '../http'; +import { RouteErrorCode, ValidatorErrorCode } from '../config/values'; +import { ResourceError } from '../lib/errors'; +import { readEmailAirdropToken } from '../lib/jwt'; +import { AirdropStatus, User } from '../models/user'; +import { Identity, LogLevel, Nft } from '@apillon/sdk'; +import { LogType, writeLog } from '../lib/logger'; +import { env } from '../config/env'; /**∂ * Installs new route on the provided application. * @param app ExpressJS application. */ export function inject(app: Application) { - app.post( - "/users/claim", - (req: Request, res: Response, next: NextFunction) => { - resolve(req, res).catch(next); - } - ); + app.post('/users/claim', (req: Request, res: Response, next: NextFunction) => { + resolve(req, res).catch(next); + }); } export async function resolve(req: Request, res: Response): Promise { @@ -33,7 +30,7 @@ export async function resolve(req: Request, res: Response): Promise { walletAddress: body.address, signature: body.signature, signatureValidityMinutes: 10, - message: `test\n${body.timestamp}`, + message: `Sign to verify and mint your free Ment NFT!\n${body.timestamp}`, timestamp: body.timestamp, }); @@ -86,20 +83,14 @@ export async function resolve(req: Request, res: Response): Promise { ? AirdropStatus.AIRDROP_COMPLETED : AirdropStatus.AIRDROP_ERROR; } catch (e) { - writeLog( - LogType.ERROR, - "Error creating airdrop", - "claim-airdrop.ts", - "resolve", - e - ); + writeLog(LogType.ERROR, 'Error creating airdrop', 'claim-airdrop.ts', 'resolve', e); user.airdrop_status = AirdropStatus.AIRDROP_ERROR; } await user.update(); if (response && response.success) { return res.respond(200, { - success: "ok", + success: 'ok', transactionHash: response.transactionHash, }); } else { diff --git a/backend/src/tests/routes/admin-login.test.ts b/backend/src/tests/routes/admin-login.test.ts index 2d9158f..78f36d7 100644 --- a/backend/src/tests/routes/admin-login.test.ts +++ b/backend/src/tests/routes/admin-login.test.ts @@ -2,15 +2,15 @@ import { createContextAndStartServer, Stage, stopServerAndCloseMySqlContext, -} from "../helpers/context"; -import * as request from "supertest"; -import { setupTestDatabase, clearTestDatabase } from "../helpers/migrations"; -import { HDNodeWallet, Wallet } from "ethers"; -import { Identity } from "@apillon/sdk"; +} from '../helpers/context'; +import * as request from 'supertest'; +import { setupTestDatabase, clearTestDatabase } from '../helpers/migrations'; +import { HDNodeWallet, Wallet } from 'ethers'; +import { Identity } from '@apillon/sdk'; let stage: Stage; let adminWallet: HDNodeWallet; -describe("admin login", () => { +describe('admin login', () => { beforeAll(async () => { adminWallet = Wallet.createRandom(); stage = await createContextAndStartServer({ @@ -24,16 +24,16 @@ describe("admin login", () => { await stopServerAndCloseMySqlContext(stage); }); - test("login", async () => { + test('login', async () => { const timestamp = new Date().getTime(); - const message = `test\n${timestamp}`; + const message = `Sign to verify and mint your free Ment NFT!\n${timestamp}`; const identity = new Identity(); const signature = await adminWallet.signMessage(message); const data = { signature, timestamp }; - const res = await request(stage.app).post("/login").send(data); + const res = await request(stage.app).post('/login').send(data); expect(res.status).toBe(200); }); diff --git a/frontend/components/parts/ConnectWallet.vue b/frontend/components/parts/ConnectWallet.vue index 1388a33..7245254 100644 --- a/frontend/components/parts/ConnectWallet.vue +++ b/frontend/components/parts/ConnectWallet.vue @@ -98,7 +98,7 @@ async function login() { } const timestamp = new Date().getTime(); - const message = 'test'; + const message = 'Sign to verify and mint your free Ment NFT!'; const signature = await walletClient.value.signMessage({ message: `${message}\n${timestamp}`, diff --git a/frontend/pages/claim.vue b/frontend/pages/claim.vue index 236af2d..8a29f37 100644 --- a/frontend/pages/claim.vue +++ b/frontend/pages/claim.vue @@ -47,7 +47,7 @@ async function claimAirdrop() { } } - const signature = await walletClient.value.signMessage({ message: `test\n${timestamp}` }); + const signature = await walletClient.value.signMessage({ message: `Sign to verify and mint your free Ment NFT!\n${timestamp}` }); const res = await $api.post('/users/claim', { jwt: query.token?.toString() || '', signature, From e0ca39aa76f078043d080f157f46186783aabf13 Mon Sep 17 00:00:00 2001 From: ZanJereb Date: Fri, 16 Feb 2024 14:41:25 +0100 Subject: [PATCH 25/36] Changed alert text color --- frontend/lib/config/naive.ts | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/frontend/lib/config/naive.ts b/frontend/lib/config/naive.ts index 5429261..26bbf9c 100644 --- a/frontend/lib/config/naive.ts +++ b/frontend/lib/config/naive.ts @@ -75,22 +75,22 @@ export const NaiveTheme: GlobalThemeOverrides = { colorLoading: colors.white, colorSuccess: colors.white, colorWarning: colors.white, - contentTextColor: colors.bg.DEFAULT, - contentTextColorError: colors.bg.DEFAULT, - contentTextColorInfo: colors.bg.DEFAULT, - contentTextColorSuccess: colors.bg.DEFAULT, - contentTextColorWarning: colors.bg.DEFAULT, - textColor: colors.bg.DEFAULT, - textColorError: colors.bg.DEFAULT, - textColorInfo: colors.bg.DEFAULT, - textColorLoading: colors.bg.DEFAULT, - textColorSuccess: colors.bg.DEFAULT, - textColorWarning: colors.bg.DEFAULT, - titleTextColor: colors.bg.DEFAULT, - titleTextColorError: colors.bg.DEFAULT, - titleTextColorInfo: colors.bg.DEFAULT, - titleTextColorSuccess: colors.bg.DEFAULT, - titleTextColorWarning: colors.bg.DEFAULT, + contentTextColor: colors.konference, + contentTextColorError: colors.konference, + contentTextColorInfo: colors.konference, + contentTextColorSuccess: colors.konference, + contentTextColorWarning: colors.konference, + textColor: colors.konference, + textColorError: colors.konference, + textColorInfo: colors.konference, + textColorLoading: colors.konference, + textColorSuccess: colors.konference, + textColorWarning: colors.konference, + titleTextColor: colors.konference, + titleTextColorError: colors.konference, + titleTextColorInfo: colors.konference, + titleTextColorSuccess: colors.konference, + titleTextColorWarning: colors.konference, titleFontWeight: '700', }, Button: { @@ -164,11 +164,11 @@ export const NaiveTheme: GlobalThemeOverrides = { borderColor: colors.bg.lighter, tdColor: colors.bg.DEFAULT, tdColorHover: colors.bg.dark, - tdTextColor: colors.white, + tdTextColor: colors.black, thColor: colors.bg.DEFAULT, thColorHover: colors.bg.DEFAULT, thFontWeight: '700', - thTextColor: colors.white, + thTextColor: colors.black, }, Dialog: { border: `1px solid ${colors.bg.lighter}`, From e94e8ed32c53f63b4526c439bb8d955aecf7d515 Mon Sep 17 00:00:00 2001 From: Tine Mlakar Date: Fri, 16 Feb 2024 14:51:05 +0100 Subject: [PATCH 26/36] cron fix --- backend/src/cron.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/backend/src/cron.ts b/backend/src/cron.ts index c19d99f..a927e86 100644 --- a/backend/src/cron.ts +++ b/backend/src/cron.ts @@ -38,7 +38,7 @@ export class Cron { let availableNftLeft = 0; if (env.MAX_SUPPLY) { - const res = await mysql.db.execute( + const res = await mysql.paramExecute( `SELECT COUNT(id) as total FROM user WHERE airdrop_status IN ( ${AirdropStatus.EMAIL_SENT}, @@ -51,7 +51,7 @@ export class Cron { ; ` ); - const numOfReservations = res[0][0].total; + const numOfReservations = res[0].total; availableNftLeft = env.MAX_SUPPLY - numOfReservations; } @@ -171,9 +171,8 @@ export class Cron { ); //Get users in waiting line and set their airdrop status to PENDING, so that they will recieve email for claim - const usersInWaitingLine = ( - await mysql.paramExecute( - `SELECT * FROM user WHERE + const usersInWaitingLine = await mysql.paramExecute( + `SELECT * FROM user WHERE airdrop_status = ${AirdropStatus.IN_WAITING_LINE} AND status = ${SqlModelStatus.ACTIVE} ORDER BY createTime ASC @@ -181,10 +180,9 @@ export class Cron { FOR UPDATE ; `, - null, - conn - ) - )[0] as Array; + null, + conn + ); console.info( 'Num of users in waiting line: ' + usersInWaitingLine.length From 6c44400d13166e60fca6d1b4a1c847b06f3f73c4 Mon Sep 17 00:00:00 2001 From: ZanJereb Date: Fri, 16 Feb 2024 15:22:18 +0100 Subject: [PATCH 27/36] Email fixes and tab title fixes --- backend/src/cron.ts | 10 ++++++---- backend/src/templates/mail/en-airdrop-claim.html | 6 +++--- frontend/pages/admin.vue | 2 +- frontend/pages/claim.vue | 2 +- frontend/pages/index.vue | 2 +- frontend/pages/share.vue | 2 +- frontend/pages/success.vue | 6 +++++- 7 files changed, 18 insertions(+), 12 deletions(-) diff --git a/backend/src/cron.ts b/backend/src/cron.ts index a927e86..6878d77 100644 --- a/backend/src/cron.ts +++ b/backend/src/cron.ts @@ -78,13 +78,14 @@ export class Cron { const token = await generateEmailAirdropToken(users[i].email); await SmtpSendTemplate( [users[i].email], - 'Claim your NFT', + 'Claim your MENT token', 'en-airdrop-claim', { appUrl: env.APP_URL, link: `${env.APP_URL}/claim?token=${token}`, claimExpiresIn: env.CLAIM_EXPIRES_IN, - } + }, + 'MENT', ); updates.push( `(${users[i].id}, '${users[i].email}', ${ @@ -95,11 +96,12 @@ export class Cron { //Currently, waiting line for airdrop is full.Send info email and set appropriate status await SmtpSendTemplate( [users[i].email], - 'You are in waiting line for NFT claim', + 'You have been placed on a waitlist for MENT token', 'en-airdrop-waiting-line', { appUrl: env.APP_URL, - } + }, + 'MENT' ); updates.push( `(${users[i].id}, '${users[i].email}', ${ diff --git a/backend/src/templates/mail/en-airdrop-claim.html b/backend/src/templates/mail/en-airdrop-claim.html index 40e941e..32d1502 100644 --- a/backend/src/templates/mail/en-airdrop-claim.html +++ b/backend/src/templates/mail/en-airdrop-claim.html @@ -79,7 +79,7 @@ font-size: 16px; font-weight: 700; font-family: 'IBM Plex Mono','arial','helvetica neue','helvetica','sans-serif'; - color: #141721; + color: #000000; text-decoration: none; width: auto; text-align: center; @@ -124,14 +124,14 @@

Congrats!

  • Proceed to the NFT platform (through the button below). The instructions will follow as you go.

  • Successfully connect your wallet and claim your MENT token.

  • Own, admire and brag about your newly acquired collectibles.

  • -

    +

    Claim MENT token



    diff --git a/frontend/pages/admin.vue b/frontend/pages/admin.vue index 869ba68..92fb295 100644 --- a/frontend/pages/admin.vue +++ b/frontend/pages/admin.vue @@ -8,7 +8,7 @@ definePageMeta({ layout: 'admin', }); useHead({ - title: 'Apillon email airdrop prebuilt solution', + title: 'MENT token airdrop', }); const message = useMessage(); diff --git a/frontend/pages/claim.vue b/frontend/pages/claim.vue index ffd5299..877b4b3 100644 --- a/frontend/pages/claim.vue +++ b/frontend/pages/claim.vue @@ -8,7 +8,7 @@ definePageMeta({ layout: 'claim', }); useHead({ - title: 'MENT NFT airdrop', + title: 'MENT token airdrop', }); const { query } = useRoute(); diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue index 7624fe5..df037a3 100644 --- a/frontend/pages/index.vue +++ b/frontend/pages/index.vue @@ -2,7 +2,7 @@ import SuperRareJpg from '~/assets/images/superrare.jpg'; useHead({ - title: 'MENT NFT airdrop', + title: 'MENT token airdrop', }); diff --git a/frontend/pages/share.vue b/frontend/pages/share.vue index 5763bd4..6f4ce6d 100644 --- a/frontend/pages/share.vue +++ b/frontend/pages/share.vue @@ -3,7 +3,7 @@ import { prepareOG } from '~/lib/utils/helpers'; import { Chains } from '~/lib/values/general.values'; useHead({ - title: 'PINK PASS', + title: 'MENT token airdrop', }); const router = useRouter(); diff --git a/frontend/pages/success.vue b/frontend/pages/success.vue index cc9cf6b..519438c 100644 --- a/frontend/pages/success.vue +++ b/frontend/pages/success.vue @@ -11,4 +11,8 @@
    - + From 7663c66d56cb004567001c9a447b27f33a11c568 Mon Sep 17 00:00:00 2001 From: ZanJereb Date: Fri, 16 Feb 2024 15:50:07 +0100 Subject: [PATCH 28/36] Fixed button on hover colors --- frontend/assets/styles/_transitions.css | 1 + frontend/components/parts/ConnectWallet.vue | 6 +++--- frontend/components/parts/form/SighUp.vue | 3 +-- frontend/components/parts/form/Upload.vue | 4 +++- frontend/components/parts/form/Wallet.vue | 2 +- frontend/pages/admin.vue | 8 +++++--- frontend/pages/claim.vue | 2 +- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/frontend/assets/styles/_transitions.css b/frontend/assets/styles/_transitions.css index 36c6a9a..7e3b0d8 100644 --- a/frontend/assets/styles/_transitions.css +++ b/frontend/assets/styles/_transitions.css @@ -19,6 +19,7 @@ &:hover { animation-name: bounceIn; animation-duration: 50s; + border: 1px solid #000; } &.locked { animation-duration: 0s; diff --git a/frontend/components/parts/ConnectWallet.vue b/frontend/components/parts/ConnectWallet.vue index db834fc..cc8f017 100644 --- a/frontend/components/parts/ConnectWallet.vue +++ b/frontend/components/parts/ConnectWallet.vue @@ -10,7 +10,7 @@ :loading="loading || isLoading" @click="disconnectWallet()" > - Disconnect + Disconnect - Login + Login - Connect your wallet + Connect your wallet diff --git a/frontend/components/parts/form/SighUp.vue b/frontend/components/parts/form/SighUp.vue index 2ee3a70..0867e54 100644 --- a/frontend/components/parts/form/SighUp.vue +++ b/frontend/components/parts/form/SighUp.vue @@ -118,13 +118,12 @@ function onCaptchaVerify(token: string) { - Sign up + Sign up diff --git a/frontend/components/parts/form/Upload.vue b/frontend/components/parts/form/Upload.vue index a60c120..8f7940b 100644 --- a/frontend/components/parts/form/Upload.vue +++ b/frontend/components/parts/form/Upload.vue @@ -33,10 +33,11 @@ - Start New Airdrop + Start New Airdrop @@ -44,6 +45,7 @@