Async await entity providers (#28841)

* fix: async await entity providers

* fix: async await userMembershipGetters

* fix: move to export class style for UserMembershipsHandler

* fix: update how Publisher model returns its v1 values

GitOrigin-RevId: f94bcb81c9703deaf938cdf7dff31c6e24dc4bd8
This commit is contained in:
Jimmy Domagala-Tang
2026-01-14 11:55:35 -05:00
committed by Copybot
parent 9b6eab9251
commit 7f3c8a281d
5 changed files with 100 additions and 137 deletions

View File

@@ -1,4 +1,4 @@
import { promisify, callbackify } from 'node:util'
import { callbackify } from 'node:util'
import UserGetter from '../User/UserGetter.mjs'
import UserMembershipsHandler from '../UserMembership/UserMembershipsHandler.mjs'
import UserMembershipEntityConfigs from '../UserMembership/UserMembershipEntityConfigs.mjs'
@@ -55,39 +55,36 @@ async function getCurrentInstitutionsWithLicence(userId) {
return Object.values(institutions)
}
async function getConfirmedAffiliations(userId) {
const emailsData = await UserGetter.promises.getUserFullEmails(userId)
const confirmedAffiliations = emailsData
.filter(
emailData =>
emailData.confirmedAt &&
emailData.affiliation &&
emailData.affiliation.institution &&
emailData.affiliation.institution.confirmed
)
.map(emailData => emailData.affiliation)
return confirmedAffiliations
}
async function getManagedInstitutions(userId) {
return await UserMembershipsHandler.promises.getEntitiesByUser(
UserMembershipEntityConfigs.institution,
userId
)
}
const InstitutionsGetter = {
getConfirmedAffiliations(userId, callback) {
UserGetter.getUserFullEmails(userId, function (error, emailsData) {
if (error) {
return callback(error)
}
const confirmedAffiliations = emailsData
.filter(
emailData =>
emailData.confirmedAt &&
emailData.affiliation &&
emailData.affiliation.institution &&
emailData.affiliation.institution.confirmed
)
.map(emailData => emailData.affiliation)
callback(null, confirmedAffiliations)
})
},
getConfirmedAffiliations: callbackify(getConfirmedAffiliations),
getCurrentInstitutionIds: callbackify(getCurrentInstitutionIds),
getCurrentInstitutionsWithLicence: callbackify(
getCurrentInstitutionsWithLicence
),
getManagedInstitutions(userId, callback) {
UserMembershipsHandler.getEntitiesByUser(
UserMembershipEntityConfigs.institution,
userId,
callback
)
},
getManagedInstitutions: callbackify(getManagedInstitutions),
}
InstitutionsGetter.promises = {
@@ -95,7 +92,7 @@ InstitutionsGetter.promises = {
getCurrentInstitutionIds,
getCurrentInstitutionsWithLicence,
getCurrentAndPastAffiliationIds,
getManagedInstitutions: promisify(InstitutionsGetter.getManagedInstitutions),
getManagedInstitutions,
}
export default InstitutionsGetter

View File

@@ -1,17 +1,4 @@
/* eslint-disable
n/handle-callback-err,
max-len,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
import async from 'async'
import { promisifyAll } from '@overleaf/promise-utils'
import { callbackifyAll } from '@overleaf/promise-utils'
import UserMembershipEntityConfigs from './UserMembershipEntityConfigs.mjs'
import * as InstitutionModel from '../../models/Institution.mjs'
import * as SubscriptionModel from '../../models/Subscription.mjs'
@@ -25,11 +12,25 @@ const EntityModels = {
}
const UserMembershipsHandler = {
removeUserFromAllEntities(userId, callback) {
// get all writable entity types
if (callback == null) {
callback = function () {}
async getEntitiesByUser(entityConfig, userId) {
if (!Features.hasFeature('saas')) {
return []
}
const query = entityConfig.baseQuery || {}
query[entityConfig.fields.access] = userId
const entities =
(await EntityModels[entityConfig.modelName].find(query)) || []
const filledEntities = []
for (const entity of entities) {
const filled = await entity.fetchV1DataPromise()
filledEntities.push(filled)
}
return filledEntities
},
async removeUserFromAllEntities(userId) {
const entityConfigs = []
for (const key in UserMembershipEntityConfigs) {
const entityConfig = UserMembershipEntityConfigs[key]
@@ -39,52 +40,19 @@ const UserMembershipsHandler = {
}
// remove the user from all entities types
async.map(
entityConfigs,
(entityConfig, innerCallback) =>
UserMembershipsHandler.removeUserFromEntities(
entityConfig,
userId,
innerCallback
),
callback
)
for (const entityConfig of entityConfigs) {
await UserMembershipsHandler.removeUserFromEntities(entityConfig, userId)
}
},
removeUserFromEntities(entityConfig, userId, callback) {
if (callback == null) {
callback = function () {}
}
async removeUserFromEntities(entityConfig, userId) {
const removeOperation = { $pull: {} }
removeOperation.$pull[entityConfig.fields.write] = userId
EntityModels[entityConfig.modelName]
.updateMany({}, removeOperation)
.then(result => callback(null, result))
.catch(callback)
},
getEntitiesByUser(entityConfig, userId, callback) {
if (callback == null) {
callback = function () {}
}
if (!Features.hasFeature('saas')) return callback(null, [])
const query = Object.assign({}, entityConfig.baseQuery)
query[entityConfig.fields.access] = userId
EntityModels[entityConfig.modelName]
.find(query)
.then(entities => {
if (entities == null) {
entities = []
}
async.mapSeries(
entities,
(entity, cb) => entity.fetchV1Data(cb),
callback
)
})
.catch(callback)
await EntityModels[entityConfig.modelName].updateMany({}, removeOperation)
},
}
UserMembershipsHandler.promises = promisifyAll(UserMembershipsHandler)
export default UserMembershipsHandler
export default {
...callbackifyAll(UserMembershipsHandler),
promises: UserMembershipsHandler,
}

View File

@@ -1,7 +1,7 @@
import mongoose from '../infrastructure/Mongoose.mjs'
import settings from '@overleaf/settings'
import logger from '@overleaf/logger'
import { promisify } from '@overleaf/promise-utils'
import { callbackify } from '@overleaf/promise-utils'
import { fetchJson } from '@overleaf/fetch-utils'
const { Schema } = mongoose
const { ObjectId } = Schema
@@ -20,16 +20,15 @@ export const InstitutionSchema = new Schema(
)
// fetch institution's data from v1 API. Errors are ignored
InstitutionSchema.method('fetchV1Data', async function (callback) {
async function fetchV1DataPromise() {
const url = `${settings.apis.v1.url}/universities/list/${this.v1Id}`
try {
const parsedBody = await fetchJson(url)
this.name = parsedBody != null ? parsedBody.name : undefined
this.countryCode = parsedBody != null ? parsedBody.country_code : undefined
this.departments = parsedBody != null ? parsedBody.departments : undefined
this.portalSlug = parsedBody != null ? parsedBody.portal_slug : undefined
this.enterpriseCommons =
parsedBody != null ? parsedBody.enterprise_commons : undefined
this.name = parsedBody?.name
this.countryCode = parsedBody?.country_code
this.departments = parsedBody?.departments
this.portalSlug = parsedBody?.portal_slug
this.enterpriseCommons = parsedBody?.enterprise_commons
} catch (error) {
// log error and carry on without v1 data
logger.err(
@@ -37,12 +36,11 @@ InstitutionSchema.method('fetchV1Data', async function (callback) {
'[fetchV1DataError]'
)
}
callback(null, this)
})
return this
}
InstitutionSchema.method(
'fetchV1DataPromise',
promisify(InstitutionSchema.methods.fetchV1Data)
)
InstitutionSchema.method('fetchV1DataPromise', fetchV1DataPromise)
InstitutionSchema.method('fetchV1Data', callbackify(fetchV1DataPromise))
export const Institution = mongoose.model('Institution', InstitutionSchema)

View File

@@ -1,7 +1,8 @@
import mongoose from '../infrastructure/Mongoose.mjs'
import settings from '@overleaf/settings'
import logger from '@overleaf/logger'
import request from 'request'
import { fetchJson } from '@overleaf/fetch-utils'
import { callbackify } from '@overleaf/promise-utils'
const { Schema } = mongoose
const { ObjectId } = Schema
@@ -13,37 +14,32 @@ export const PublisherSchema = new Schema(
{ minimize: false }
)
// fetch publisher's (brand on v1) data from v1 API. Errors are ignored
PublisherSchema.method('fetchV1Data', function (callback) {
request(
{
baseUrl: settings.apis.v1.url,
url: `/api/v2/brands/${this.slug}`,
method: 'GET',
auth: {
async function fetchV1DataPromise() {
const url = `${settings.apis.v1.url}/api/v2/brands/${this.slug}`
try {
const parsedBody = await fetchJson(url, {
basicAuth: {
user: settings.apis.v1.user,
pass: settings.apis.v1.pass,
sendImmediately: true,
},
timeout: settings.apis.v1.timeout,
},
(error, response, body) => {
let parsedBody
try {
parsedBody = JSON.parse(body)
} catch (error1) {
// log error and carry on without v1 data
error = error1
logger.err(
{ model: 'Publisher', slug: this.slug, error },
'[fetchV1DataError]'
)
}
this.name = parsedBody != null ? parsedBody.name : undefined
this.partner = parsedBody != null ? parsedBody.partner : undefined
callback(null, this)
}
)
})
signal: AbortSignal.timeout(settings.apis.v1.timeout),
})
this.name = parsedBody?.name
this.partner = parsedBody?.partner
return this
} catch (error) {
// log error and carry on without v1 data
logger.err(
{ model: 'Publisher', slug: this.slug, error },
'[fetchV1DataError]'
)
}
return this
}
PublisherSchema.method('fetchV1DataPromise', fetchV1DataPromise)
PublisherSchema.method('fetchV1Data', callbackify(fetchV1DataPromise))
export const Publisher = mongoose.model('Publisher', PublisherSchema)

View File

@@ -1,5 +1,6 @@
import mongoose from '../infrastructure/Mongoose.mjs'
import { TeamInviteSchema } from './TeamInvite.mjs'
import { callbackify } from '@overleaf/promise-utils'
const { Schema } = mongoose
const { ObjectId } = Schema
@@ -123,8 +124,11 @@ export const SubscriptionSchema = new Schema(
)
// Subscriptions have no v1 data to fetch
SubscriptionSchema.method('fetchV1Data', function (callback) {
callback(null, this)
})
async function fetchV1DataPromise() {
return this
}
SubscriptionSchema.method('fetchV1Data', callbackify(fetchV1DataPromise))
SubscriptionSchema.method('fetchV1DataPromise', fetchV1DataPromise)
export const Subscription = mongoose.model('Subscription', SubscriptionSchema)