Skip to content
Merged
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
120 changes: 120 additions & 0 deletions backend/src/database/repositories/__tests__/userRepository.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import UserRepository from '../userRepository'
import SequelizeTestUtils from '../../utils/sequelizeTestUtils'
import Error404 from '../../../errors/Error404'
import Roles from '../../../security/roles'

const db = null

describe('UserRepository tests', () => {
beforeEach(async () => {
await SequelizeTestUtils.wipeDatabase(db)
})

afterAll((done) => {
// Closing the DB connection allows Jest to exit successfully.
SequelizeTestUtils.closeConnection(db)
done()
})

describe('findAllUsersOfTenant method', () => {
it('Should find all related users of a tenant successfully', async () => {
// Getting options already creates one random user
const mockIRepositoryOptions = await SequelizeTestUtils.getTestIRepositoryOptions(db)

let allUsersOfTenant = (
await UserRepository.findAllUsersOfTenant(mockIRepositoryOptions.currentTenant.id)
).map((u) => SequelizeTestUtils.objectWithoutKey(u, 'tenants'))

expect(allUsersOfTenant).toStrictEqual([
mockIRepositoryOptions.currentUser.get({ plain: true }),
])

// add more users to the test tenant
const randomUser2 = await SequelizeTestUtils.getRandomUser()
const user2 = await mockIRepositoryOptions.database.user.create(randomUser2)

await mockIRepositoryOptions.database.tenantUser.create({
roles: [Roles.values.admin],
status: 'active',
tenantId: mockIRepositoryOptions.currentTenant.id,
userId: user2.id,
})

allUsersOfTenant = (
await UserRepository.findAllUsersOfTenant(mockIRepositoryOptions.currentTenant.id)
).map((u) => SequelizeTestUtils.objectWithoutKey(u, 'tenants'))

expect(allUsersOfTenant).toStrictEqual([
mockIRepositoryOptions.currentUser.get({ plain: true }),
user2.get({ plain: true }),
])

const randomUser3 = await SequelizeTestUtils.getRandomUser()
const user3 = await mockIRepositoryOptions.database.user.create(randomUser3)

await mockIRepositoryOptions.database.tenantUser.create({
roles: [Roles.values.admin],
status: 'active',
tenantId: mockIRepositoryOptions.currentTenant.id,
userId: user3.id,
})

allUsersOfTenant = (
await UserRepository.findAllUsersOfTenant(mockIRepositoryOptions.currentTenant.id)
).map((u) => SequelizeTestUtils.objectWithoutKey(u, 'tenants'))

expect(allUsersOfTenant).toStrictEqual([
mockIRepositoryOptions.currentUser.get({ plain: true }),
user2.get({ plain: true }),
user3.get({ plain: true }),
])

// add other users and tenants that are non related to previous couples
await SequelizeTestUtils.getTestIRepositoryOptions(db)

// users of the previous tenant should be the same
allUsersOfTenant = (
await UserRepository.findAllUsersOfTenant(mockIRepositoryOptions.currentTenant.id)
).map((u) => SequelizeTestUtils.objectWithoutKey(u, 'tenants'))

expect(allUsersOfTenant).toStrictEqual([
mockIRepositoryOptions.currentUser.get({ plain: true }),
user2.get({ plain: true }),
user3.get({ plain: true }),
])

const tenantUsers = await mockIRepositoryOptions.database.tenantUser.findAll({
tenantId: mockIRepositoryOptions.currentTenant.id,
})

// remove last user added to the tenant
await tenantUsers[2].destroy({ force: true })

allUsersOfTenant = (
await UserRepository.findAllUsersOfTenant(mockIRepositoryOptions.currentTenant.id)
).map((u) => SequelizeTestUtils.objectWithoutKey(u, 'tenants'))

expect(allUsersOfTenant).toStrictEqual([
mockIRepositoryOptions.currentUser.get({ plain: true }),
user2.get({ plain: true }),
])

// remove first user added to the tenant
await tenantUsers[0].destroy({ force: true })

allUsersOfTenant = (
await UserRepository.findAllUsersOfTenant(mockIRepositoryOptions.currentTenant.id)
).map((u) => SequelizeTestUtils.objectWithoutKey(u, 'tenants'))

expect(allUsersOfTenant).toStrictEqual([user2.get({ plain: true })])

// remove the last remaining user from the tenant
await tenantUsers[1].destroy({ force: true })

// function now should be throwing Error404
await expect(() =>
UserRepository.findAllUsersOfTenant(mockIRepositoryOptions.currentTenant.id),
).rejects.toThrowError(new Error404())
})
})
})
25 changes: 25 additions & 0 deletions backend/src/database/repositories/userRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,31 @@ export default class UserRepository {
return this._populateRelations(record, options)
}

/**
* Finds all users of a tenant.
* @param tenantId
* @returns
*/
static async findAllUsersOfTenant(tenantId: string): Promise<any[]> {
const options = await SequelizeRepository.getDefaultIRepositoryOptions()

const records = await options.database.user.findAll({
include: [
{
model: options.database.tenantUser,
as: 'tenants',
where: { tenantId },
},
],
})

if (records.length === 0) {
throw new Error404()
}

return this._populateRelationsForRows(records, options)
}

