From 8f609ee0133b762605d13b94254ce0a56683f7dd Mon Sep 17 00:00:00 2001 From: Ken Goldfarb Date: Tue, 16 May 2023 14:28:49 -0600 Subject: [PATCH 01/10] wip email --- package.json | 21 +- src/config/default.ts | 11 +- src/controllers/AgreementController.ts | 99 +++ src/controllers/TestController.ts | 344 +------- src/lib/emailTemplate.ts | 459 +++++++++++ src/models/Invite.ts | 45 ++ src/routers/apiRouter.ts | 14 +- src/services/Aws.ts | 47 ++ src/services/Ethers.ts | 6 + src/services/Guild.ts | 754 ------------------ src/services/MeemId.ts | 53 +- src/services/Puppeteer.ts | 26 - src/services/Scraper.ts | 29 - src/services/Web3.ts | 122 --- src/types/meem.generated.ts | 35 + src/types/meem.public.generated.ts | 35 + src/types/models.ts | 2 + src/types/services.d.ts | 6 +- .../api/agreements/sendInvites.shared.ts | 35 + yarn.lock | 238 +----- 20 files changed, 859 insertions(+), 1522 deletions(-) create mode 100644 src/lib/emailTemplate.ts create mode 100644 src/models/Invite.ts create mode 100644 src/services/Aws.ts delete mode 100644 src/services/Guild.ts delete mode 100644 src/services/Puppeteer.ts delete mode 100644 src/services/Scraper.ts create mode 100644 src/types/shared/api/agreements/sendInvites.shared.ts diff --git a/package.json b/package.json index 2e688884..4c90d536 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "clean": "rm -rf build/*", "build": "npm run clean ; tsc ; echo \"Finished Building\"", "release": "semantic-release", - "heroku-prebuild": "yarn remove puppeteer puppeteer-extra puppeteer-extra-plugin-adblocker puppeteer-extra-plugin-stealth -D && yarn add puppeteer@^13.1.3 puppeteer-extra@^3.2.3 puppeteer-extra-plugin-adblocker@^2.12.0 puppeteer-extra-plugin-stealth@^2.9.0", "heroku-cleanup": "cp .yarnclean.ci .yarnclean && yarn autoclean --force", "########## Run ##########": "", "start": "NODE_ENV=local RUN_PRODUCTION=true ENABLE_GUNDB=false GENERATE_SHARED_TYPES=false node ./build/src/server.js", @@ -20,14 +19,8 @@ "local": "npm run kill-inspect && NODE_ENV=local PORT=3005 tsnd --exit-child --inspect=4005 --respawn --transpile-only --ignore-watch=src/types/meem.generated.ts --ignore-watch=src/types/meem.public.generated.ts src/server.ts", "########## DB Migrations ##########": "", "migration:create": "sequelize migration:create", - "db:migrate": "sequelize db:migrate", - "db:migrate:local": "NODE_ENV=local node ./node_modules/.bin/sequelize db:migrate", - "db:migrate:development": "NODE_ENV=development node ./node_modules/.bin/sequelize db:migrate", - "db:migrate:stage": "NODE_ENV=staging node ./node_modules/.bin/sequelize db:migrate", - "db:migrate:production": "NODE_ENV=production node ./node_modules/.bin/sequelize db:migrate", "########## Tests ##########": "", "test": "NODE_ENV=local DISABLE_MIGRATIONS=true ALLOW_NON_SSL=true ORM_LOGGING=false LOG_LEVEL=debug TESTING=true ENABLE_GUNDB=false GENERATE_SHARED_TYPES=false DISCORD_ENABLE_LISTENERS=false PORT= mocha --exit -r ts-node/register/transpile-only 'src/**/*.test.ts'", - "#test": "DISABLE_ORM_SYNC=false NODE_ENV=local DISABLE_MIGRATIONS=true ALLOW_NON_SSL=true ORM_LOGGING=false LOG_LEVEL=debug TESTING=true ENABLE_GUNDB=false GENERATE_SHARED_TYPES=false PORT= DATABASE_URL=sqlite:./tmp/testing.db ENABLE_CONTRACT_LISTENERS=false ENABLE_TWITTER_LISTENERS=false mocha --exit -r ts-node/register/transpile-only 'src/**/*.test.ts'", "########## Serverless ##########": "", "deploy:dev": "SLS_DEBUG=* sls deploy -s dev", "deploy:staging": "SLS_DEBUG=* sls deploy -s staging", @@ -47,15 +40,6 @@ "postinstall": "if [ \"${RUN_POSTINSTALL:=\"false\"}\" = \"true\" ]; then npm run build; else echo \"Skipping postinstall RUN_POSTINSTALL is not set to true.\" && exit 0; fi", "kill-inspect": "kill-port 4005", "########## Meems ##########": "", - "#generateTypes": "typechain --target=ethers-v5 'src/abis/*.json' --out-dir='src/types'", - "fetchMeemABI": "tsnd --transpile-only src/scripts/fetchMeemABI.ts", - "fetchWhitelist": "tsnd --transpile-only src/scripts/fetchWhitelist.ts", - "fetchAccess": "tsnd --transpile-only src/scripts/fetchAccess.ts", - "fetchMeemIdABI": "tsnd --transpile-only src/scripts/fetchMeemIdABI.ts", - "fetchMeemMarketABI": "tsnd --transpile-only src/scripts/fetchMeemMarketABI.ts", - "fetchMeemContracts": "tsnd --transpile-only src/scripts/fetchMeemContracts.ts", - "fetchMeemIdContracts": "tsnd --transpile-only src/scripts/fetchMeemIdContracts.ts", - "fetchContracts": "yarn fetchMeemContracts && yarn fetchMeemIdContracts", "swagger": "npx swagger-inline 'src/types/shared/api/**/*.ts' --base 'src/types/shared/api/swaggerBase.yaml' --format .json > 'src/types/shared/api/meem-api.json'", "generateHasuraMetadata": "tsnd --transpile-only src/scripts/generateHasuraMetadata.ts", "migrateSymphony": "tsnd --transpile-only src/scripts/migrateSymphony.ts", @@ -63,7 +47,6 @@ }, "dependencies": { "@discordjs/rest": "^1.6.0", - "@guildxyz/sdk": "^1.1.1", "@kengoldfarb/log": "^1.0.3", "@meemproject/meem-contracts": "^0.17.1", "@meemproject/metadata": "^0.17.1", @@ -76,6 +59,7 @@ "@typechain/ethers-v5": "^10.1.0", "alchemy-sdk": "^2.1.1", "auth0": "^2.42.0", + "aws-sdk": "^2.1378.0", "bcrypt": "^5.0.1", "body-parser": "^1.20.0", "busboy": "^0.3.1", @@ -97,12 +81,10 @@ "openai": "^3.2.1", "pg": "^8.8.0", "sequelize": "^6.23.2", - "sharp": "^0.31.0", "slug": "^8.0.0", "string-argv": "^0.3.1", "superagent": "^8.0.0", "supertest": "^6.2.4", - "tslib": "^2.4.1", "twitter-api-sdk": "^1.2.1", "twitter-api-v2": "^1.12.7", "typechain": "^8.1.0", @@ -141,7 +123,6 @@ "@types/uuid": "^8.3.4", "@types/ws": "^8.5.3", "aws-lambda": "^1.0.7", - "aws-sdk": "^2.1225.0", "babel-eslint": "^10.1.0", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", diff --git a/src/config/default.ts b/src/config/default.ts index 4a813943..5f752cb2 100644 --- a/src/config/default.ts +++ b/src/config/default.ts @@ -13,9 +13,6 @@ export default { MEEM_DOMAIN: process.env.MEEM_DOMAIN ?? 'https://app.meem.wtf', SERVER_LISTENING: process.env.SERVER_LISTENING !== 'false', SERVER_ADMIN_KEY: process.env.SERVER_ADMIN_KEY ?? 'xGugNAB2PEX4uY4sPF', - // JWT_SECRET: - // process.env.JWT_SECRET ?? - // 'ac741f40d71a2564e08180f5eb1cc9dd28e288ed75b33c34cba2fc18a3c31a64e719835877c7a6db9fdae8054037053172aba56f4dabc5f1b', JWT_RSA_PUBLIC_KEY: process.env.JWT_RSA_PUBLIC_KEY ?? '', JWT_RSA_PRIVATE_KEY: process.env.JWT_RSA_PRIVATE_KEY ?? '', JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN @@ -27,11 +24,7 @@ export default { GENERATE_SHARED_TYPES: process.env.GENERATE_SHARED_TYPES === 'true', TESTING: process.env.TESTING === 'true', DISABLE_RATE_LIMIT: process.env.DISABLE_RATE_LIMIT === 'true', - DATABASE_URL: process.env.DATABASE_URL ?? 'sqlite::memory:', - // DATABASE_URL_TESTING: - // process.env.DATABASE_URL_TESTING || - // `sqlite:${__dirname}/../../tmp/testing.db`, DISABLE_MIGRATIONS: process.env.DISABLE_MIGRATIONS === 'true', DISABLE_ORM_SYNC: process.env.DISABLE_ORM_SYNC === 'true', DATABASE_POOL_MAX: process.env.DATABASE_POOL_MAX @@ -207,5 +200,7 @@ export default { MEEM_HELPDESK_SUPABASE_SUBDOMAIN: process.env.MEEM_HELPDESK_SUPABASE_SUBDOMAIN ?? '', MEEM_HELPDESK_DISCORD_CHANNEL_ID: - process.env.MEEM_HELPDESK_DISCORD_CHANNEL_ID ?? '' + process.env.MEEM_HELPDESK_DISCORD_CHANNEL_ID ?? '', + AWS_SMTP_ACCESS_KEY_ID: process.env.AWS_SMTP_ACCESS_KEY_ID ?? '', + AWS_SMTP_SECRET_ACCESS_KEY: process.env.AWS_SMTP_SECRET_ACCESS_KEY ?? '' } diff --git a/src/controllers/AgreementController.ts b/src/controllers/AgreementController.ts index 52460c3c..0c48c6b7 100644 --- a/src/controllers/AgreementController.ts +++ b/src/controllers/AgreementController.ts @@ -393,4 +393,103 @@ export default class AgreementController { isAdmin }) } + + public static async sendInvites( + req: IAuthenticatedRequest, + res: IResponse + ): Promise { + if (!req.wallet) { + throw new Error('USER_NOT_LOGGED_IN') + } + + const { agreementId } = req.params + const { to } = req.body + + const agreement = await orm.models.Agreement.findOne({ + where: { + id: agreementId + } + }) + + if (!agreement) { + throw new Error('AGREEMENT_NOT_FOUND') + } + + const isAdmin = await agreement.isAdmin(req.wallet.address) + + if (!isAdmin) { + throw new Error('NOT_AUTHORIZED') + } + + const walletAddresses: string[] = [] + const emails: string[] = [] + + to.forEach(t => { + if (ethers.utils.isAddress(t)) { + walletAddresses.push(ethers.utils.getAddress(t)) + } else { + emails.push(t) + } + }) + + const [agreementTokens, currentTokenId, wallets] = await Promise.all([ + orm.models.AgreementToken.findAll({ + where: { + AgreementId: agreement.id + }, + include: [ + { + model: orm.models.Wallet, + as: 'Owner', + where: { + address: { + [Op.in]: walletAddresses + } + } + } + ] + }), + orm.models.AgreementToken.count({ + where: { + AgreementId: agreement.id + } + }), + orm.models.Wallet.findAll({ + where: { + address: { + [Op.in]: walletAddresses + } + } + }) + ]) + + const newAgreementTokens: Record[] = [] + let tokenId = currentTokenId + 1 + for (let i = 0; i < walletAddresses.length; i++) { + const walletAddress = walletAddresses[i] + let wallet = wallets.find(w => w.address === walletAddress) + if (!wallet) { + wallet = await orm.models.Wallet.create({ + address: walletAddress + }) + } + const agreementToken = agreementTokens.find(t => t.OwnerId === wallet?.id) + + if (!agreementToken) { + newAgreementTokens.push({ + tokenId, + AgreementId: agreement.id, + OwnerId: wallet.id + }) + + tokenId++ + } + } + + await orm.models.AgreementToken.bulkCreate(newAgreementTokens) + + return res.json({ + status: 'success' + }) + } } diff --git a/src/controllers/TestController.ts b/src/controllers/TestController.ts index 0e945c3e..d7ae4281 100644 --- a/src/controllers/TestController.ts +++ b/src/controllers/TestController.ts @@ -1,75 +1,11 @@ -/* eslint-disable no-unused-vars */ -/* eslint-disable no-console */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { Validator } from '@meemproject/metadata' -import { Wallet as AlchemyWallet } from 'alchemy-sdk' -import { TextChannel } from 'discord.js' -import { ethers, ethers as Ethers } from 'ethers' -import { encodeSingle, TransactionType } from 'ethers-multisend' import { Request, Response } from 'express' -import keccak256 from 'keccak256' -import { DateTime, Duration } from 'luxon' -import { MerkleTree } from 'merkletreejs' -import { Op } from 'sequelize' -import GnosisSafeABI from '../abis/GnosisSafe.json' -import GnosisSafeProxyABI from '../abis/GnosisSafeProxy.json' -import meemABI from '../abis/Meem.json' -import { prompt1, prompt2 } from '../lib/gptPrompts' -import { Constructor } from '../serverless/cron' -import { Mycontract__factory } from '../types/Meem' -import { MeemAPI } from '../types/meem.generated' export default class TestController { - public static async testSummary( - req: Request, - res: Response - ): Promise { - const client = services.discord.client - const guild = await client.guilds.fetch('887371146171920475') - const channel = (await guild.channels.fetch( - '888127909468962856' - )) as TextChannel - - const messages = await channel.messages.fetch({ - limit: 50 - }) - - const formattedMessages: Record[] = [] - - messages.forEach(message => { - formattedMessages.push(services.discord.parseMessageForWebhook(message)) - }) - - const completion = await services.openai.getCompletion({ - messages: [ - { - role: 'system', - content: prompt2({ - brandName: 'Tiffany and Co', - brandVoice: 'Absurd and weird' - }) - }, - { - role: 'user', - content: `Here's a bunch of messages in JSON to curate and create a newsletter: ${JSON.stringify( - formattedMessages - )}` - } - ] - }) - // const completion = await services.openai.getCompletion({ - // messages: [ - // { - // role: 'user', - // content: `Hello` - // } - // ] - // }) + public static async releaseLock(req: Request, res: Response) { + await services.ethers.releaseLock(+(req.query.chainId as string)) return res.json({ - status: 'success', - stopReason: completion.data.choices[0].finish_reason, - completion: completion.data.choices[0].message + status: 'success' }) } @@ -88,276 +24,18 @@ export default class TestController { }) } - public static async testCron(req: Request, res: Response): Promise { - // eslint-disable-next-line - const cronConstructor = require(`../cron/jobs/${req.query.job}`) - .default as Constructor - // eslint-disable-next-line - const cronjob = new cronConstructor() - await cronjob.run() - - return res.json({ - status: 'success' - }) - } - - public static async syncContract(req: Request, res: Response) { - await services.contractEvents.meemHandleContractInitialized({ - address: req.query.address as string, - chainId: +(req.query.chainId as string) as number - }) - - return res.json({ - status: 'success' - }) - } - - public static async metadata(req: Request, res: Response) { - const metadata = { - meem_metadata_type: 'MeemAgreement_Contract', - meem_metadata_version: '20221116', - name: 'metadataaaa', - description: 'asdfasdf', - image: - 'data:image/png;base64,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', - associations: [], - external_url: '', - application_instructions: '' - } - - const contractMetadataValidator = new Validator(metadata) - const contractMetadataValidatorResult = - contractMetadataValidator.validate(metadata) - - return res.json({ - status: 'success', - contractMetadataValidatorResult - }) - } - - public static async testGnosis(req: Request, res: Response) { - const masterContractAddress = '0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552' - const proxyContractAddress = '0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2' - const topic = - '0x141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a8' - const safeOwners: string[] = [ - '0xbA343C26ad4387345edBB3256e62f4bB73d68a04', - '0xde19C037a85A609ec33Fc747bE9Db8809175C3a5' - ] - const threshold = 1 - - // gnosisSafeAbi is the Gnosis Safe ABI in JSON format, - // you can find an example here: https://github.com/gnosis/safe-deployments/blob/main/src/assets/v1.1.1/gnosis_safe.json#L16 - const { provider, wallet } = await services.ethers.getProvider({ - chainId: +(req.query.chainId as string) as number - }) - - const proxyContract = new ethers.Contract( - proxyContractAddress, - GnosisSafeProxyABI, - wallet - ) - const gnosisInterface = new ethers.utils.Interface(GnosisSafeABI) - const safeSetupData = gnosisInterface.encodeFunctionData('setup', [ - safeOwners, - threshold, - '0x0000000000000000000000000000000000000000', - '0x', - '0x0000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000', - '0', - '0x0000000000000000000000000000000000000000' - ]) - - // safeContractFactory is an instance of the "Contract" type from Ethers JS - // see https://docs.ethers.io/v5/getting-started/#getting-started--contracts - // for more details. - // You're going to need the address of a Safe contract factory and the ABI, - // which can be found here: https://github.com/gnosis/safe-deployments/blob/main/src/assets/v1.1.1/proxy_factory.json#L16 - const tx = await proxyContract.createProxy( - masterContractAddress, - safeSetupData - ) - - await tx.wait() - - const receipt = await provider.core.getTransactionReceipt(tx.hash) - - if (receipt) { - // Find the newly created Safe contract address in the transaction receipt - for (let i = 0; i < receipt.logs.length; i += 1) { - const receiptLog = receipt.logs[i] - const foundTopic = receiptLog.topics.find(t => t === topic) - if (foundTopic) { - log.info(`address: ${receiptLog.address}`) - break - } - } - } - - console.log(tx) - - return res.json({ - status: 'success', - tx - }) - } - - public static async testHash(req: Request, res: Response) { - const addresses = [ - '0xbA343C26ad4387345edBB3256e62f4bB73d68a04', - '0xE7EDF0FeAebaF19Ad799eA9246E7bd8a38002d89', - '0xEC41a0AAea12ad8F588e5aD0e71A837d83e05792' - ] - - const hashedAddress = keccak256( - '0xbA343C26ad4387345edBB3256e62f4bB73d68a04' - ) - - const leaves = addresses.map(a => keccak256(a)) - const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true }) - const rootHash = merkleTree.getRoot().toString('hex') - const proof = merkleTree.getHexProof(hashedAddress) - - // const isVerified = merkleTree.verify(proof, hashedAddress, rootHash) - const tree = new MerkleTree([], keccak256, { sortPairs: true }) - const isVerified = tree.verify(proof, hashedAddress, rootHash) - - return res.json({ - status: 'success', - rootHash, - isVerified, - merkleTree: merkleTree.toString(), - proof - }) - } - - public static async releaseLock(req: Request, res: Response) { - await services.ethers.releaseLock(+(req.query.chainId as string)) - - return res.json({ - status: 'success' - }) - } - - public static async mintPKP(req: Request, res: Response) { - const tx = await services.lit.mintPKP() - - return res.json({ - status: 'success', - tx - }) - } - - public static async getEthAddress(req: Request, res: Response) { - const ethAddress = await services.lit.getEthAddress( - req.query.tokenId as string - ) - - return res.json({ - status: 'success', - ethAddress - }) - } - - public static async testPinata(req: Request, res: Response) { - const result = await services.web3.saveToPinata({ - json: { - meem_metadata_type: 'Meem_AgreementContract', - meem_metadata_version: '20221116', - name: 'aqer', - description: 'asdfgscfg', - image: - 'data:image/png;base64,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', - associations: [], - external_url: '', - application_instructions: [] - } - }) - - return res.json({ - status: 'success', - result - }) - } - - public static async testTxEncoding(req: Request, res: Response) { - // const functionSignature = ethers.utils - // .id('mint((address,string,uint8))') - // .substring(0, 10) - const m = await services.agreement.getAgreementContract({ - chainId: 5, - address: '0xf5a0fD628AFe07D8c3736762Bd65Ae009F23e335' - }) - - const encoded = encodeSingle({ - id: '1', - abi: JSON.stringify(meemABI), - functionSignature: 'bulkMint((address,string,uint8)[])', - to: '0xf5a0fD628AFe07D8c3736762Bd65Ae009F23e335', - value: '0', - inputValues: { - bulkParams: [ - { - to: '0xbA343C26ad4387345edBB3256e62f4bB73d68a04', - tokenType: '0', - tokenURI: 'ipfs://QmTFk4Wgka3VfwpMJzjbHA9ZsKjG7bbRcbbDoPnHYVV3Gv' - }, - { - to: '0x1e6c71F7338276524D646dB2F04a49fA88458cF2', - tokenType: '0', - tokenURI: 'ipfs://QmTFk4Wgka3VfwpMJzjbHA9ZsKjG7bbRcbbDoPnHYVV3Gv' - } - ] - }, - type: TransactionType.callContract - }) - // const x = m.interface.functions['setMaxSupply(uint256)'].format() - - const encoded2 = encodeSingle({ - id: '1', - abi: JSON.stringify(meemABI), - functionSignature: 'mint((address,string,uint8))', - to: '0xf5a0fD628AFe07D8c3736762Bd65Ae009F23e335', - value: '0', - inputValues: { - params: [ - '0xbA343C26ad4387345edBB3256e62f4bB73d68a04', - 'https://meem.wtf', - '0' - ] - }, - type: TransactionType.callContract - }) - - // console.log({ x, encoded }) - - return res.json({ - status: 'success', - encoded, - encoded2, - meemABI + public static async testEmail( + req: Request, + res: Response + ): Promise { + await services.aws.sendEmail({ + to: ['ken@meem.wtf'], + body: '

Testing email

', + subject: 'Testing email' }) - } - public static async testCallback(req: Request, res: Response) { - log.debug({ - query: req.query, - body: req.body - }) return res.json({ status: 'success' }) } - - public static async testDecrypt(req: Request, res: Response) { - const decrypted = await services.data.decrypt({ - strToDecrypt: req.query.s as string, - privateKey: config.ENCRYPTION_KEY - }) - return res.json({ - status: 'success', - decrypted - }) - } } diff --git a/src/lib/emailTemplate.ts b/src/lib/emailTemplate.ts new file mode 100644 index 00000000..887481a8 --- /dev/null +++ b/src/lib/emailTemplate.ts @@ -0,0 +1,459 @@ +export const transactionalTemplate = (options: { + subject: string + inboxPreview?: string + title: string + bodyText: string + ctaText?: string + ctaUrl?: string +}) => ` + + + + + + + + + + ${ + options.subject + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +` diff --git a/src/models/Invite.ts b/src/models/Invite.ts new file mode 100644 index 00000000..c78b37dd --- /dev/null +++ b/src/models/Invite.ts @@ -0,0 +1,45 @@ +import { DataTypes } from 'sequelize' +import { BaseModel } from '../core/BaseModel' +import type { IModels } from '../types/models' +import type Agreement from './Agreement' + +export default class Invite extends BaseModel { + public static readonly modelName = 'Invite' + + public static get indexes() { + return [ + { + name: 'Invite_AgreementId', + fields: ['AgreementId'] + }, + { + name: 'Invite_code', + fields: ['code'] + } + ] + } + + public static readonly attributes = { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + code: { + type: DataTypes.STRING, + allowNull: false + } + } + + public id!: string + + public code!: string + + public AgreementId!: string | null + + public Agreement?: Agreement | null + + public static associate(models: IModels) { + this.belongsTo(models.Agreement) + } +} diff --git a/src/routers/apiRouter.ts b/src/routers/apiRouter.ts index 8d549a62..4d3b236e 100644 --- a/src/routers/apiRouter.ts +++ b/src/routers/apiRouter.ts @@ -175,19 +175,7 @@ export default (app: Express, _express: typeof coreExpress) => { router.postAsync('/test/webhook', TestController.testWebhook) if (config.ENABLE_TEST_ENDPOINTS) { - router.getAsync('/test/gnosis', TestController.testGnosis) - router.getAsync('/test/testCron', TestController.testCron) - router.getAsync('/test/syncContract', TestController.syncContract) - router.getAsync('/test/metadata', TestController.metadata) - router.getAsync('/test/hash', TestController.testHash) router.getAsync('/test/releaseLock', TestController.releaseLock) - router.getAsync('/test/mintPKP', TestController.mintPKP) - router.getAsync('/test/getEthAddress', TestController.getEthAddress) - router.getAsync('/test/txEncoding', TestController.testTxEncoding) - router.getAsync('/test/testPinata', TestController.testPinata) - router.getAsync('/test/callback', TestController.testCallback) - router.postAsync('/test/callback', TestController.testCallback) - router.getAsync('/test/summary', TestController.testSummary) - router.getAsync('/test/decrypt', TestController.testDecrypt) + router.getAsync('/test/email', TestController.testEmail) } } diff --git a/src/services/Aws.ts b/src/services/Aws.ts new file mode 100644 index 00000000..79ae3c6b --- /dev/null +++ b/src/services/Aws.ts @@ -0,0 +1,47 @@ +import AWS from 'aws-sdk' +import { transactionalTemplate } from '../lib/emailTemplate' + +export default class AwsService { + public static async sendEmail(options: { + to: string[] + from?: string + subject: string + body: string + }) { + const { to, from, subject, body } = options + const ses = new AWS.SES({ + region: 'us-east-1', + + credentials: { + accessKeyId: config.APP_AWS_ACCESS_KEY_ID, + secretAccessKey: config.APP_AWS_SECRET_ACCESS_KEY + } + }) + const params = { + Destination: { + ToAddresses: to + }, + Message: { + Body: { + Html: { + Charset: 'UTF-8', + Data: transactionalTemplate({ + subject, + inboxPreview: "Here's an inbox preview of the message." + }) + // Data: body + } + }, + Subject: { + Charset: 'UTF-8', + Data: subject + } + }, + Source: from ?? 'Meem ' + } + + const result = await ses.sendEmail(params).promise() + + return result + } +} diff --git a/src/services/Ethers.ts b/src/services/Ethers.ts index 706b2cde..b6441a61 100644 --- a/src/services/Ethers.ts +++ b/src/services/Ethers.ts @@ -196,6 +196,7 @@ export default class EthersService { fromVersion: IFacetVersion[] toVersion: IFacetVersion[] }) { + throw new Error('DEPRECATED') const { chainId, tableName, @@ -250,6 +251,7 @@ export default class EthersService { contractAddress?: string parentContractTxtId?: string }) { + throw new Error('DEPRECATED') const { chainId, abi, @@ -301,6 +303,7 @@ export default class EthersService { bytecode: string args: any[] }) { + throw new Error('DEPRECATED') const { chainId, abi, bytecode, args } = options const txId = uuidv4() @@ -334,6 +337,7 @@ export default class EthersService { /** Necessary when QueueEvent == DeploySafe */ agreementId?: string }) { + throw new Error('DEPRECATED') const { abi, functionSignature, @@ -383,6 +387,8 @@ export default class EthersService { /** Explicitly set the gas limit */ gasLimit?: ethers.BigNumberish | Promise | undefined }): Promise> { + throw new Error('DEPRECATED') + const { chainId, fn, params, gasLimit } = options try { await this.acquireLock(chainId) diff --git a/src/services/Guild.ts b/src/services/Guild.ts deleted file mode 100644 index 73324772..00000000 --- a/src/services/Guild.ts +++ /dev/null @@ -1,754 +0,0 @@ -// import { -// Chain, -// GetGuildResponse, -// guild, -// role as guildRole -// } from '@guildxyz/sdk' -// // eslint-disable-next-line import/no-extraneous-dependencies -// import AWS from 'aws-sdk' -// import type { Bytes } from 'ethers' -// import _ from 'lodash' -// import Agreement from '../models/Agreement' -// import AgreementGuild from '../models/AgreementGuild' -// import AgreementRole from '../models/AgreementRole' -// import Meem from '../models/AgreementToken' -// import { Mycontract__factory } from '../types/Meem' - -export default class GuildService { - // public static getGuildChain(chainId: number): Chain { - // switch (chainId) { - // case 1: - // return 'ETHEREUM' - // break - // case 4: - // return 'RINKEBY' as Chain - // break - // case 5: - // return 'GOERLI' - // break - // case 137: - // return 'POLYGON' - // break - // case 420: - // return 'OPTIMISM' - // break - // case 421613: - // return 'ARBITRUM' - // break - // default: - // return 'POLYGON' - // } - // } - // public static async createAgreementGuild(data: { - // agreementId: string - // adminAddresses?: string[] - // }): Promise<{ - // agreementGuild: AgreementGuild - // agreementRoles: AgreementRole[] - // }> { - // const { agreementId } = data - // let adminAddresses = data.adminAddresses - // const agreement = await orm.models.Agreement.findOne({ - // where: { - // id: agreementId - // }, - // include: [ - // { - // model: orm.models.AgreementGuild, - // include: [ - // { - // model: orm.models.AgreementRole - // } - // ] - // } - // ] - // }) - // if (!agreement) { - // throw new Error('AGREEMENT_NOT_FOUND') - // } - // if (agreement.AgreementGuild) { - // return { - // agreementGuild: agreement.AgreementGuild, - // agreementRoles: agreement.AgreementGuild?.AgreementRoles ?? [] - // } - // } - // const { wallet } = await services.ethers.getProvider({ - // chainId: agreement.chainId - // }) - // const sign = (signableMessage: string | Bytes) => - // wallet.signMessage(signableMessage) - // if (!adminAddresses) { - // const adminWallets = await orm.models.AgreementWallet.findAll({ - // where: { - // AgreementId: agreement.id, - // role: config.ADMIN_ROLE - // }, - // include: [orm.models.Wallet] - // }) - // adminAddresses = adminWallets.map(aw => aw.Wallet?.address ?? '') - // } - // adminAddresses = adminAddresses.filter( - // a => a !== '' && a !== wallet.address.toLowerCase() - // ) - // try { - // const guildChain = this.getGuildChain(agreement.chainId) - // const createGuildResponse = await guild.create(wallet.address, sign, { - // name: agreement.name, - // description: `Agreement Guild Details - Contract Address: ${agreement.address} | Chain ID: ${agreement.chainId}`, - // roles: [ - // { - // name: `Token Holder`, - // logic: 'OR', - // requirements: [ - // { - // type: 'ERC721', - // chain: guildChain, - // address: agreement.address, - // data: { - // minAmount: 1 - // } - // } - // ] - // } - // ] - // }) - // const agreementGuild = await orm.models.AgreementGuild.create({ - // guildId: createGuildResponse.id, - // AgreementId: agreement.id - // }) - // const guildResponse = await guild.get(createGuildResponse.id) - // const agreementRoles = await Promise.all( - // guildResponse.roles.map(async role => { - // const agreementRole = await orm.models.AgreementRole.create({ - // guildRoleId: role.id, - // name: role.name, - // AgreementId: agreement.id, - // AgreementGuildId: agreementGuild.id, - // isDefaultRole: true - // }) - // return agreementRole - // }) - // ) - // const adminRole = await this.createAgreementGuildRole({ - // name: 'Admin', - // agreement, - // agreementGuild, - // permissions: [ - // 'clubs.admin.editProfile', - // 'clubs.admin.manageMembershipSettings', - // 'clubs.admin.manageRoles', - // 'clubs.apps.manageApps', - // 'clubs.apps.viewApps' - // ], - // isTokenBasedRole: true, - // isTokenTransferrable: false, - // members: adminAddresses, - // senderWalletAddress: wallet.address, - // isAdminRole: true - // }) - // if (adminRole) { - // agreementRoles.push(adminRole) - // } - // return { - // agreementGuild, - // agreementRoles - // } - // } catch (e) { - // log.crit(e) - // throw new Error('SERVER_ERROR') - // } - // } - // public static async deleteAgreementGuild(data: { - // agreementId: string - // }): Promise { - // const { agreementId } = data - // const agreement = await orm.models.Agreement.findOne({ - // where: { - // id: agreementId - // }, - // include: [ - // { - // model: orm.models.AgreementGuild, - // include: [ - // { - // model: orm.models.AgreementRole, - // include: [ - // { - // model: orm.models.RolePermission - // } - // ] - // } - // ] - // } - // ] - // }) - // if (!agreement || !agreement.AgreementGuild) { - // throw new Error('AGREEMENT_NOT_FOUND') - // } - // const { wallet } = await services.ethers.getProvider({ - // chainId: agreement.chainId - // }) - // const sign = (signableMessage: string | Bytes) => - // wallet.signMessage(signableMessage) - // try { - // await guild.delete( - // agreement.AgreementGuild.guildId, - // wallet.address, - // sign, - // true - // ) - // const promises: Promise[] = [] - // const t = await orm.sequelize.transaction() - // promises.push( - // orm.models.AgreementRole.destroy({ - // where: { - // id: agreement.AgreementGuild.AgreementRoles?.map(role => role.id) - // }, - // transaction: t - // }) - // ) - // promises.push( - // orm.models.AgreementGuild.destroy({ - // where: { - // id: agreement.AgreementGuild.id - // }, - // transaction: t - // }) - // ) - // await Promise.all(promises) - // await t.commit() - // return - // } catch (e) { - // log.crit(e) - // throw new Error('SERVER_ERROR') - // } - // } - // public static async getAgreementGuild(data: { - // agreementId: string - // }): Promise { - // try { - // const agreementGuild = await orm.models.AgreementGuild.findOne({ - // where: { - // AgreementId: data.agreementId - // } - // }) - // if (!agreementGuild) { - // return null - // } - // const guildResponse = await guild.get(agreementGuild.guildId) - // return guildResponse - // } catch (e) { - // log.crit(e) - // throw new Error('SERVER_ERROR') - // } - // } - // public static async createAgreementGuildRole(data: { - // name: string - // agreement: Agreement - // agreementGuild: AgreementGuild - // permissions?: string[] - // isTokenBasedRole: boolean - // isTokenTransferrable?: boolean - // members: string[] - // senderWalletAddress: string - // isAdminRole?: boolean - // }): Promise { - // const { - // name, - // agreement, - // agreementGuild, - // permissions, - // isTokenBasedRole, - // isTokenTransferrable, - // members, - // senderWalletAddress, - // isAdminRole - // } = data - // const { wallet } = await services.ethers.getProvider({ - // chainId: agreement.chainId - // }) - // const sign = (signableMessage: string | Bytes) => - // wallet.signMessage(signableMessage) - // try { - // if (isTokenBasedRole) { - // const baseContract = Mycontract__factory.connect( - // agreement.address, - // wallet - // ) - // const admins = await baseContract.getRoles(config.ADMIN_ROLE) - // const roleContractData = { - // chainId: agreement.chainId, - // shouldMintTokens: true, - // metadata: { - // meem_metadata_type: 'Meem_AgreementRoleContract', - // meem_metadata_version: 'MeemClubRole_Contract_20220718', - // name: `${agreement.name ?? ''} - ${name}`, - // description: name, - // image: '', - // associations: [ - // { - // meem_contract_type: 'meem-club', - // address: agreement.address - // } - // ], - // external_url: '' - // }, - // name: `${agreement.name ?? ''} - ${name}`, - // admins, - // members, - // minters: admins, - // maxSupply: '0', - // // TODO: What do we want mintPermissions to be? - // mintPermissions: agreement.mintPermissions, - // splits: [], - // isTransferLocked: !isTokenTransferrable, - // tokenMetadata: { - // meem_metadata_version: 'MeemClubRole_Token_20220718', - // description: name, - // name: `${agreement.name ?? ''} - ${name}`, - // // TODO: Token image? - // image: '', - // associations: [], - // external_url: '' - // } - // } - // // log.debug(JSON.stringify(data)) - // log.debug(roleContractData) - // if (config.DISABLE_ASYNC_MINTING) { - // try { - // await services.agreement.createAgreement({ - // ...roleContractData, - // senderWalletAddress, - // agreementRoleData: { - // name, - // agreement, - // agreementGuild, - // permissions, - // isAdminRole - // } - // }) - // } catch (e) { - // log.crit(e) - // sockets?.emitError( - // config.errors.CONTRACT_CREATION_FAILED, - // senderWalletAddress - // ) - // } - // } else { - // const lambda = new AWS.Lambda({ - // accessKeyId: config.APP_AWS_ACCESS_KEY_ID, - // secretAccessKey: config.APP_AWS_SECRET_ACCESS_KEY, - // region: 'us-east-1' - // }) - // await lambda - // .invoke({ - // InvocationType: 'Event', - // FunctionName: config.LAMBDA_CREATE_CONTRACT_FUNCTION, - // Payload: JSON.stringify({ - // ...roleContractData, - // senderWalletAddress - // }) - // }) - // .promise() - // } - // } else { - // const guildChain = this.getGuildChain(agreement.chainId) - // const createGuildRoleResponse = await guildRole.create( - // wallet.address, - // sign, - // { - // guildId: agreementGuild.guildId, - // name, - // logic: 'AND', - // requirements: [ - // { - // type: 'ALLOWLIST', - // data: { - // addresses: members - // } - // }, - // { - // type: 'ERC721', - // chain: guildChain, - // address: agreement.address, - // data: { - // minAmount: 1 - // } - // } - // ] - // } - // ) - // const agreementRole = await orm.models.AgreementRole.create({ - // guildRoleId: createGuildRoleResponse.id, - // name, - // AgreementId: agreement.id, - // AgreementGuildId: agreementGuild.id - // }) - // if (!_.isUndefined(permissions) && _.isArray(permissions)) { - // const promises: Promise[] = [] - // const t = await orm.sequelize.transaction() - // const roleIdsToAdd = - // permissions.filter(pid => { - // const existingPermission = agreementRole.RolePermissions?.find( - // rp => rp.id === pid - // ) - // return !existingPermission - // }) ?? [] - // if (roleIdsToAdd.length > 0) { - // const agreementRolePermissionsData: { - // AgreementRoleId: string - // RolePermissionId: string - // }[] = roleIdsToAdd.map(rid => { - // return { - // AgreementRoleId: agreementRole.id, - // RolePermissionId: rid - // } - // }) - // promises.push( - // orm.models.AgreementRolePermission.bulkCreate( - // agreementRolePermissionsData, - // { - // transaction: t - // } - // ) - // ) - // } - // try { - // await Promise.all(promises) - // await t.commit() - // } catch (e) { - // log.crit(e) - // throw new Error('SERVER_ERROR') - // } - // } - // } - // } catch (e) { - // log.crit(e) - // throw new Error('SERVER_ERROR') - // } - // } - // public static async updateAgreementGuildRole(data: { - // name?: string - // agreementId: string - // guildRoleId: number - // members?: string[] - // guildRoleData?: { - // rolePlatforms?: { - // guildPlatform: { - // platformName: string - // platformGuildId: string - // isNew: boolean - // } - // platformRoleData?: { - // [key: string]: string - // } - // }[] - // } - // senderWalletAddress: string - // }): Promise { - // const { - // name, - // agreementId, - // guildRoleId, - // members, - // guildRoleData, - // senderWalletAddress - // } = data - // const agreement = await orm.models.Agreement.findOne({ - // where: { - // id: agreementId - // }, - // include: [ - // { - // model: orm.models.AgreementGuild, - // include: [ - // { - // model: orm.models.AgreementRole - // } - // ] - // } - // ] - // }) - // const agreementRole = agreement?.AgreementGuild?.AgreementRoles?.find( - // r => r.guildRoleId === guildRoleId - // ) - // if (!agreement || !agreementRole) { - // throw new Error('AGREEMENT_NOT_FOUND') - // } - // if ( - // _.isUndefined(name) && - // _.isUndefined(members) && - // _.isUndefined(guildRoleData) - // ) { - // return agreementRole - // } - // const { wallet } = await services.ethers.getProvider({ - // chainId: agreement.chainId - // }) - // const sign = (signableMessage: string | Bytes) => - // wallet.signMessage(signableMessage) - // try { - // const existingGuildRole = await guildRole.get(guildRoleId) - // const requirements = existingGuildRole.requirements - // const allowListRoleIndex = requirements.findIndex( - // r => r.type === 'ALLOWLIST' - // ) - // if (allowListRoleIndex > -1 && members) { - // requirements[allowListRoleIndex] = { - // type: 'ALLOWLIST', - // data: { - // addresses: members - // } - // } - // } - // if ((allowListRoleIndex > -1 && members) || name || guildRoleData) { - // const rolePlatforms: - // | { - // guildPlatform: { - // platformName: string - // platformGuildId: string - // isNew: boolean - // } - // platformRoleData?: { - // [key: string]: string - // } - // }[] - // | undefined = guildRoleData?.rolePlatforms - // ? guildRoleData.rolePlatforms - // : existingGuildRole.rolePlatforms?.map(rp => { - // return { - // guildPlatform: { - // platformGuildId: rp.guildPlatform.platformGuildId, - // platformName: rp.guildPlatform.platformName, - // isNew: false - // }, - // platformRoleData: rp.platformRoleData - // } - // }) - // await guildRole.update(guildRoleId, wallet.address, sign, { - // name: name ?? existingGuildRole.name, - // logic: existingGuildRole.logic, - // rolePlatforms, - // requirements - // }) - // } - // if (name) { - // await agreementRole.update({ - // name - // }) - // } - // if (members && agreementRole.tokenAddress) { - // const roleAgreement = await orm.models.Agreement.findOne({ - // where: { - // address: agreementRole.tokenAddress - // } - // }) - // // TODO: Allow other contracts besides Meem - // if (roleAgreement) { - // const memberMeems = await orm.models.AgreementToken.findAll({ - // where: { - // AgreementId: roleAgreement.id - // }, - // include: [ - // { - // model: orm.models.Wallet, - // as: 'Owner' - // } - // ] - // }) - // const removeMemberMeems: Meem[] = [] - // memberMeems.forEach(meem => { - // if (meem.Owner !== null) { - // if ( - // members.findIndex( - // m => m.toLowerCase() === meem.Owner.address.toLowerCase() - // ) < 0 - // ) { - // removeMemberMeems.push(meem) - // } - // } - // }) - // if (removeMemberMeems.length > 0) { - // // const roleContract = Mycontract__factory.connect( - // // roleAgreement.address, - // // wallet - // // ) - // log.debug( - // 'BURN MEMBER TOKENS', - // removeMemberMeems.map(m => m.id) - // ) - // // for (let i = 0; i < removeMemberMeems.length; i += 1) { - // // await roleContract.burn(removeMemberMeems[i].tokenId) - // // } - // } - // const membersToAdd: string[] = members.filter((m: string) => { - // const existingMemberIndex = memberMeems.findIndex( - // meem => meem.Owner?.address.toLowerCase() === m.toLowerCase() - // ) - // return existingMemberIndex < 0 - // }) - // log.debug('ADD MEMBERS', membersToAdd) - // // TODO: Do we need this to be async? - // // Need to get token metadata - // if (membersToAdd.length > 0) { - // const roleTokenMetadata = { - // meem_metadata_version: 'MeemClubRole_Token_20220718', - // name: `${agreement.name ?? ''} - ${name ?? agreementRole.name}`, - // description: name ?? agreementRole.name, - // image: '', - // associations: [ - // { - // meem_contract_type: 'meem-club', - // address: agreement.address - // } - // ], - // external_url: '' - // } - // const tokens = membersToAdd.map(a => { - // return { - // to: a, - // metadata: roleTokenMetadata - // } - // }) - // if (config.DISABLE_ASYNC_MINTING) { - // try { - // await services.meem.bulkMint({ - // tokens, - // mintedBy: senderWalletAddress, - // agreementId: agreement.id - // }) - // } catch (e) { - // log.crit(e) - // } - // } else { - // const lambda = new AWS.Lambda({ - // accessKeyId: config.APP_AWS_ACCESS_KEY_ID, - // secretAccessKey: config.APP_AWS_SECRET_ACCESS_KEY, - // region: 'us-east-1' - // }) - // await lambda - // .invoke({ - // InvocationType: 'Event', - // FunctionName: config.LAMBDA_AGREEMENT_BULK_MINT_FUNCTION, - // Payload: JSON.stringify({ - // tokens, - // mintedBy: senderWalletAddress, - // agreementId: agreement.id - // }) - // }) - // .promise() - // } - // } - // } - // } - // return agreementRole - // } catch (e) { - // // TODO: Re-create guild role if no longer exists? - // log.crit(e) - // throw new Error('SERVER_ERROR') - // } - // } - // public static async deleteAgreementGuildRole(data: { - // agreementId: string - // guildRoleId: number - // }): Promise { - // const { agreementId, guildRoleId } = data - // const agreement = await orm.models.Agreement.findOne({ - // where: { - // id: agreementId - // }, - // include: [ - // { - // model: orm.models.AgreementGuild, - // include: [ - // { - // model: orm.models.AgreementRole - // } - // ] - // } - // ] - // }) - // const agreementRole = agreement?.AgreementGuild?.AgreementRoles?.find( - // r => r.guildRoleId === guildRoleId - // ) - // if (!agreement || !agreementRole) { - // throw new Error('AGREEMENT_NOT_FOUND') - // } - // const { wallet } = await services.ethers.getProvider({ - // chainId: agreement.chainId - // }) - // const sign = (signableMessage: string | Bytes) => - // wallet.signMessage(signableMessage) - // try { - // await guildRole.delete(guildRoleId, wallet.address, sign) - // return - // } catch (e) { - // log.crit(e) - // throw new Error('SERVER_ERROR') - // } - // } - // public static async getUserGuilds(data: { - // walletAddress: string - // }): Promise { - // const guildMemberships = - // (await user.getMemberships(data.walletAddress)) ?? [] - // const guilds = await Promise.all( - // guildMemberships?.map(async gm => - // orm.models.Guild.findOne({ - // where: { - // guildId: gm.guildId - // } - // }) - // ) - // ) - // return guilds ?? [] - // } - // public static async getAgreementGuilds(data: { - // agreementId: string - // }): Promise { - // const agreement = await orm.models.Agreement.findOne({ - // where: { - // id: data.agreementId - // } - // }) - // if (!agreement) { - // throw new Error('AGREEMENT_NOT_FOUND') - // } - // const guilds = await orm.models.Guild.findAll({ - // include: [ - // { - // model: orm.models.Agreement, - // where: { - // id: data.agreementId - // } - // } - // ] - // }) - // const guildsData = await Promise.all(guilds.map(g => guild.get(g.guildId))) - // return guildsData - // } - // public static async getAgreementGuilds(data: { - // agreementId: string - // }): Promise { - // const agreement = await orm.models.Agreement.findOne({ - // where: { - // id: data.agreementId - // } - // }) - // if (!agreement) { - // throw new Error('AGREEMENT_NOT_FOUND') - // } - // const guilds = await orm.models.Guild.findAll({ - // include: [ - // { - // model: orm.models.Agreement, - // where: { - // id: data.agreementId - // } - // } - // ] - // }) - // const guildsData = await Promise.all(guilds.map(g => guild.get(g.guildId))) - // return guildsData - // } -} diff --git a/src/services/MeemId.ts b/src/services/MeemId.ts index 62ae3ea3..fa161ce2 100644 --- a/src/services/MeemId.ts +++ b/src/services/MeemId.ts @@ -76,8 +76,12 @@ export default class MeemIdentityService { /** Wallet signature */ signature?: string + + /** Optional invite code */ + inviteCode?: string }) { - const { attachToUser, accessToken, message, signature } = options + const { attachToUser, accessToken, message, signature, inviteCode } = + options let wallet: Wallet | undefined | null let user: User | undefined | null = attachToUser @@ -211,19 +215,46 @@ export default class MeemIdentityService { } if (!wallet) { - const { tokenId, address: pkpAddress } = await services.lit.mintPKP() - wallet = await orm.models.Wallet.create({ - address: pkpAddress, - UserId: user.id, - pkpTokenId: tokenId + throw new Error('MISSING_WALLET') + } + + if (inviteCode) { + const invite = await orm.models.Invite.findOne({ + where: { + code: inviteCode + } }) - user.DefaultWalletId = wallet.id - await user.save() - } + if (!invite) { + throw new Error('INVALID_NOT_FOUND') + } + + const agreementId = invite.AgreementId - if (wallet.pkpTokenId === null && wallet.ensFetchedAt === null) { - await this.updateENS(wallet) + const [agreementToken, tokenId] = await Promise.all([ + orm.models.AgreementToken.findOne({ + where: { + AgreementId: agreementId, + OwnerId: wallet.id + } + }), + orm.models.AgreementToken.count({ + where: { + AgreementId: agreementId + } + }) + ]) + + if (!agreementToken) { + await Promise.all([ + orm.models.AgreementToken.create({ + tokenId: services.web3.toBigNumber(tokenId + 1).toHexString(), + AgreementId: agreementId, + OwnerId: wallet.id + }), + invite.destroy() + ]) + } } return { diff --git a/src/services/Puppeteer.ts b/src/services/Puppeteer.ts deleted file mode 100644 index a2b471f1..00000000 --- a/src/services/Puppeteer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type Puppeteer from 'puppeteer-extra' - -export default class PuppeteerService { - public static shouldInitialize = true - - public puppeteer!: typeof Puppeteer - - public constructor() { - if (!config.ENABLE_PUPPETEER) { - log.debug('Puppeteer is disabled') - return - } - // eslint-disable-next-line - this.puppeteer = require('puppeteer-extra') - // eslint-disable-next-line - const StealthPlugin = require('puppeteer-extra-plugin-stealth') - this.puppeteer.use(StealthPlugin()) - // eslint-disable-next-line - const AdblockerPlugin = require('puppeteer-extra-plugin-adblocker') - this.puppeteer.use(AdblockerPlugin({ blockTrackers: true })) - } - - public getInstance() { - return this.puppeteer - } -} diff --git a/src/services/Scraper.ts b/src/services/Scraper.ts deleted file mode 100644 index 19b7f89e..00000000 --- a/src/services/Scraper.ts +++ /dev/null @@ -1,29 +0,0 @@ -export default class ScraperService { - public static async screenshotUrl(url: string): Promise { - const puppeteer = services.puppeteer.getInstance() - const browser = await puppeteer.launch({ - // headless: true, // debug only - args: ['--no-sandbox'] - }) - - const page = await browser.newPage() - - page.setViewport({ - width: 1024, - height: 768 - }) - - await page.goto(url, { - waitUntil: ['load', 'networkidle0', 'domcontentloaded'] - }) - - const buffer = await page.screenshot({ - fullPage: true, - type: 'jpeg' - }) - - await browser.close() - - return buffer - } -} diff --git a/src/services/Web3.ts b/src/services/Web3.ts index 993fa3f4..f05c1f0b 100644 --- a/src/services/Web3.ts +++ b/src/services/Web3.ts @@ -1,6 +1,5 @@ import { Readable } from 'stream' import Pinata, { PinataPinResponse } from '@pinata/sdk' -// import BigNumber from 'bignumber.js' import type { ethers as Ethers } from 'ethers' import request from 'superagent' import { MeemAPI } from '../types/meem.generated' @@ -155,17 +154,6 @@ export default class Web3 { public static toBigNumber(val: Ethers.BigNumberish): Ethers.BigNumber { const ethers = services.ethers.getInstance() const bn = ethers.BigNumber.from(val.toString()) - // let bigStr = bn.toString() - // let isNegative = false - // if (/^-/.test(bigStr)) { - // bigStr = bigStr.substr(1) - // isNegative = true - // } - // const ebn = ethers.BigNumber.from( - // `${isNegative ? '-' : ''}0x${bn.toHexString()}` - // ) - - // return ebn return bn } @@ -174,116 +162,6 @@ export default class Web3 { return pinata } - // public static async saveMeemMetadata(data: { - // imageBase64?: string - // image?: Buffer - // metadata: MeemAPI.IMeemMetadataLike | MeemAPI.ICreateMeemMetadata - // }): Promise<{ metadata: MeemAPI.IMeemMetadataLike; tokenURI: string }> { - // if (config.TESTING) { - // const imageURI = services.testing.getIpfsUrl() - // return { - // metadata: services.testing.getMeemMetadata({ - // image: imageURI, - // image_original: imageURI - // }), - // tokenURI: services.testing.getIpfsUrl() - // } - // } - - // this.validateCreateMeemMetadata(data.metadata) - - // const imgData = data.imageBase64 ?? data.image?.toString('base64') - - // if (!imgData) { - // throw new Error('INVALID_IMAGE_TYPE') - // } - - // const meemMetadata = data.metadata as MeemAPI.IMeemMetadataLike - - // const meemId = data.metadata.meem_id ?? uuidv4() - // const isValid = validateUUID(meemId) - - // if (!isValid) { - // throw new Error('INVALID_METADATA') - // } - - // // const buffStream = new stream.PassThrough() - // // buffStream.end(Buffer.from(imgData, 'base64')) - // const buff = Buffer.from(imgData, 'base64') - // const stream = Readable.from(buff) - // // @ts-ignore - // stream.path = `${meemId}/image.png` - - // const imageResponse = await this.saveToPinata({ - // // file: Readable.from(Buffer.from(imgData, 'base64')) - // // file: buffStream - // file: stream - // }) - - // const image = `ipfs://${imageResponse.IpfsHash}` - - // const externalUrl = - // meemMetadata.external_url ?? `${config.MEEM_DOMAIN}/meems/${meemId}` - - // const storedMetadata: MeemAPI.IMeemMetadataLike = { - // ...data.metadata, - // external_url: externalUrl, - // meem_id: meemId, - // image, - // image_original: - // meemMetadata.image && meemMetadata.image !== '' - // ? meemMetadata.image - // : image - // } - - // const metadataResponse = await this.saveToPinata({ - // json: storedMetadata - // }) - - // // return { - // // metadata: storedMetadata, - // // tokenURI: metadataPath - // // } - // return { - // metadata: storedMetadata, - // tokenURI: `ipfs://${metadataResponse.IpfsHash}` - // } - // } - - // public static validateCreateMeemMetadata(metadata: Record) { - // if (!metadata.name || !metadata.description) { - // throw new Error('INVALID_METADATA') - // } - // } - - public static async syncPins() { - const meems = await orm.models.AgreementToken.findAll({ limit: 5 }) - const pinata = this.getPinataInstance() - - for (let i = 0; i < meems.length; i += 1) { - const meem = meems[i] - const matches = meem.tokenURI.match(/ipfs:\/\/([^/]*)/) - if (matches && matches[1]) { - const ipfsHash = matches[1] - log.debug(`Pinning metadata w/ hash: ${ipfsHash}`) - // eslint-disable-next-line no-await-in-loop - await pinata.pinByHash(ipfsHash) - } else { - log.debug(`Skipping ${meem.tokenURI}`) - } - - const imgMatches = meem.metadata.image.match(/ipfs:\/\/([^/]*)/) - if (imgMatches && imgMatches[1]) { - const ipfsHash = imgMatches[1] - log.debug(`Pinning image w/ hash: ${ipfsHash}`) - // eslint-disable-next-line no-await-in-loop - await pinata.pinByHash(ipfsHash) - } else { - log.debug(`Skipping ${meem.metadata.image}`) - } - } - } - public static async saveToPinata(options: { json?: Record file?: Readable diff --git a/src/types/meem.generated.ts b/src/types/meem.generated.ts index 2151ae27..c6aa6112 100644 --- a/src/types/meem.generated.ts +++ b/src/types/meem.generated.ts @@ -1667,6 +1667,41 @@ export namespace ReInitializeAgreementRole { +/** Allows invitation via email and/or wallet addresses. In the case of wallet address, this will function the same as bulkMint */ +export namespace SendAgreementInvites { + export interface IPathParams { + /** The id of the agreement */ + agreementId: string + } + + export const path = (options: IPathParams) => + `/api/1.0/agreements/${options.agreementId}/invite` + + export const method = HttpMethod.Post + + export interface IQueryParams {} + + export interface IRequestBody { + /** Array of email and/or wallet addresses */ + to: string[] + } + + export interface IResponseBody extends IApiResponseBody { + status: 'success' + } + + export interface IDefinition { + pathParams: IPathParams + queryParams: IQueryParams + requestBody: IRequestBody + responseBody: IResponseBody + } + + export type Response = IResponseBody | IError +} + + + /** Set the agreement admin role */ export namespace SetAgreementAdminRole { export interface IPathParams { diff --git a/src/types/meem.public.generated.ts b/src/types/meem.public.generated.ts index c08b0cf4..90727fc1 100644 --- a/src/types/meem.public.generated.ts +++ b/src/types/meem.public.generated.ts @@ -1665,6 +1665,41 @@ export namespace ReInitializeAgreementRole { +/** Allows invitation via email and/or wallet addresses. In the case of wallet address, this will function the same as bulkMint */ +export namespace SendAgreementInvites { + export interface IPathParams { + /** The id of the agreement */ + agreementId: string + } + + export const path = (options: IPathParams) => + `/api/1.0/agreements/${options.agreementId}/invite` + + export const method = HttpMethod.Post + + export interface IQueryParams {} + + export interface IRequestBody { + /** Array of email and/or wallet addresses */ + to: string[] + } + + export interface IResponseBody extends IApiResponseBody { + status: 'success' + } + + export interface IDefinition { + pathParams: IPathParams + queryParams: IQueryParams + requestBody: IRequestBody + responseBody: IResponseBody + } + + export type Response = IResponseBody | IError +} + + + /** Set the agreement admin role */ export namespace SetAgreementAdminRole { export interface IPathParams { diff --git a/src/types/models.ts b/src/types/models.ts index a682e653..32786b1b 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -22,6 +22,7 @@ import ContractInstance from '../models/ContractInstance' import Discord from '../models/Discord' import Extension from '../models/Extension' import IdentityProvider from '../models/IdentityProvider' +import Invite from '../models/Invite' import Message from '../models/Message' import Rule from '../models/Rule' import Slack from '../models/Slack' @@ -57,6 +58,7 @@ export interface IModels { Discord: typeof Discord Extension: typeof Extension IdentityProvider: typeof IdentityProvider + Invite: typeof Invite Message: typeof Message UserIdentity: typeof UserIdentity Transaction: typeof Transaction diff --git a/src/types/services.d.ts b/src/types/services.d.ts index 7d5960d1..32acbdf8 100644 --- a/src/types/services.d.ts +++ b/src/types/services.d.ts @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import Agreement from '../services/Agreement' import Analytics from '../services/Analytics' +import Aws from '../services/Aws' import Child from '../services/Child' import ContractEvents from '../services/ContractEvents' import Data from '../services/Data' @@ -9,7 +10,6 @@ import Db from '../services/Db' import Discord from '../services/Discord' import Ethers from '../services/Ethers' import Git from '../services/Git' -import Guild from '../services/Guild' import Ipfs from '../services/Ipfs' import Lint from '../services/Lint' import Lit from '../services/Lit' @@ -17,10 +17,8 @@ import Meem from '../services/Meem' import MeemHelpDesk from '../services/MeemHelpdesk' import MeemId from '../services/MeemId' import Openai from '../services/Openai' -import Puppeteer from '../services/Puppeteer' import Queue from '../services/Queue' import Rule from '../services/Rule' -import Scraper from '../services/Scraper' import Slack from '../services/Slack' import Storage from '../services/Storage' import Testing from '../services/Testing' @@ -32,7 +30,7 @@ declare global { namespace services { let analytics: typeof Analytics let agreement: typeof Agreement - let agreementRole: typeof AgreementRole + let aws: typeof Aws let child: typeof Child let contractEvents: typeof ContractEvents let data: typeof Data diff --git a/src/types/shared/api/agreements/sendInvites.shared.ts b/src/types/shared/api/agreements/sendInvites.shared.ts new file mode 100644 index 00000000..3e972b50 --- /dev/null +++ b/src/types/shared/api/agreements/sendInvites.shared.ts @@ -0,0 +1,35 @@ +import { IError, HttpMethod, IApiResponseBody } from '../../api.shared' +import { IMeemMetadataLike } from '../../meem.shared' + +/** Allows invitation via email and/or wallet addresses. In the case of wallet address, this will function the same as bulkMint */ +export namespace SendAgreementInvites { + export interface IPathParams { + /** The id of the agreement */ + agreementId: string + } + + export const path = (options: IPathParams) => + `/api/1.0/agreements/${options.agreementId}/invite` + + export const method = HttpMethod.Post + + export interface IQueryParams {} + + export interface IRequestBody { + /** Array of email and/or wallet addresses */ + to: string[] + } + + export interface IResponseBody extends IApiResponseBody { + status: 'success' + } + + export interface IDefinition { + pathParams: IPathParams + queryParams: IQueryParams + requestBody: IRequestBody + responseBody: IResponseBody + } + + export type Response = IResponseBody | IError +} diff --git a/yarn.lock b/yarn.lock index 347cc86e..ad406bb0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -431,13 +431,6 @@ resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== -"@ethersproject/networks@5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.0.tgz#df72a392f1a63a57f87210515695a31a245845ad" - integrity sha512-MG6oHSQHd4ebvJrleEQQ4HhVu8Ichr0RDYEfHzsVAVjHNM+w36x9wp9r+hf1JstMXtseXDtkiVoARAG6M959AA== - dependencies: - "@ethersproject/logger" "^5.7.0" - "@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" @@ -460,32 +453,6 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.0.tgz#a885cfc7650a64385e7b03ac86fe9c2d4a9c2c63" - integrity sha512-+TTrrINMzZ0aXtlwO/95uhAggKm4USLm1PbeCBR/3XZ7+Oey+3pMyddzZEyRhizHpy1HXV0FRWRMI1O3EGYibA== - dependencies: - "@ethersproject/abstract-provider" "^5.7.0" - "@ethersproject/abstract-signer" "^5.7.0" - "@ethersproject/address" "^5.7.0" - "@ethersproject/base64" "^5.7.0" - "@ethersproject/basex" "^5.7.0" - "@ethersproject/bignumber" "^5.7.0" - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/constants" "^5.7.0" - "@ethersproject/hash" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - "@ethersproject/networks" "^5.7.0" - "@ethersproject/properties" "^5.7.0" - "@ethersproject/random" "^5.7.0" - "@ethersproject/rlp" "^5.7.0" - "@ethersproject/sha2" "^5.7.0" - "@ethersproject/strings" "^5.7.0" - "@ethersproject/transactions" "^5.7.0" - "@ethersproject/web" "^5.7.0" - bech32 "1.1.4" - ws "7.4.6" - "@ethersproject/providers@5.7.1": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.1.tgz#b0799b616d5579cd1067a8ebf1fc1ec74c1e122c" @@ -641,17 +608,6 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" -"@ethersproject/web@5.7.0": - version "5.7.0" - resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.0.tgz#40850c05260edad8b54827923bbad23d96aac0bc" - integrity sha512-ApHcbbj+muRASVDSCl/tgxaH2LBkRMEYfLOLVa0COipx0+nlu0QKet7U2lEg0vdkh8XRSLf2nd1f1Uk9SrVSGA== - dependencies: - "@ethersproject/base64" "^5.7.0" - "@ethersproject/bytes" "^5.7.0" - "@ethersproject/logger" "^5.7.0" - "@ethersproject/properties" "^5.7.0" - "@ethersproject/strings" "^5.7.0" - "@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" @@ -679,15 +635,6 @@ resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz" integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== -"@guildxyz/sdk@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@guildxyz/sdk/-/sdk-1.1.1.tgz#1ff8d3ff12e5003d9286e49b5a66de3bc5b10166" - integrity sha512-zyNCdxmKJgKfSsgtSRMdLAfLdXKkbOA1UXXXkA6PLJoiso4fzKtQUCnLyOka8urTBcvueCapSXVeLWNN6KmiRg== - dependencies: - axios "^0.26.1" - ethers "^5.5.4" - fast-json-stable-stringify "^2.1.0" - "@humanwhocodes/config-array@^0.11.8": version "0.11.8" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" @@ -2662,7 +2609,7 @@ aws-lambda@^1.0.7: js-yaml "^3.14.1" watchpack "^2.0.0-beta.10" -aws-sdk@^2.1195.0, aws-sdk@^2.1225.0, aws-sdk@^2.814.0: +aws-sdk@^2.1195.0, aws-sdk@^2.814.0: version "2.1225.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1225.0.tgz#5747cd1842f461300fb1b8d034c7ef1ae14185a1" integrity sha512-JYYYVpr4EktsohOrO8+KfhmHGt2x2zi20Ypkrtllhrh6fhtosduqpH5cQF7wYX/zEvjqduv0dHYd3sXCROHP8A== @@ -2678,6 +2625,22 @@ aws-sdk@^2.1195.0, aws-sdk@^2.1225.0, aws-sdk@^2.814.0: uuid "8.0.0" xml2js "0.4.19" +aws-sdk@^2.1378.0: + version "2.1378.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1378.0.tgz#a6ba9785bdc2677864b77ff757e9dbca0e65e7d3" + integrity sha512-9ZY11zkc3nMSejrrj08NdL+7hgiY7GZE3Sk7eVhH4VAJeoNqAMAyxc61Sg9yi83Y1i5rRWIcIgX9CYwenokyWQ== + dependencies: + buffer "4.9.2" + events "1.1.1" + ieee754 "1.1.13" + jmespath "0.16.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + util "^0.12.4" + uuid "8.0.0" + xml2js "0.5.0" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" @@ -3486,32 +3449,16 @@ color-name@1.1.3: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - color-support@^1.1.2, color-support@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" - integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== - dependencies: - color-convert "^2.0.1" - color-string "^1.9.0" - colors@^1.1.2: version "1.4.0" resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" @@ -4127,11 +4074,6 @@ detect-libc@^1.0.3: resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -detect-libc@^2.0.0, detect-libc@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== - devtools-protocol@0.0.1036444: version "0.0.1036444" resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.1036444.tgz#a570d3cdde61527c82f9b03919847b8ac7b1c2b9" @@ -4966,42 +4908,6 @@ ethers-multisend@^2.4.0: "@ethersproject/solidity" "^5.0.0" "@ethersproject/units" "^5.0.0" -ethers@^5.5.4: - version "5.7.0" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.0.tgz#0055da174b9e076b242b8282638bc94e04b39835" - integrity sha512-5Xhzp2ZQRi0Em+0OkOcRHxPzCfoBfgtOQA+RUylSkuHbhTEaQklnYi2hsWbRgs3ztJsXVXd9VKBcO1ScWL8YfA== - dependencies: - "@ethersproject/abi" "5.7.0" - "@ethersproject/abstract-provider" "5.7.0" - "@ethersproject/abstract-signer" "5.7.0" - "@ethersproject/address" "5.7.0" - "@ethersproject/base64" "5.7.0" - "@ethersproject/basex" "5.7.0" - "@ethersproject/bignumber" "5.7.0" - "@ethersproject/bytes" "5.7.0" - "@ethersproject/constants" "5.7.0" - "@ethersproject/contracts" "5.7.0" - "@ethersproject/hash" "5.7.0" - "@ethersproject/hdnode" "5.7.0" - "@ethersproject/json-wallets" "5.7.0" - "@ethersproject/keccak256" "5.7.0" - "@ethersproject/logger" "5.7.0" - "@ethersproject/networks" "5.7.0" - "@ethersproject/pbkdf2" "5.7.0" - "@ethersproject/properties" "5.7.0" - "@ethersproject/providers" "5.7.0" - "@ethersproject/random" "5.7.0" - "@ethersproject/rlp" "5.7.0" - "@ethersproject/sha2" "5.7.0" - "@ethersproject/signing-key" "5.7.0" - "@ethersproject/solidity" "5.7.0" - "@ethersproject/strings" "5.7.0" - "@ethersproject/transactions" "5.7.0" - "@ethersproject/units" "5.7.0" - "@ethersproject/wallet" "5.7.0" - "@ethersproject/web" "5.7.0" - "@ethersproject/wordlists" "5.7.0" - ethers@^5.6.4, ethers@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.1.tgz#48c83a44900b5f006eb2f65d3ba6277047fd4f33" @@ -5138,11 +5044,6 @@ exit-on-epipe@~1.0.1: resolved "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz" integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== -expand-template@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz" - integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== - express@^4.18.1: version "4.18.1" resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" @@ -5269,7 +5170,7 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -5819,11 +5720,6 @@ git-log-parser@^1.2.0: through2 "~2.0.0" traverse "~0.6.6" -github-from-package@0.0.0: - version "0.0.0" - resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz" - integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= - glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" @@ -6366,11 +6262,6 @@ is-arrayish@^0.2.1: resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" @@ -7824,7 +7715,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: +minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -7904,7 +7795,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: +mkdirp-classic@^0.5.2: version "0.5.3" resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== @@ -8079,11 +7970,6 @@ nanoid@3.3.3: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== -napi-build-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz" - integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== - native-promise-only@^0.8.1: version "0.8.1" resolved "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz" @@ -8151,13 +8037,6 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-abi@^3.3.0: - version "3.25.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.25.0.tgz#ca57dd23ae67679ce152b6c45cae2c57ed04faff" - integrity sha512-p+0xx5ruIQ+8X57CRIMxbTZRT7tU0Tjn2C/aAK68AEMrbGsCo6IjnDdPNhEyyjWCT4bRtzomXchYd3sSgk3BJQ== - dependencies: - semver "^7.3.5" - node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" @@ -8173,11 +8052,6 @@ node-addon-api@^4.2.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== -node-addon-api@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" - integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== - node-dir@^0.1.17: version "0.1.17" resolved "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz" @@ -9100,24 +8974,6 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prebuild-install@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" - integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== - dependencies: - detect-libc "^2.0.0" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^1.0.1" - node-abi "^3.3.0" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^4.0.0" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -9440,7 +9296,7 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.2.7, rc@^1.2.8: +rc@^1.2.8: version "1.2.8" resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -10103,20 +9959,6 @@ shallow-clone@^0.1.2: lazy-cache "^0.2.3" mixin-object "^2.0.1" -sharp@^0.31.0: - version "0.31.0" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.31.0.tgz#ce9b5202a5667486721cf07fd5b52360b1c2275a" - integrity sha512-ft96f8WzGxavg0rkLpMw90MTPMUZDyf0tHjPPh8Ob59xt6KzX8EqtotcqZGUm7kwqpX2pmYiyYX2LL0IZ/FDEw== - dependencies: - color "^4.2.3" - detect-libc "^2.0.1" - node-addon-api "^5.0.0" - prebuild-install "^7.1.1" - semver "^7.3.7" - simple-get "^4.0.1" - tar-fs "^2.1.1" - tunnel-agent "^0.6.0" - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz" @@ -10184,20 +10026,6 @@ signale@^1.2.1: figures "^2.0.0" pkg-conf "^2.1.0" -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^4.0.0, simple-get@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" - integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== - dependencies: - decompress-response "^6.0.0" - once "^1.3.1" - simple-concat "^1.0.0" - simple-git@^3.7.0: version "3.14.1" resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.14.1.tgz#68018a5f168f8a568862e30b692004b37c3b5ced" @@ -10207,13 +10035,6 @@ simple-git@^3.7.0: "@kwsites/promise-deferred" "^1.1.1" debug "^4.3.4" -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - siwe@^2.0.5: version "2.1.3" resolved "https://registry.yarnpkg.com/siwe/-/siwe-2.1.3.tgz#3048f6d0c1534d4edfb074efddc3f41939472421" @@ -10773,7 +10594,7 @@ table-layout@^1.0.2: typical "^5.2.0" wordwrapjs "^4.0.0" -tar-fs@2.1.1, tar-fs@^2.0.0, tar-fs@^2.1.1: +tar-fs@2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz" integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== @@ -11716,6 +11537,19 @@ xml2js@0.4.19: sax ">=0.6.0" xmlbuilder "~9.0.1" +xml2js@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" + integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + xmlbuilder@~9.0.1: version "9.0.7" resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz" From 182a811d1b57e86a18893a360c6aed27046e88fb Mon Sep 17 00:00:00 2001 From: Ken Goldfarb Date: Tue, 16 May 2023 15:24:04 -0600 Subject: [PATCH 02/10] feat: send and accept invites --- src/config/errors.ts | 6 ++ src/controllers/AgreementController.ts | 56 +++++++++++ src/controllers/TestController.ts | 18 ++++ src/lib/emailTemplate.ts | 96 +++---------------- src/routers/apiRouter.ts | 11 +++ src/services/Agreement.ts | 53 ++++++++++ src/services/Aws.ts | 14 +-- src/services/MeemId.ts | 38 +------- src/types/meem.generated.ts | 32 +++++++ src/types/meem.public.generated.ts | 32 +++++++ .../api/agreements/acceptInvite.shared.ts | 32 +++++++ 11 files changed, 264 insertions(+), 124 deletions(-) create mode 100644 src/types/shared/api/agreements/acceptInvite.shared.ts diff --git a/src/config/errors.ts b/src/config/errors.ts index fe3a5a93..34e8e1c2 100644 --- a/src/config/errors.ts +++ b/src/config/errors.ts @@ -475,6 +475,12 @@ const errors = { 'Something went wrong minting. Check that you have the correct permissions and try again.', friendlyReason: 'Something went wrong minting. Check that you have the correct permissions and try again.' + }, + INVITE_NOT_FOUND: { + httpCode: 404, + status: 'failure', + reason: 'The invite could not be found', + friendlyReason: 'The invite could not be found' } } diff --git a/src/controllers/AgreementController.ts b/src/controllers/AgreementController.ts index 0c48c6b7..a9e14a3e 100644 --- a/src/controllers/AgreementController.ts +++ b/src/controllers/AgreementController.ts @@ -1,6 +1,8 @@ import { ethers } from 'ethers' import { Response } from 'express' import { Op } from 'sequelize' +import { v4 as uuidv4 } from 'uuid' +import { transactionalTemplate } from '../lib/emailTemplate' import { IAuthenticatedRequest, IRequest, IResponse } from '../types/app' import { MeemAPI } from '../types/meem.generated' export default class AgreementController { @@ -477,6 +479,7 @@ export default class AgreementController { if (!agreementToken) { newAgreementTokens.push({ + id: uuidv4(), tokenId, AgreementId: agreement.id, OwnerId: wallet.id @@ -487,9 +490,62 @@ export default class AgreementController { } await orm.models.AgreementToken.bulkCreate(newAgreementTokens) + const invitesData: Record[] = [] + const subject = `You have been invited to join ${agreement.name}` + + for (let i = 0; i < emails.length; i++) { + const email = emails[i] + + const code = uuidv4() + + invitesData.push({ + id: uuidv4(), + code, + AgreementId: agreement.id + }) + + await services.aws.sendEmail({ + to: [email], + subject, + body: transactionalTemplate({ + bodyText: `Click the button below to accept the invite and join ${agreement.name}`, + ctaText: 'Accept Invite', + ctaUrl: `${config.MEEM_DOMAIN}/invite?code=${code}`, + subject, + title: `Join ${agreement.name}` + }) + }) + } + + await orm.models.Invite.bulkCreate(invitesData) return res.json({ status: 'success' }) } + + public static async acceptInvite( + req: IAuthenticatedRequest, + res: IResponse + ): Promise { + if (!req.wallet) { + throw new Error('USER_NOT_LOGGED_IN') + } + + const { code } = req.body + + const { agreement, agreementToken } = await services.agreement.acceptInvite( + { + code, + wallet: req.wallet + } + ) + + return res.json({ + agreementId: agreement.id, + agreementTokenId: agreementToken.id, + name: agreement.name, + slug: agreement.slug + }) + } } diff --git a/src/controllers/TestController.ts b/src/controllers/TestController.ts index d7ae4281..bc8ecd1b 100644 --- a/src/controllers/TestController.ts +++ b/src/controllers/TestController.ts @@ -1,4 +1,5 @@ import { Request, Response } from 'express' +import { transactionalTemplate } from '../lib/emailTemplate' export default class TestController { public static async releaseLock(req: Request, res: Response) { @@ -38,4 +39,21 @@ export default class TestController { status: 'success' }) } + + public static async testEmailHtml( + req: Request, + res: Response + ): Promise { + const html = transactionalTemplate({ + subject: 'Testing email', + inboxPreview: "Here's an inbox preview of the message.", + title: 'Testing email', + bodyText: + 'Ut reprehenderit qui. Animi numquam laborum est recusandae. Sequi iusto nisi assumenda vel est numquam aut. Eius adipisci voluptatem distinctio magnam sed nemo non quas eligendi. Facilis officia et et deleniti consequuntur vel.\n\nLibero magni rem vel. Quo est dignissimos ea ut enim et qui. Dignissimos ratione qui quisquam fuga quos ut delectus quia. Quia nihil a voluptatem ullam. Et veniam maiores consequatur vel et odit et repellat. Quasi accusamus quis.\n\n Quo eius rerum voluptatum doloremque qui nisi explicabo ipsam ipsa. Eum iste molestiae qui facilis velit nisi alias molestias sint. Voluptatem voluptatem rerum consequatur eum quidem perspiciatis. Voluptatum iure modi qui alias eius distinctio at.', + ctaText: 'Click here to do something', + ctaUrl: 'https://meem.wtf' + }) + + return res.send(html) + } } diff --git a/src/lib/emailTemplate.ts b/src/lib/emailTemplate.ts index 887481a8..a6f834f0 100644 --- a/src/lib/emailTemplate.ts +++ b/src/lib/emailTemplate.ts @@ -218,8 +218,11 @@ export const transactionalTemplate = (options: { color: #aaaaaa !important; } .darkmode-fullbleed-bg { - background-color: #0F3016 !important; + background-color: #F9FF15 !important; } + .footer-container p { + color: #111111 !important; + } } /* Dark Mode Styles : END */ @@ -319,84 +322,17 @@ export const transactionalTemplate = (options: { ` : '' } - - - -

Praesent in felis ut velit pretium lobortis rhoncus ut erat.

-
    -
  • A list item.
  • -
  • Another list item here.
  • -
  • Everyone gets a list item, list items for everyone!
  • -
-

Maecenas sed ante pellentesque, posuere leo id, eleifend dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent laoreet malesuada cursus. Maecenas scelerisque congue eros eu posuere. Praesent in felis ut velit pretium lobortis rhoncus ut erat.

- - + + + +   + + + - - - - - - - - - -
- - - - - - - -
- alt_text -
-

Maecenas sed ante pellentesque, posuere leo id, eleifend dolor.

-
-
- - - - - - - -
- alt_text -
-

Maecenas sed ante pellentesque, posuere leo id, eleifend dolor.

-
-
- - - - - - - -   - - - - - - - - - - - -
-

Maecenas sed ante pellentesque, posuere leo id, eleifend dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent laoreet malesuada cursus. Maecenas scelerisque congue eros eu posuere. Praesent in felis ut velit pretium lobortis rhoncus ut erat.

-
- - - - @@ -404,11 +340,7 @@ export const transactionalTemplate = (options: { @@ -422,7 +354,7 @@ export const transactionalTemplate = (options: { - +