-
Notifications
You must be signed in to change notification settings - Fork 333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore(rethinkdb): OrganizationUser: Phase 1 #9952
Changes from 63 commits
bfa5c21
7ac086e
9844960
62e123c
c38f86c
d318560
16843dc
abb3bdf
9911448
af03b31
2a3c0ca
8dfa870
3b48d22
24da04b
c60e502
0d2c7d7
9ef9346
76aeff9
0830c8e
a12b08a
a732657
954503d
c7775ad
d53aca4
6689e39
a6fd0d3
00964a8
50ca56b
b220f22
22ca4f3
9c55acd
9dfd35c
654aa42
ebbea29
7dbea0d
3fd2236
c552ea6
50d9e77
b7c092d
fe9efe9
4d838f3
34b05bf
02aa23d
dfba81e
81d68b7
f4f5ede
dcbcd78
b329fd3
0a549d6
5f6bdfc
21876d9
af856f2
129d1a9
6e368a4
27b0cf2
b5422dd
3c061e2
1cdf979
473c0c5
49be1bc
65614c6
ca01882
44d153e
3c3b46c
3e3634c
8d33999
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,3 +1,4 @@ | ||||||||||||||||||||||||||||||||||||||||
import {sql} from 'kysely' | ||||||||||||||||||||||||||||||||||||||||
import {InvoiceItemType} from 'parabol-client/types/constEnums' | ||||||||||||||||||||||||||||||||||||||||
import getRethink from '../../database/rethinkDriver' | ||||||||||||||||||||||||||||||||||||||||
import {RDatum} from '../../database/stricterR' | ||||||||||||||||||||||||||||||||||||||||
|
@@ -36,7 +37,7 @@ const maybeUpdateOrganizationActiveDomain = async ( | |||||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
// don't modify if we can't guess the domain or the domain we guess is the current domain | ||||||||||||||||||||||||||||||||||||||||
const domain = await getActiveDomainForOrgId(orgId) | ||||||||||||||||||||||||||||||||||||||||
const domain = await getActiveDomainForOrgId(orgId, dataLoader) | ||||||||||||||||||||||||||||||||||||||||
if (!domain || domain === activeDomain) return | ||||||||||||||||||||||||||||||||||||||||
organization.activeDomain = domain | ||||||||||||||||||||||||||||||||||||||||
const pg = getKysely() | ||||||||||||||||||||||||||||||||||||||||
|
@@ -52,13 +53,19 @@ const changePause = (inactive: boolean) => async (_orgIds: string[], user: IUser | |||||||||||||||||||||||||||||||||||||||
email, | ||||||||||||||||||||||||||||||||||||||||
isActive: !inactive | ||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||
return Promise.all([ | ||||||||||||||||||||||||||||||||||||||||
await Promise.all([ | ||||||||||||||||||||||||||||||||||||||||
updateUser( | ||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||
inactive | ||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||
userId | ||||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||||
getKysely() | ||||||||||||||||||||||||||||||||||||||||
.updateTable('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||||
.set({inactive}) | ||||||||||||||||||||||||||||||||||||||||
.where('userId', '=', userId) | ||||||||||||||||||||||||||||||||||||||||
.where('removedAt', 'is', null) | ||||||||||||||||||||||||||||||||||||||||
.execute(), | ||||||||||||||||||||||||||||||||||||||||
r | ||||||||||||||||||||||||||||||||||||||||
.table('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||||
.getAll(userId, {index: 'userId'}) | ||||||||||||||||||||||||||||||||||||||||
|
@@ -73,33 +80,28 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork | |||||||||||||||||||||||||||||||||||||||
const r = await getRethink() | ||||||||||||||||||||||||||||||||||||||||
const [rawOrganizations, organizationUsers] = await Promise.all([ | ||||||||||||||||||||||||||||||||||||||||
dataLoader.get('organizations').loadMany(orgIds), | ||||||||||||||||||||||||||||||||||||||||
r | ||||||||||||||||||||||||||||||||||||||||
.table('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||||
.getAll(userId, {index: 'userId'}) | ||||||||||||||||||||||||||||||||||||||||
.orderBy(r.desc('newUserUntil')) | ||||||||||||||||||||||||||||||||||||||||
.coerceTo('array') | ||||||||||||||||||||||||||||||||||||||||
.run() | ||||||||||||||||||||||||||||||||||||||||
dataLoader.get('organizationUsersByUserId').load(userId) | ||||||||||||||||||||||||||||||||||||||||
]) | ||||||||||||||||||||||||||||||||||||||||
dataLoader.get('organizationUsersByUserId').clear(userId) | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clear the cache after modification. Ensure that the data loader cache is cleared after modifying the data to avoid stale data issues. + dataLoader.get('organizations').clearMany(orgIds)
|
||||||||||||||||||||||||||||||||||||||||
const organizations = rawOrganizations.filter(isValid) | ||||||||||||||||||||||||||||||||||||||||
const docs = orgIds.map((orgId) => { | ||||||||||||||||||||||||||||||||||||||||
const oldOrganizationUser = organizationUsers.find( | ||||||||||||||||||||||||||||||||||||||||
(organizationUser) => organizationUser.orgId === orgId | ||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||
const organization = organizations.find((organization) => organization.id === orgId)! | ||||||||||||||||||||||||||||||||||||||||
// continue the grace period from before, if any OR set to the end of the invoice OR (if it is a free account) no grace period | ||||||||||||||||||||||||||||||||||||||||
const newUserUntil = | ||||||||||||||||||||||||||||||||||||||||
(oldOrganizationUser && oldOrganizationUser.newUserUntil) || | ||||||||||||||||||||||||||||||||||||||||
organization.periodEnd || | ||||||||||||||||||||||||||||||||||||||||
new Date() | ||||||||||||||||||||||||||||||||||||||||
return new OrganizationUser({ | ||||||||||||||||||||||||||||||||||||||||
id: oldOrganizationUser?.id, | ||||||||||||||||||||||||||||||||||||||||
orgId, | ||||||||||||||||||||||||||||||||||||||||
userId, | ||||||||||||||||||||||||||||||||||||||||
newUserUntil, | ||||||||||||||||||||||||||||||||||||||||
tier: organization.tier | ||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
await getKysely() | ||||||||||||||||||||||||||||||||||||||||
.insertInto('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||||
.values(docs) | ||||||||||||||||||||||||||||||||||||||||
.onConflict((oc) => oc.doNothing()) | ||||||||||||||||||||||||||||||||||||||||
.execute() | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+100
to
+104
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure transactional integrity. Consider wrapping the database update operations in a transaction to ensure atomicity. + const trx = await getKysely().transaction()
+ try {
await getKysely()
.insertInto('OrganizationUser')
.values(docs)
.onConflict((oc) => oc.doNothing())
.execute()
await trx.commit()
} catch (error) {
await trx.rollback()
throw error
} Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
await r.table('OrganizationUser').insert(docs, {conflict: 'replace'}).run() | ||||||||||||||||||||||||||||||||||||||||
await Promise.all( | ||||||||||||||||||||||||||||||||||||||||
orgIds.map((orgId) => { | ||||||||||||||||||||||||||||||||||||||||
|
@@ -111,7 +113,13 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork | |||||||||||||||||||||||||||||||||||||||
const deleteUser = async (orgIds: string[], user: IUser) => { | ||||||||||||||||||||||||||||||||||||||||
const r = await getRethink() | ||||||||||||||||||||||||||||||||||||||||
orgIds.forEach((orgId) => analytics.userRemovedFromOrg(user, orgId)) | ||||||||||||||||||||||||||||||||||||||||
return r | ||||||||||||||||||||||||||||||||||||||||
await getKysely() | ||||||||||||||||||||||||||||||||||||||||
.updateTable('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||||
.set({removedAt: sql`CURRENT_TIMESTAMP`}) | ||||||||||||||||||||||||||||||||||||||||
.where('userId', '=', user.id) | ||||||||||||||||||||||||||||||||||||||||
.where('orgId', 'in', orgIds) | ||||||||||||||||||||||||||||||||||||||||
.execute() | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+116
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure transactional integrity. Consider wrapping the database update operations in a transaction to ensure atomicity. + const trx = await getKysely().transaction()
+ try {
await getKysely()
.updateTable('OrganizationUser')
.set({removedAt: sql`CURRENT_TIMESTAMP`})
.where('userId', '=', user.id)
.where('orgId', 'in', orgIds)
.execute()
await trx.commit()
} catch (error) {
await trx.rollback()
throw error
} Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
await r | ||||||||||||||||||||||||||||||||||||||||
.table('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||||
.getAll(user.id, {index: 'userId'}) | ||||||||||||||||||||||||||||||||||||||||
.filter((row: RDatum) => r.expr(orgIds).contains(row('orgId'))) | ||||||||||||||||||||||||||||||||||||||||
|
@@ -152,7 +160,6 @@ export default async function adjustUserCount( | |||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const dbAction = dbActionTypeLookup[type] | ||||||||||||||||||||||||||||||||||||||||
await dbAction(orgIds, user, dataLoader) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
const auditEventType = auditEventTypeLookup[type] | ||||||||||||||||||||||||||||||||||||||||
await insertOrgUserAudit(orgIds, userId, auditEventType) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,5 +1,3 @@ | ||||||||||||||
import getRethink from '../../database/rethinkDriver' | ||||||||||||||
import {RDatum} from '../../database/stricterR' | ||||||||||||||
import {DataLoaderWorker} from '../../graphql/graphql' | ||||||||||||||
import {OrganizationSource} from '../../graphql/public/types/Organization' | ||||||||||||||
import {analytics} from '../../utils/analytics/analytics' | ||||||||||||||
|
@@ -9,31 +7,23 @@ const sendEnterpriseOverageEvent = async ( | |||||||||||||
organization: OrganizationSource, | ||||||||||||||
dataLoader: DataLoaderWorker | ||||||||||||||
) => { | ||||||||||||||
const r = await getRethink() | ||||||||||||||
const manager = getStripeManager() | ||||||||||||||
const {id: orgId, stripeSubscriptionId} = organization | ||||||||||||||
if (!stripeSubscriptionId) return | ||||||||||||||
const [orgUserCount, subscriptionItem] = await Promise.all([ | ||||||||||||||
r | ||||||||||||||
.table('OrganizationUser') | ||||||||||||||
.getAll(orgId, {index: 'orgId'}) | ||||||||||||||
.filter({removedAt: null, inactive: false}) | ||||||||||||||
.count() | ||||||||||||||
.run(), | ||||||||||||||
const [orgUsers, subscriptionItem] = await Promise.all([ | ||||||||||||||
dataLoader.get('organizationUsersByOrgId').load(orgId), | ||||||||||||||
manager.getSubscriptionItem(stripeSubscriptionId) | ||||||||||||||
]) | ||||||||||||||
const activeOrgUsers = orgUsers.filter(({inactive}) => !inactive) | ||||||||||||||
const orgUserCount = activeOrgUsers.length | ||||||||||||||
if (!subscriptionItem) return | ||||||||||||||
|
||||||||||||||
const quantity = subscriptionItem.quantity | ||||||||||||||
if (!quantity) return | ||||||||||||||
if (orgUserCount > quantity) { | ||||||||||||||
const billingLeaderOrgUser = await r | ||||||||||||||
.table('OrganizationUser') | ||||||||||||||
.getAll(orgId, {index: 'orgId'}) | ||||||||||||||
.filter({removedAt: null}) | ||||||||||||||
.filter((row: RDatum) => r.expr(['BILLING_LEADER', 'ORG_ADMIN']).contains(row('role'))) | ||||||||||||||
.nth(0) | ||||||||||||||
.run() | ||||||||||||||
const billingLeaderOrgUser = orgUsers.find( | ||||||||||||||
({role}) => role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure proper error handling in promise chain. The - sendEnterpriseOverageEvent(org, dataLoader).catch()
+ sendEnterpriseOverageEvent(org, dataLoader).catch((error) => {
+ console.error(`Failed to send overage event for org ${org.id}:`, error);
+ }); Committable suggestion
Suggested change
|
||||||||||||||
)! | ||||||||||||||
const {id: userId} = billingLeaderOrgUser | ||||||||||||||
const user = await dataLoader.get('users').loadNonNull(userId) | ||||||||||||||
analytics.enterpriseOverUserLimit(user, orgId) | ||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -21,6 +21,13 @@ import sendTeamsLimitEmail from './sendTeamsLimitEmail' | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
const enableUsageStats = async (userIds: string[], orgId: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
const pg = getKysely() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
await pg | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.updateTable('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.set({suggestedTier: 'team'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.where('orgId', '=', orgId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.where('userId', 'in', userIds) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.where('removedAt', 'is', null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.execute() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+24
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure transactional integrity. Consider wrapping the database update operations in a transaction to ensure atomicity. + const trx = await pg.transaction()
+ try {
await pg
.updateTable('OrganizationUser')
.set({suggestedTier: 'team'})
.where('orgId', '=', orgId)
.where('userId', 'in', userIds)
.where('removedAt', 'is', null)
.execute()
await pg
.updateTable('User')
.set({featureFlags: sql`arr_append_uniq("featureFlags", 'insights')`})
.where('id', 'in', userIds)
.execute()
+ await trx.commit()
+ } catch (error) {
+ await trx.rollback()
+ throw error
+ }
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
await r | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.table('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.getAll(r.args(userIds), {index: 'userId'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -87,6 +94,13 @@ export const maybeRemoveRestrictions = async (orgId: string, dataLoader: DataLoa | |||||||||||||||||||||||||||||||||||||||||||||||||||||
.set({tierLimitExceededAt: null, scheduledLockAt: null, lockedAt: null}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.where('id', '=', orgId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.execute(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
pg | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.updateTable('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.set({suggestedTier: 'starter'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.where('orgId', '=', orgId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.where('userId', 'in', billingLeadersIds) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.where('removedAt', 'is', null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added this because it was erroneously missing in the rethinkdb query |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
.execute(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+97
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure transactional integrity. Similar to the previous comment, consider wrapping the database update operations in a transaction to ensure atomicity. + const trx = await pg.transaction()
+ try {
await pg
.updateTable('Organization')
.set({tierLimitExceededAt: null, scheduledLockAt: null, lockedAt: null})
.where('id', '=', orgId)
.execute()
await pg
.updateTable('OrganizationUser')
.set({suggestedTier: 'starter'})
.where('orgId', '=', orgId)
.where('userId', 'in', billingLeadersIds)
.where('removedAt', 'is', null)
.execute()
await trx.commit()
} catch (error) {
await trx.rollback()
throw error
} Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
r | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.table('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
.getAll(r.args(billingLeadersIds), {index: 'userId'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -11,9 +11,10 @@ import {getStripeManager} from '../../utils/stripe' | |||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||
const updateSubscriptionQuantity = async (orgId: string, logMismatch?: boolean) => { | ||||||||||||||||||||||||||||||||||||||
const r = await getRethink() | ||||||||||||||||||||||||||||||||||||||
const pg = getKysely() | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid redundant database connections The code is opening a connection to RethinkDB but doesn't seem to use it. Consider removing the redundant connection. - const r = await getRethink()
|
||||||||||||||||||||||||||||||||||||||
const manager = getStripeManager() | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
const org = await getKysely() | ||||||||||||||||||||||||||||||||||||||
const org = await pg | ||||||||||||||||||||||||||||||||||||||
.selectFrom('Organization') | ||||||||||||||||||||||||||||||||||||||
.selectAll() | ||||||||||||||||||||||||||||||||||||||
.where('id', '=', orgId) | ||||||||||||||||||||||||||||||||||||||
|
@@ -34,15 +35,29 @@ const updateSubscriptionQuantity = async (orgId: string, logMismatch?: boolean) | |||||||||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
const [orgUserCount, teamSubscription] = await Promise.all([ | ||||||||||||||||||||||||||||||||||||||
const [orgUserCountRes, orgUserCount, teamSubscription] = await Promise.all([ | ||||||||||||||||||||||||||||||||||||||
pg | ||||||||||||||||||||||||||||||||||||||
.selectFrom('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||
.select(({fn}) => fn.count<number>('id').as('count')) | ||||||||||||||||||||||||||||||||||||||
.where('orgId', '=', orgId) | ||||||||||||||||||||||||||||||||||||||
.where('removedAt', 'is', null) | ||||||||||||||||||||||||||||||||||||||
.where('inactive', '=', false) | ||||||||||||||||||||||||||||||||||||||
.executeTakeFirstOrThrow(), | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+38
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove redundant RethinkDB query The code is querying both PostgreSQL and RethinkDB for the same data. Since the transition is towards using PostgreSQL, remove the RethinkDB query. const [orgUserCountRes, teamSubscription] = await Promise.all([
pg
.selectFrom('OrganizationUser')
.select(({fn}) => fn.count<number>('id').as('count'))
.where('orgId', '=', orgId)
.where('removedAt', 'is', null)
.where('inactive', '=', false)
.executeTakeFirstOrThrow(),
- r
- .table('OrganizationUser')
- .getAll(orgId, {index: 'orgId'})
- .filter({removedAt: null, inactive: false})
- .count()
- .run(),
manager.getSubscriptionItem(stripeSubscriptionId)
]) Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
r | ||||||||||||||||||||||||||||||||||||||
.table('OrganizationUser') | ||||||||||||||||||||||||||||||||||||||
.getAll(orgId, {index: 'orgId'}) | ||||||||||||||||||||||||||||||||||||||
.filter({removedAt: null, inactive: false}) | ||||||||||||||||||||||||||||||||||||||
.count() | ||||||||||||||||||||||||||||||||||||||
.run(), | ||||||||||||||||||||||||||||||||||||||
await manager.getSubscriptionItem(stripeSubscriptionId) | ||||||||||||||||||||||||||||||||||||||
manager.getSubscriptionItem(stripeSubscriptionId) | ||||||||||||||||||||||||||||||||||||||
]) | ||||||||||||||||||||||||||||||||||||||
if (orgUserCountRes.count !== orgUserCount) { | ||||||||||||||||||||||||||||||||||||||
sendToSentry(new Error('OrganizationUser count mismatch'), { | ||||||||||||||||||||||||||||||||||||||
tags: { | ||||||||||||||||||||||||||||||||||||||
orgId | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||||||
teamSubscription && | ||||||||||||||||||||||||||||||||||||||
teamSubscription.quantity !== undefined && | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure transactional integrity.
Consider wrapping the database update operations in a transaction to ensure atomicity.
Committable suggestion