static async create(data, options: IRepositoryOptions) {
const currentUser = SequelizeRepository.getCurrentUser(options)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import { AnalyticsEmailsOutput } from '../../messageTypes'
import { platformDisplayNames } from '../../../../../utils/platformDisplayNames'
import getStage from '../../../../../services/helpers/getStage'
import { s3 } from '../../../../../services/aws'
import UserRepository from '../../../../../database/repositories/userRepository'

/**
* Sends weekly analytics of a given tenant to the user email.
* Sends weekly analytics emails of a given tenant
* to all users of the tenant.
* Data sent is for the last week.
* @param tenantId
*/
Expand Down Expand Up @@ -78,44 +80,48 @@ async function weeklyAnalyticsEmailsWorker(tenantId: string): Promise<AnalyticsE
)

if (hasReasonableInsights({ newMembers, activeMembers, newActivities, newConversations })) {
const data = {
analytics: {
dateRangeStart: dateTimeStart.format('D MMMM, YYYY'),
dateRangeEnd: dateTimeEnd.format('D MMMM, YYYY'),
activeMembers,
newMembers,
activitiesTracked: newActivities,
conversationsStarted: newConversations,
hotConversations,
hasHotConversations: hotConversations.length > 0,
hotConversationsCount: hotConversations.length,
},
tenant: {
name: userContext.currentTenant.name,
},
user: {
name: userContext.currentUser.firstName,
},
}
const allTenantUsers = await UserRepository.findAllUsersOfTenant(tenantId)

const advancedSuppressionManager = {
groupId: parseInt(getConfig().SENDGRID_WEEKLY_ANALYTICS_UNSUBSCRIBE_GROUP_ID, 10),
groupsToDisplay: [parseInt(getConfig().SENDGRID_WEEKLY_ANALYTICS_UNSUBSCRIBE_GROUP_ID, 10)],
}

console.log(`SENDING EMAIL!!!! to: ${userContext.currentUser.email}`)
console.log(`sendgrid template object: `)
console.log(data)
for (const user of allTenantUsers) {
if (user.email && user.emailVerified) {
const userFirstName = user.firstName ? user.firstName : user.email.split('@')[0]

const data = {
analytics: {
dateRangeStart: dateTimeStart.format('D MMMM, YYYY'),
dateRangeEnd: dateTimeEnd.format('D MMMM, YYYY'),
activeMembers,
newMembers,
activitiesTracked: newActivities,
conversationsStarted: newConversations,
hotConversations,
hasHotConversations: hotConversations.length > 0,
hotConversationsCount: hotConversations.length,
},
tenant: {
name: userContext.currentTenant.name,
},
user: {
name: userFirstName,
},
}

new EmailSender(EmailSender.TEMPLATES.WEEKLY_ANALYTICS, data).sendTo(
userContext.currentUser.email,
advancedSuppressionManager,
)
await new EmailSender(EmailSender.TEMPLATES.WEEKLY_ANALYTICS, data).sendTo(
user.email,
advancedSuppressionManager,
)

new EmailSender(EmailSender.TEMPLATES.WEEKLY_ANALYTICS, data).sendTo(
'team@crowd.dev',
advancedSuppressionManager,
)
await new EmailSender(EmailSender.TEMPLATES.WEEKLY_ANALYTICS, data).sendTo(
'team@crowd.dev',
advancedSuppressionManager,
)
}
}

return { status: 200, emailSent: true }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import moment from 'moment'
import { NodeMicroserviceMessage } from './messageTypes'
import { getConfig } from '../../../config'

const { SQS } = require('aws-sdk')

const sqs = new SQS()
import { sqs } from '../../../services/aws'

/**
* Send a message to the node microservice queue
Expand All @@ -23,7 +20,7 @@ async function sendNodeMicroserviceMessage(body: NodeMicroserviceMessage): Promi

await sqs
.sendMessage({
QueueUrl: getConfig().QUEUE_URL,
QueueUrl: getConfig().NODE_MICROSERVICES_SQS_URL,
MessageGroupId: messageGroupId,
MessageDeduplicationId: messageDeduplicationId,
MessageBody: JSON.stringify(body),
Expand Down
4 changes: 3 additions & 1 deletion backend/src/serverless/microservices/nodejs/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ constructs:
environment:
NODE_ENV: ${env:NODE_ENV}
NODE_MICROSERVICES_STATEMACHINE_ARN: ${env:NODE_MICROSERVICES_STATEMACHINE_ARN}
LOCALSTACK_HOSTNAME: ${env:LOCALSTACK_HOSTNAME}
LOCALSTACK_PORT: ${env:LOCALSTACK_PORT}

functions:
weeklyAnalyticsEmailsCoordinator:
Expand All @@ -83,7 +85,7 @@ functions:
NODE_ENV: ${env:NODE_ENV}
EDITION: ${env:EDITION}
SEGMENT_WRITE_KEY: ${env:SEGMENT_WRITE_KEY}
QUEUE_URL: ${env:NODE_MICROSERVICES_SQS_URL}
NODE_MICROSERVICES_SQS_URL: ${env:NODE_MICROSERVICES_SQS_URL}
DATABASE_USERNAME: ${env:DATABASE_USERNAME}
DATABASE_DIALECT: ${env:DATABASE_DIALECT}
DATABASE_PASSWORD: ${env:DATABASE_PASSWORD}
Expand Down
2 changes: 1 addition & 1 deletion start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ echo $IS_DEV
if [[ "$IS_DEV" = false ]]; then
cd $CLI_HOME/docker && docker-compose -p crowd up --force-recreate
else
bash backend/util/install-all.sh
cd $CLI_HOME && bash $CLI_HOME/backend/util/install-all.sh
cd $CLI_HOME/docker && docker-compose -f docker-compose.yaml -f docker-compose.dev.yaml -p crowd up --force-recreate
fi