From 796de43c9693db5e656f1c8e3b9938ade42d311c Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Sun, 27 Oct 2024 23:50:13 +0400 Subject: [PATCH 1/4] feat: return email subscription data in User --- .env.example | 1 + src/graphql/operations/users.ts | 28 +++++++++++++++++++++++++++- src/graphql/schema.gql | 6 ++++++ src/helpers/mysql.ts | 16 ++++++++++++++-- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 145016ca..634b4892 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,7 @@ PORT=3000 NETWORK=testnet HUB_DATABASE_URL=mysql://... SEQ_DATABASE_URL=mysql:// +ENVELOP_DATABASE_URL=mysql:// SEQUENCER_URL=https://testnet.seq.snapshot.org STAMP_URL=https://stamp.fyi KEYCARD_URL=https://keycard.snapshot.org diff --git a/src/graphql/operations/users.ts b/src/graphql/operations/users.ts index ef9414ff..748e516b 100644 --- a/src/graphql/operations/users.ts +++ b/src/graphql/operations/users.ts @@ -1,6 +1,6 @@ import { capture } from '@snapshot-labs/snapshot-sentry'; import log from '../../helpers/log'; -import db from '../../helpers/mysql'; +import db, { envelopDB } from '../../helpers/mysql'; import { buildWhereQuery, checkLimits, formatUser } from '../helpers'; export default async function (parent, args) { @@ -72,6 +72,15 @@ export default async function (parent, args) { user.lastVote = count.lastVote; }); } + + const subscriptions = await getEmailSubscribers(ids); + users.forEach((user: any) => { + user.emailSubscription = subscriptions[user.id] || { + status: 'NOT_SUBSCRIBED', + subscriptions: [] + }; + }); + return users.map(formatUser); } catch (e: any) { log.error(`[graphql] users, ${JSON.stringify(e)}`); @@ -79,3 +88,20 @@ export default async function (parent, args) { return Promise.reject(new Error('request failed')); } } + +async function getEmailSubscribers(user_ids: string[]) { + const subscribers = await envelopDB.queryAsync( + `SELECT verified, address, subscriptions FROM subscribers WHERE address IN (?)`, + user_ids + ); + + return Object.fromEntries( + subscribers.map((s: any) => [ + s.address, + { + status: s.verified > 0 ? 'VERIFIED' : 'UNVERIFIED', + subscriptions: JSON.parse(s.subscriptions) + } + ]) + ); +} diff --git a/src/graphql/schema.gql b/src/graphql/schema.gql index c4823f34..2d4d1a59 100644 --- a/src/graphql/schema.gql +++ b/src/graphql/schema.gql @@ -520,6 +520,11 @@ type DelegationPortal { delegationApi: String! } +type EmailSubscription { + status: String! + subscriptions: [String] +} + type Vote { id: String! ipfs: String @@ -581,6 +586,7 @@ type User { votesCount: Int proposalsCount: Int lastVote: Int + emailSubscription: EmailSubscription } type Statement { diff --git a/src/helpers/mysql.ts b/src/helpers/mysql.ts index f5903bdb..24f43f29 100644 --- a/src/helpers/mysql.ts +++ b/src/helpers/mysql.ts @@ -19,7 +19,6 @@ hubConfig.connectTimeout = 60e3; hubConfig.acquireTimeout = 60e3; hubConfig.timeout = 60e3; hubConfig.charset = 'utf8mb4'; - const hubDB = mysql.createPool(hubConfig); // @ts-ignore @@ -35,6 +34,19 @@ sequencerConfig.timeout = 60e3; sequencerConfig.charset = 'utf8mb4'; const sequencerDB = mysql.createPool(sequencerConfig); +// @ts-ignore +const envelopConfig = parse(process.env.ENVELOP_DATABASE_URL); +envelopConfig.connectionLimit = connectionLimit; +envelopConfig.multipleStatements = true; +envelopConfig.database = envelopConfig.path[0]; +envelopConfig.host = envelopConfig.hosts[0].name; +envelopConfig.port = envelopConfig.hosts[0].port; +envelopConfig.connectTimeout = 60e3; +envelopConfig.acquireTimeout = 60e3; +envelopConfig.timeout = 60e3; +envelopConfig.charset = 'utf8mb4'; +const envelopDB = mysql.createPool(envelopConfig); + bluebird.promisifyAll([Pool, Connection]); -export { hubDB as default, sequencerDB }; +export { hubDB as default, sequencerDB, envelopDB }; From 7979b2897cc94449f8e6547ebd9b2eaed1e2c933 Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Mon, 28 Oct 2024 00:06:01 +0400 Subject: [PATCH 2/4] test: fix tests --- test/.env.test | 1 + test/schema_envelop.sql | 11 +++++++++++ test/setupDb.ts | 7 +++++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 test/schema_envelop.sql diff --git a/test/.env.test b/test/.env.test index 7346081a..e7669e7e 100644 --- a/test/.env.test +++ b/test/.env.test @@ -1,3 +1,4 @@ HUB_DATABASE_URL=mysql://root:root@127.0.0.1:3306/hub_test SEQ_DATABASE_URL=mysql://root:root@127.0.0.1:3306/hub_test +ENVELOP_DATABASE_URL=mysql://root:root@127.0.0.1:3306/hub_test NODE_ENV=test diff --git a/test/schema_envelop.sql b/test/schema_envelop.sql new file mode 100644 index 00000000..8e67b7ab --- /dev/null +++ b/test/schema_envelop.sql @@ -0,0 +1,11 @@ +CREATE TABLE subscribers ( + email VARCHAR(256) NOT NULL, + address VARCHAR(256) NOT NULL, + subscriptions JSON DEFAULT NULL, + created BIGINT NOT NULL, + verified BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY (email, address), + UNIQUE KEY idx_address_email (address, email), + INDEX created (created), + INDEX verified (verified) +); diff --git a/test/setupDb.ts b/test/setupDb.ts index 8dbec39a..867f374a 100644 --- a/test/setupDb.ts +++ b/test/setupDb.ts @@ -15,6 +15,8 @@ bluebird.promisifyAll([Pool, Connection]); const db = mysql.createPool(config); const dbName = config.path[0]; +const schemaFiles = ['./src/helpers/schema.sql', './test/schema_envelop.sql']; + if (!dbName.endsWith(TEST_DATABASE_SUFFIX)) { console.error( `Invalid test database name. Must end with ${TEST_DATABASE_SUFFIX}` @@ -33,8 +35,9 @@ async function run() { console.info(`- Creating new database: ${dbName}`); await db.queryAsync(`CREATE DATABASE ${dbName}`); - const schema = fs - .readFileSync('./src/helpers/schema.sql', 'utf8') + const schema = schemaFiles + .map(file => fs.readFileSync(file, 'utf8')) + .join(' ') .replaceAll('CREATE TABLE ', `CREATE TABLE ${dbName}.`) .split(splitToken) .filter(s => s.trim().length > 0); From ff67024cd1c98c74c96f9a3a1de5d569a221f8ea Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Mon, 28 Oct 2024 00:09:10 +0400 Subject: [PATCH 3/4] refactor: better variable name --- src/graphql/operations/users.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphql/operations/users.ts b/src/graphql/operations/users.ts index 748e516b..4c3f0620 100644 --- a/src/graphql/operations/users.ts +++ b/src/graphql/operations/users.ts @@ -73,9 +73,9 @@ export default async function (parent, args) { }); } - const subscriptions = await getEmailSubscribers(ids); + const subscribers = await getEmailSubscribers(ids); users.forEach((user: any) => { - user.emailSubscription = subscriptions[user.id] || { + user.emailSubscription = subscribers[user.id] || { status: 'NOT_SUBSCRIBED', subscriptions: [] }; From 2482d692ea2746b190dd5f837c408c00bf57f2bb Mon Sep 17 00:00:00 2001 From: Wan Qi Chen <495709+wa0x6e@users.noreply.github.com> Date: Sun, 8 Dec 2024 22:30:11 +0800 Subject: [PATCH 4/4] fix: make envelopDB connection optional --- src/graphql/operations/users.ts | 8 +++++++- src/helpers/mysql.ts | 27 +++++++++++++++------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/graphql/operations/users.ts b/src/graphql/operations/users.ts index 4c3f0620..a70ddce3 100644 --- a/src/graphql/operations/users.ts +++ b/src/graphql/operations/users.ts @@ -89,7 +89,13 @@ export default async function (parent, args) { } } -async function getEmailSubscribers(user_ids: string[]) { +async function getEmailSubscribers( + user_ids: string[] +): Promise< + Record +> { + if (!envelopDB) return {}; + const subscribers = await envelopDB.queryAsync( `SELECT verified, address, subscriptions FROM subscribers WHERE address IN (?)`, user_ids diff --git a/src/helpers/mysql.ts b/src/helpers/mysql.ts index 24f43f29..21fd314f 100644 --- a/src/helpers/mysql.ts +++ b/src/helpers/mysql.ts @@ -34,18 +34,21 @@ sequencerConfig.timeout = 60e3; sequencerConfig.charset = 'utf8mb4'; const sequencerDB = mysql.createPool(sequencerConfig); -// @ts-ignore -const envelopConfig = parse(process.env.ENVELOP_DATABASE_URL); -envelopConfig.connectionLimit = connectionLimit; -envelopConfig.multipleStatements = true; -envelopConfig.database = envelopConfig.path[0]; -envelopConfig.host = envelopConfig.hosts[0].name; -envelopConfig.port = envelopConfig.hosts[0].port; -envelopConfig.connectTimeout = 60e3; -envelopConfig.acquireTimeout = 60e3; -envelopConfig.timeout = 60e3; -envelopConfig.charset = 'utf8mb4'; -const envelopDB = mysql.createPool(envelopConfig); +let envelopDB; +if (process.env.ENVELOP_DATABASE_URL) { + // @ts-ignore + const envelopConfig = parse(process.env.ENVELOP_DATABASE_URL); + envelopConfig.connectionLimit = connectionLimit; + envelopConfig.multipleStatements = true; + envelopConfig.database = envelopConfig.path[0]; + envelopConfig.host = envelopConfig.hosts[0].name; + envelopConfig.port = envelopConfig.hosts[0].port; + envelopConfig.connectTimeout = 60e3; + envelopConfig.acquireTimeout = 60e3; + envelopConfig.timeout = 60e3; + envelopConfig.charset = 'utf8mb4'; + envelopDB = mysql.createPool(envelopConfig); +} bluebird.promisifyAll([Pool, Connection]);