diff --git a/packages/server/src/models/UserModel.test.ts b/packages/server/src/models/UserModel.test.ts index 319225038..079c6bf44 100644 --- a/packages/server/src/models/UserModel.test.ts +++ b/packages/server/src/models/UserModel.test.ts @@ -20,7 +20,7 @@ describe('UserModel', function() { await beforeEachDb(); }); - test('should validate user objects', async function() { + test('should validate user objects', async () => { const { user: user1 } = await createUserAndSession(2, false); const { user: user2 } = await createUserAndSession(3, false); @@ -51,7 +51,7 @@ describe('UserModel', function() { expect(error instanceof ErrorUnprocessableEntity).toBe(true); }); - test('should delete a user', async function() { + test('should delete a user', async () => { const { session: session1, user: user1 } = await createUserAndSession(2, false); const userModel = models().user(); @@ -72,7 +72,7 @@ describe('UserModel', function() { expect((await models().userItem().all()).length).toBe(0); }); - test('should push an email when creating a new user', async function() { + test('should push an email when creating a new user', async () => { const { user: user1 } = await createUserAndSession(1); const { user: user2 } = await createUserAndSession(2); @@ -90,7 +90,7 @@ describe('UserModel', function() { expect(email.error).toBe(''); }); - test('should send a beta reminder email', async function() { + test('should send a beta reminder email', async () => { stripeConfig().enabled = true; const { user: user1 } = await createUserAndSession(1, false, { email: 'toto@example.com' }); const range = betaUserDateRange(); @@ -140,7 +140,7 @@ describe('UserModel', function() { stripeConfig().enabled = false; }); - test('should disable beta account once expired', async function() { + test('should disable beta account once expired', async () => { stripeConfig().enabled = true; const { user: user1 } = await createUserAndSession(1, false, { email: 'toto@example.com' }); const range = betaUserDateRange(); @@ -166,7 +166,7 @@ describe('UserModel', function() { stripeConfig().enabled = false; }); - test('should disable upload and send an email if payment failed recently', async function() { + test('should disable upload and send an email if payment failed recently', async () => { stripeConfig().enabled = true; const { user: user1 } = await models().subscription().saveUserAndSubscription('toto@example.com', 'Toto', AccountType.Basic, 'usr_111', 'sub_111'); @@ -207,7 +207,7 @@ describe('UserModel', function() { stripeConfig().enabled = false; }); - test('should disable disable the account and send an email if payment failed for good', async function() { + test('should disable disable the account and send an email if payment failed for good', async () => { stripeConfig().enabled = true; const { user: user1 } = await models().subscription().saveUserAndSubscription('toto@example.com', 'Toto', AccountType.Basic, 'usr_111', 'sub_111'); @@ -236,7 +236,7 @@ describe('UserModel', function() { stripeConfig().enabled = false; }); - test('should send emails when the account is over the size limit', async function() { + test('should send emails and flag accounts when it is over the size limit', async () => { const { user: user1 } = await createUserAndSession(1); const { user: user2 } = await createUserAndSession(2); @@ -287,6 +287,7 @@ describe('UserModel', function() { // User upload should be disabled expect((await models().user().load(user2.id)).can_upload).toBe(0); + expect(await models().userFlag().byUserId(user2.id, UserFlagType.AccountOverLimit)).toBeTruthy(); expect(emailAfterCount).toBe(emailBeforeCount + 1); const email = (await models().email().all()).pop(); @@ -300,4 +301,25 @@ describe('UserModel', function() { } }); + test('should remove flag when account goes under the limit', async () => { + const { user: user1 } = await createUserAndSession(1); + + await models().user().save({ + id: user1.id, + account_type: AccountType.Basic, + total_item_size: Math.round(accountByType(AccountType.Basic).max_total_item_size * 1.1), + }); + + await models().user().handleOversizedAccounts(); + expect(await models().userFlag().byUserId(user1.id, UserFlagType.AccountOverLimit)).toBeTruthy(); + + await models().user().save({ + id: user1.id, + total_item_size: Math.round(accountByType(AccountType.Basic).max_total_item_size * 0.5), + }); + + await models().user().handleOversizedAccounts(); + expect(await models().userFlag().byUserId(user1.id, UserFlagType.AccountOverLimit)).toBeFalsy(); + }); + }); diff --git a/packages/server/src/models/UserModel.ts b/packages/server/src/models/UserModel.ts index 5216ceac1..73efb1320 100644 --- a/packages/server/src/models/UserModel.ts +++ b/packages/server/src/models/UserModel.ts @@ -484,13 +484,22 @@ export default class UserModel extends BaseModel { const basicAccount = accountByType(AccountType.Basic); const proAccount = accountByType(AccountType.Pro); + const basicDefaultLimit1 = Math.round(alertLimit1 * basicAccount.max_total_item_size); + const proDefaultLimit1 = Math.round(alertLimit1 * proAccount.max_total_item_size); + const basicDefaultLimitMax = Math.round(alertLimitMax * basicAccount.max_total_item_size); + const proDefaultLimitMax = Math.round(alertLimitMax * proAccount.max_total_item_size); + + // ------------------------------------------------------------------------ + // First, find all the accounts that are over the limit and send an + // email to the owner. Also flag accounts that are over 100% full. + // ------------------------------------------------------------------------ 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 = ?', [Math.round(alertLimit1 * basicAccount.max_total_item_size), AccountType.Basic]) - .orWhereRaw('total_item_size > ? AND account_type = ?', [Math.round(alertLimit1 * proAccount.max_total_item_size), AccountType.Pro]); + void this.whereRaw('total_item_size > ? AND account_type = ?', [basicDefaultLimit1, AccountType.Basic]) + .orWhereRaw('total_item_size > ? AND account_type = ?', [proDefaultLimit1, AccountType.Pro]); }) // Users who are disabled or who cannot upload already received the // notification. @@ -536,7 +545,32 @@ export default class UserModel extends BaseModel { }); } } - }, 'UserModel::handleOversizedAccounts'); + }, 'UserModel::handleOversizedAccounts::1'); + + // ------------------------------------------------------------------------ + // Secondly, find all the accounts that have previously been flagged and + // that are now under the limit. Remove the flag from these accounts. + // ------------------------------------------------------------------------ + + const flaggedUsers = await this + .db({ f: 'user_flags' }) + .select(['u.id', 'u.total_item_size', 'u.max_total_item_size', 'u.account_type', 'u.email', 'u.full_name']) + .join({ u: 'users' }, 'u.id', 'f.user_id') + .where('f.type', '=', UserFlagType.AccountOverLimit) + .where(function() { + void this + .whereRaw('u.total_item_size < ? AND u.account_type = ?', [basicDefaultLimitMax, AccountType.Basic]) + .orWhereRaw('u.total_item_size < ? AND u.account_type = ?', [proDefaultLimitMax, AccountType.Pro]); + }); + + await this.withTransaction(async () => { + for (const user of flaggedUsers) { + const maxTotalItemSize = getMaxTotalItemSize(user); + if (user.total_item_size < maxTotalItemSize) { + await this.models().userFlag().remove(user.id, UserFlagType.AccountOverLimit); + } + } + }, 'UserModel::handleOversizedAccounts::2'); } private formatValues(user: User): User {