mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-14 18:27:44 +02:00
Server: Handle flags for accounts over limit
This commit is contained in:
parent
f922e9a239
commit
6e087bcb23
@ -2,7 +2,7 @@ import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models,
|
||||
import { EmailSender, User, UserFlagType } from '../services/database/types';
|
||||
import { ErrorUnprocessableEntity } from '../utils/errors';
|
||||
import { betaUserDateRange, stripeConfig } from '../utils/stripe';
|
||||
import { AccountType } from './UserModel';
|
||||
import { accountByType, AccountType } from './UserModel';
|
||||
import { failedPaymentDisableUploadInterval } from './SubscriptionModel';
|
||||
import { stripePortalUrl } from '../utils/urlUtils';
|
||||
|
||||
@ -203,4 +203,68 @@ describe('UserModel', function() {
|
||||
}
|
||||
});
|
||||
|
||||
test('should send emails when the account is over the size limit', async function() {
|
||||
const { user: user1 } = await createUserAndSession(1);
|
||||
const { user: user2 } = await createUserAndSession(2);
|
||||
|
||||
await models().user().save({
|
||||
id: user1.id,
|
||||
account_type: AccountType.Basic,
|
||||
total_item_size: accountByType(AccountType.Basic).max_total_item_size * 0.85,
|
||||
});
|
||||
|
||||
await models().user().save({
|
||||
id: user2.id,
|
||||
account_type: AccountType.Pro,
|
||||
total_item_size: accountByType(AccountType.Pro).max_total_item_size * 0.2,
|
||||
});
|
||||
|
||||
const emailBeforeCount = (await models().email().all()).length;
|
||||
|
||||
await models().user().handleOversizedAccounts();
|
||||
|
||||
const emailAfterCount = (await models().email().all()).length;
|
||||
|
||||
expect(emailAfterCount).toBe(emailBeforeCount + 1);
|
||||
|
||||
const email = (await models().email().all()).pop();
|
||||
expect(email.recipient_id).toBe(user1.id);
|
||||
expect(email.subject).toContain('80%');
|
||||
|
||||
{
|
||||
// Running it again should not send a second email
|
||||
await models().user().handleOversizedAccounts();
|
||||
expect((await models().email().all()).length).toBe(emailBeforeCount + 1);
|
||||
}
|
||||
|
||||
{
|
||||
// Now check that the 100% email is sent too
|
||||
|
||||
await models().user().save({
|
||||
id: user2.id,
|
||||
total_item_size: accountByType(AccountType.Pro).max_total_item_size * 1.1,
|
||||
});
|
||||
|
||||
// User upload should be enabled at this point
|
||||
expect((await models().user().load(user2.id)).can_upload).toBe(1);
|
||||
|
||||
const emailBeforeCount = (await models().email().all()).length;
|
||||
await models().user().handleOversizedAccounts();
|
||||
const emailAfterCount = (await models().email().all()).length;
|
||||
|
||||
// User upload should be disabled
|
||||
expect((await models().user().load(user2.id)).can_upload).toBe(0);
|
||||
|
||||
expect(emailAfterCount).toBe(emailBeforeCount + 1);
|
||||
const email = (await models().email().all()).pop();
|
||||
|
||||
expect(email.recipient_id).toBe(user2.id);
|
||||
expect(email.subject).toContain('100%');
|
||||
|
||||
// Running it again should not send a second email
|
||||
await models().user().handleOversizedAccounts();
|
||||
expect((await models().email().all()).length).toBe(emailBeforeCount + 1);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -16,6 +16,9 @@ import { betaStartSubUrl, betaUserDateRange, betaUserTrialPeriodDays, isBetaUser
|
||||
import endOfBetaTemplate from '../views/emails/endOfBetaTemplate';
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
import paymentFailedUploadDisabledTemplate from '../views/emails/paymentFailedUploadDisabledTemplate';
|
||||
import oversizedAccount1 from '../views/emails/oversizedAccount1';
|
||||
import oversizedAccount2 from '../views/emails/oversizedAccount2';
|
||||
import dayjs = require('dayjs');
|
||||
|
||||
const logger = Logger.create('UserModel');
|
||||
|
||||
@ -188,7 +191,7 @@ export default class UserModel extends BaseModel<User> {
|
||||
const maxItemSize = getMaxItemSize(user);
|
||||
const maxSize = maxItemSize * (itemIsEncrypted(item) ? 2.2 : 1);
|
||||
if (maxSize && itemSize > maxSize) {
|
||||
throw new ErrorPayloadTooLarge(_('Cannot save %s "%s" because it is larger than than the allowed limit (%s)',
|
||||
throw new ErrorPayloadTooLarge(_('Cannot save %s "%s" because it is larger than the allowed limit (%s)',
|
||||
isNote ? _('note') : _('attachment'),
|
||||
itemTitle ? itemTitle : item.name,
|
||||
formatBytes(maxItemSize)
|
||||
@ -369,6 +372,67 @@ export default class UserModel extends BaseModel<User> {
|
||||
});
|
||||
}
|
||||
|
||||
public async handleOversizedAccounts() {
|
||||
const alertLimit1 = 0.8;
|
||||
const alertLimitMax = 1;
|
||||
|
||||
const basicAccount = accountByType(AccountType.Basic);
|
||||
const proAccount = accountByType(AccountType.Pro);
|
||||
|
||||
const users: User[] = await this
|
||||
.db(this.tableName)
|
||||
.select(['id', 'total_item_size', 'max_total_item_size', 'account_type', 'email', 'full_name'])
|
||||
.where(function() {
|
||||
void this.whereRaw('total_item_size > ? AND account_type = ?', [alertLimit1 * basicAccount.max_total_item_size, AccountType.Basic])
|
||||
.orWhereRaw('total_item_size > ? AND account_type = ?', [alertLimit1 * proAccount.max_total_item_size, AccountType.Pro]);
|
||||
})
|
||||
// Users who are disabled or who cannot upload already received the
|
||||
// notification.
|
||||
.andWhere('enabled', '=', 1)
|
||||
.andWhere('can_upload', '=', 1);
|
||||
|
||||
const makeEmailKey = (user: User, alertLimit: number): string => {
|
||||
return [
|
||||
'oversizedAccount',
|
||||
user.account_type,
|
||||
alertLimit * 100,
|
||||
// Also add the month/date to the key so that we don't send more than one email a month
|
||||
dayjs(Date.now()).format('MMYY'),
|
||||
].join('::');
|
||||
};
|
||||
|
||||
await this.withTransaction(async () => {
|
||||
for (const user of users) {
|
||||
const maxTotalItemSize = getMaxTotalItemSize(user);
|
||||
const account = accountByType(user.account_type);
|
||||
|
||||
if (user.total_item_size > maxTotalItemSize * alertLimitMax) {
|
||||
await this.models().email().push({
|
||||
...oversizedAccount2({
|
||||
percentLimit: alertLimitMax * 100,
|
||||
url: this.baseUrl,
|
||||
}),
|
||||
...this.userEmailDetails(user),
|
||||
sender_id: EmailSender.Support,
|
||||
key: makeEmailKey(user, alertLimitMax),
|
||||
});
|
||||
|
||||
await this.models().userFlag().add(user.id, UserFlagType.AccountOverLimit);
|
||||
} else if (maxTotalItemSize > account.max_total_item_size * alertLimit1) {
|
||||
await this.models().email().push({
|
||||
...oversizedAccount1({
|
||||
percentLimit: alertLimit1 * 100,
|
||||
url: this.baseUrl,
|
||||
}),
|
||||
...this.userEmailDetails(user),
|
||||
sender_id: EmailSender.Support,
|
||||
key: makeEmailKey(user, alertLimit1),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private formatValues(user: User): User {
|
||||
const output: User = { ...user };
|
||||
if ('email' in output) output.email = user.email.trim().toLowerCase();
|
||||
|
@ -33,6 +33,10 @@ export default class CronService extends BaseService {
|
||||
cron.schedule('0 13 * * *', async () => {
|
||||
await runCronTask('handleFailedPaymentSubscriptions', async () => this.models.user().handleFailedPaymentSubscriptions());
|
||||
});
|
||||
|
||||
cron.schedule('0 14 * * *', async () => {
|
||||
await runCronTask('handleOversizedAccounts', async () => this.models.user().handleOversizedAccounts());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
28
packages/server/src/views/emails/oversizedAccount1.ts
Normal file
28
packages/server/src/views/emails/oversizedAccount1.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import config from '../../config';
|
||||
import { EmailSubjectBody } from '../../models/EmailModel';
|
||||
|
||||
interface TemplateView {
|
||||
percentLimit: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export default function(view: TemplateView): EmailSubjectBody {
|
||||
return {
|
||||
subject: `Your ${config().appName} account is over ${view.percentLimit}% full`,
|
||||
body: `
|
||||
|
||||
Your ${config().appName} account is over ${view.percentLimit}% full.
|
||||
|
||||
Please consider deleting notes or attachments before it reaches its limit.
|
||||
|
||||
Once the account is full it will no longer be possible to upload new notes to it.
|
||||
|
||||
If you have Pro account and would like to request more space, please contact us by replying to this email.
|
||||
|
||||
You may access your account by following this URL:
|
||||
|
||||
${view.url}
|
||||
|
||||
`.trim(),
|
||||
};
|
||||
}
|
26
packages/server/src/views/emails/oversizedAccount2.ts
Normal file
26
packages/server/src/views/emails/oversizedAccount2.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import config from '../../config';
|
||||
import { EmailSubjectBody } from '../../models/EmailModel';
|
||||
|
||||
interface TemplateView {
|
||||
percentLimit: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export default function(view: TemplateView): EmailSubjectBody {
|
||||
return {
|
||||
subject: `Your ${config().appName} account is over ${view.percentLimit}% full`,
|
||||
body: `
|
||||
|
||||
Your ${config().appName} account is over ${view.percentLimit}% full, and as a result it is not longer possible to upload new notes to it.
|
||||
|
||||
Please consider deleting notes or attachments so as to go below the limit.
|
||||
|
||||
If you have Pro account and would like to request more space, please contact us by replying to this email.
|
||||
|
||||
You may access your account by following this URL:
|
||||
|
||||
${view.url}
|
||||
|
||||
`.trim(),
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user