Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion openapi.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"swagger": "2.0",
"info": {
"version": "1.26.16",
"version": "1.26.18",
"title": "THX API Specification",
"description": "User guides are available at https://docs.thx.network."
},
Expand Down Expand Up @@ -576,6 +576,11 @@
],
"description": "",
"parameters": [
{
"name": "forceSync",
"in": "query",
"type": "string"
},
{
"name": "body",
"in": "body",
Expand Down Expand Up @@ -752,6 +757,11 @@
],
"description": "",
"parameters": [
{
"name": "forceSync",
"in": "query",
"type": "string"
},
{
"name": "body",
"in": "body",
Expand Down
59 changes: 59 additions & 0 deletions src/jobs/reminders/01_accountCreatedNoTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { TransactionalEmail } from '@/models/TransactionalEmail';
import { EVENT_SEND_REMINDER_ACCOUNT_CREATED_NO_TOKENS, MAX_REMINDERS } from '@/util/agenda';
import ERC20 from '../../models/ERC20';
import { ERC721 } from '../../models/ERC721';
import AccountProxy from '../../proxies/AccountProxy';
import { logger } from '../../util/logger';
import { sendEmail } from './utils';

export const reminderJobAccountCreatedNoTokens = async () => {
const reminderType = EVENT_SEND_REMINDER_ACCOUNT_CREATED_NO_TOKENS;
try {
logger.info(`START REMINDER JOB - ${reminderType}`);

// Account created but no Tokens or Collectibles
const accounts = await AccountProxy.getActiveAccountsEmail();
const promises = accounts.map(async (account) => {
const now = Date.now();
const maxInactivityDays = 7;

if ((now - new Date(account.createdAt).getTime()) / (24 * 60 * 60 * 1000) < maxInactivityDays) {
logger.info('ACCOUNT REGISTERED LESS THAN 7 DAYS AGO');
return;
}

const numERC20s = await ERC20.count({ sub: account.id });
const numERC721s = await ERC721.count({ sub: account.id });

if (numERC20s + numERC721s == 0) {
const numEmailSent = await TransactionalEmail.count({ sub: account.id, type: reminderType });

if (numEmailSent >= MAX_REMINDERS) {
logger.info('MAXIMUM NUMBER OF EMAILS REACHED.');
return;
}

const subject = 'THX Reminder';
const title = 'Account created but no Tokens or Collectibles';
const message = 'Email with tips on how to deploy assets';

await sendEmail({ to: account.email, subject, title, message });

await TransactionalEmail.create({
sub: account.id,
type: reminderType,
});
logger.info('EMAIL SENT');
}
});
if (promises.length) {
await Promise.all(promises);
} else {
logger.info('NO ACCOUNTS TO PROCESS.');
}

logger.info('END REMINDER JOB');
} catch (err) {
logger.error(`ERROR on Reminders Job - ${reminderType}`, err);
}
};
52 changes: 52 additions & 0 deletions src/jobs/reminders/02_tokensCreatedNoPools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import ERC20 from '../../models/ERC20';
import { ERC721 } from '../../models/ERC721';
import AccountProxy from '../../proxies/AccountProxy';
import { AssetPool } from '../../models/AssetPool';
import { logger } from '../../util/logger';
import { sendEmail } from './utils';
import { TransactionalEmail } from '@/models/TransactionalEmail';
import { EVENT_SEND_REMINDER_TOKENS_CREATED_NO_POOLS, MAX_REMINDERS } from '@/util/agenda';

export const reminderJobTokensCreatedNoPools = async () => {
const reminderType = EVENT_SEND_REMINDER_TOKENS_CREATED_NO_POOLS;
try {
logger.info(`START REMINDER JOB - ${reminderType}`);

// Account and tokens / collectibles created but no pools
const accounts = await AccountProxy.getActiveAccountsEmail();
const promises = accounts.map(async (account) => {
const numERC20s = await ERC20.count({ sub: account.id });
const numERC721s = await ERC721.count({ sub: account.id });

if (numERC20s + numERC721s == 0) {
return;
}

const pools = await AssetPool.find({ sub: account.id });

if (pools.length == 0) {
const numEmailSent = await TransactionalEmail.count({ sub: account.id, type: reminderType });

if (numEmailSent >= MAX_REMINDERS) {
logger.info('MAXIMUM NUMBER OF EMAILS REACHED.');
return;
}

const subject = 'THX Reminder';
const title = 'Account and tokens / collectibles created but no pools to start using features';
const message = 'Email with tips on how to config pool';

await sendEmail({ to: account.email, subject, title, message });
await TransactionalEmail.create({ sub: account.id, type: reminderType });
logger.info('EMAIL SENT');
}
});
if (promises.length) {
await Promise.all(promises);
} else {
logger.info('NO ACCOUNTS TO PROCESS.');
}
} catch (err) {
logger.error(`ERROR on Reminders Job - ${reminderType}`, err);
}
};
61 changes: 61 additions & 0 deletions src/jobs/reminders/03_poolsCreatedNoWithdrawals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import AccountProxy from '../../proxies/AccountProxy';
import { AssetPool } from '../../models/AssetPool';
import { Withdrawal } from '../../models/Withdrawal';
import { logger } from '../../util/logger';
import { sendEmail } from './utils';
import { TransactionalEmail } from '@/models/TransactionalEmail';
import { EVENT_SEND_REMINDER_POOLS_CREATED_NO_WITHDRAWALS, MAX_REMINDERS } from '@/util/agenda';

export const reminderJobPoolsCreatedNoWithdrawals = async () => {
const reminderType = EVENT_SEND_REMINDER_POOLS_CREATED_NO_WITHDRAWALS;
try {
logger.info(`START REMINDER JOB - ${reminderType}`);

// Pool created for tokens / collectibles but no withdrawals
const pools = await AssetPool.find();

const promises = pools.map(async (pool) => {
const numWithdrawals = await Withdrawal.count({ poolId: pool._id });
if (numWithdrawals > 0) {
return;
}
const account = await AccountProxy.getById(pool.sub);
if (!account) {
logger.error('POOL ACCOUNT NOT FOUND', { poolId: pool._id });
return;
}
if (!account.active) {
logger.info('POOL ACCOUNT NOT ACTIVE');
return;
}
if (!account.email) {
logger.error('ACCOUNT EMAIL NOT SET', { accountId: account._id });
return;
}

const numEmailSent = await TransactionalEmail.count({ sub: account.id, type: reminderType });

if (numEmailSent >= MAX_REMINDERS) {
logger.info('MAXIMUM NUMBER OF EMAILS REACHED.');
return;
}

const subject = 'THX Reminder';
const title = 'Pool created for tokens / collectibles but no withdrawals for that pool';
const message = 'Email with tips on how to config rewards etc';

await sendEmail({ to: account.email, subject, title, message });
await TransactionalEmail.create({ sub: account.id, type: reminderType });
logger.info('EMAIL SENT');
});

if (promises.length) {
await Promise.all(promises);
} else {
logger.info('NO POOLS TO PROCESS.');
}
logger.info('END REMINDER JOB');
} catch (err) {
logger.error(`ERROR on Reminders Job - ${reminderType}`, err);
}
};
18 changes: 18 additions & 0 deletions src/jobs/reminders/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import MailService from '../../services/MailService';
import ejs from 'ejs';
import { API_URL, DASHBOARD_URL } from '@/config/secrets';

export async function sendEmail(data: { to: string; subject: string; title: string; message: string }) {
const html = await ejs.renderFile(
'./src/templates/email/reminder.ejs',
{
title: data.title,
message: data.message,
baseUrl: API_URL,
dashboardUrl: DASHBOARD_URL,
},
{ async: true },
);

return await MailService.send(data.to, data.subject, html);
}
1 change: 1 addition & 0 deletions src/models/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface IAccount {
twitter?: any;
plan: AccountPlanType;
email: string;
createdAt?: Date;
}
export interface ERC20Token {
chainId: ChainId;
Expand Down
17 changes: 17 additions & 0 deletions src/models/TransactionalEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { TTransactionalEmail } from '@/types/TTransactionalEmail';
import mongoose from 'mongoose';

export type TransactionalEmailDocument = mongoose.Document & TTransactionalEmail;

const transactionalEmailSchema = new mongoose.Schema(
{
type: String,
sub: String,
},
{ timestamps: true },
);

export const TransactionalEmail = mongoose.model<TransactionalEmailDocument>(
'TransactionalEmail',
transactionalEmailSchema,
);
11 changes: 11 additions & 0 deletions src/proxies/AccountProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ export default class AccountProxy {
return data;
}

static async getActiveAccountsEmail(): Promise<IAccount[]> {
const { data } = await authClient({
method: 'GET',
url: 'account/emails',
headers: {
Authorization: await getAuthAccessToken(),
},
});
return data;
}

static async isEmailDuplicate(email: string) {
try {
await authClient({
Expand Down
Loading