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-15 09:06:23 +00:00
committed by Copybot
parent 9b6eab9251
commit 7f3c8a281d
5 changed files with 100 additions and 137 deletions
@@ -1,4 +1,4 @@
import { promisify, callbackify } from 'node:util' import { callbackify } from 'node:util'
import UserGetter from '../User/UserGetter.mjs' import UserGetter from '../User/UserGetter.mjs'
import UserMembershipsHandler from '../UserMembership/UserMembershipsHandler.mjs' import UserMembershipsHandler from '../UserMembership/UserMembershipsHandler.mjs'
import UserMembershipEntityConfigs from '../UserMembership/UserMembershipEntityConfigs.mjs' import UserMembershipEntityConfigs from '../UserMembership/UserMembershipEntityConfigs.mjs'
@@ -55,39 +55,36 @@ async function getCurrentInstitutionsWithLicence(userId) {
return Object.values(institutions) 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 = { const InstitutionsGetter = {
getConfirmedAffiliations(userId, callback) { getConfirmedAffiliations: callbackify(getConfirmedAffiliations),
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)
})
},
getCurrentInstitutionIds: callbackify(getCurrentInstitutionIds), getCurrentInstitutionIds: callbackify(getCurrentInstitutionIds),
getCurrentInstitutionsWithLicence: callbackify( getCurrentInstitutionsWithLicence: callbackify(
getCurrentInstitutionsWithLicence getCurrentInstitutionsWithLicence
), ),
getManagedInstitutions: callbackify(getManagedInstitutions),
getManagedInstitutions(userId, callback) {
UserMembershipsHandler.getEntitiesByUser(
UserMembershipEntityConfigs.institution,
userId,
callback
)
},
} }
InstitutionsGetter.promises = { InstitutionsGetter.promises = {
@@ -95,7 +92,7 @@ InstitutionsGetter.promises = {
getCurrentInstitutionIds, getCurrentInstitutionIds,
getCurrentInstitutionsWithLicence, getCurrentInstitutionsWithLicence,
getCurrentAndPastAffiliationIds, getCurrentAndPastAffiliationIds,
getManagedInstitutions: promisify(InstitutionsGetter.getManagedInstitutions), getManagedInstitutions,
} }
export default InstitutionsGetter export default InstitutionsGetter
@@ -1,17 +1,4 @@
/* eslint-disable import { callbackifyAll } from '@overleaf/promise-utils'
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 UserMembershipEntityConfigs from './UserMembershipEntityConfigs.mjs' import UserMembershipEntityConfigs from './UserMembershipEntityConfigs.mjs'
import * as InstitutionModel from '../../models/Institution.mjs' import * as InstitutionModel from '../../models/Institution.mjs'
import * as SubscriptionModel from '../../models/Subscription.mjs' import * as SubscriptionModel from '../../models/Subscription.mjs'
@@ -25,11 +12,25 @@ const EntityModels = {
} }
const UserMembershipsHandler = { const UserMembershipsHandler = {
removeUserFromAllEntities(userId, callback) { async getEntitiesByUser(entityConfig, userId) {
// get all writable entity types if (!Features.hasFeature('saas')) {
if (callback == null) { return []
callback = function () {}
} }
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 = [] const entityConfigs = []
for (const key in UserMembershipEntityConfigs) { for (const key in UserMembershipEntityConfigs) {
const entityConfig = UserMembershipEntityConfigs[key] const entityConfig = UserMembershipEntityConfigs[key]
@@ -39,52 +40,19 @@ const UserMembershipsHandler = {
} }
// remove the user from all entities types // remove the user from all entities types
async.map( for (const entityConfig of entityConfigs) {
entityConfigs, await UserMembershipsHandler.removeUserFromEntities(entityConfig, userId)
(entityConfig, innerCallback) => }
UserMembershipsHandler.removeUserFromEntities(
entityConfig,
userId,
innerCallback
),
callback
)
}, },
removeUserFromEntities(entityConfig, userId, callback) { async removeUserFromEntities(entityConfig, userId) {
if (callback == null) {
callback = function () {}
}
const removeOperation = { $pull: {} } const removeOperation = { $pull: {} }
removeOperation.$pull[entityConfig.fields.write] = userId removeOperation.$pull[entityConfig.fields.write] = userId
EntityModels[entityConfig.modelName] await EntityModels[entityConfig.modelName].updateMany({}, removeOperation)
.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)
}, },
} }
UserMembershipsHandler.promises = promisifyAll(UserMembershipsHandler) export default {
export default UserMembershipsHandler ...callbackifyAll(UserMembershipsHandler),
promises: UserMembershipsHandler,
}
+12 -14
View File
@@ -1,7 +1,7 @@
import mongoose from '../infrastructure/Mongoose.mjs' import mongoose from '../infrastructure/Mongoose.mjs'
import settings from '@overleaf/settings' import settings from '@overleaf/settings'
import logger from '@overleaf/logger' import logger from '@overleaf/logger'
import { promisify } from '@overleaf/promise-utils' import { callbackify } from '@overleaf/promise-utils'
import { fetchJson } from '@overleaf/fetch-utils' import { fetchJson } from '@overleaf/fetch-utils'
const { Schema } = mongoose const { Schema } = mongoose
const { ObjectId } = Schema const { ObjectId } = Schema
@@ -20,16 +20,15 @@ export const InstitutionSchema = new Schema(
) )
// fetch institution's data from v1 API. Errors are ignored // 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}` const url = `${settings.apis.v1.url}/universities/list/${this.v1Id}`
try { try {
const parsedBody = await fetchJson(url) const parsedBody = await fetchJson(url)
this.name = parsedBody != null ? parsedBody.name : undefined this.name = parsedBody?.name
this.countryCode = parsedBody != null ? parsedBody.country_code : undefined this.countryCode = parsedBody?.country_code
this.departments = parsedBody != null ? parsedBody.departments : undefined this.departments = parsedBody?.departments
this.portalSlug = parsedBody != null ? parsedBody.portal_slug : undefined this.portalSlug = parsedBody?.portal_slug
this.enterpriseCommons = this.enterpriseCommons = parsedBody?.enterprise_commons
parsedBody != null ? parsedBody.enterprise_commons : undefined
} catch (error) { } catch (error) {
// log error and carry on without v1 data // log error and carry on without v1 data
logger.err( logger.err(
@@ -37,12 +36,11 @@ InstitutionSchema.method('fetchV1Data', async function (callback) {
'[fetchV1DataError]' '[fetchV1DataError]'
) )
} }
callback(null, this) return this
}) }
InstitutionSchema.method( InstitutionSchema.method('fetchV1DataPromise', fetchV1DataPromise)
'fetchV1DataPromise',
promisify(InstitutionSchema.methods.fetchV1Data) InstitutionSchema.method('fetchV1Data', callbackify(fetchV1DataPromise))
)
export const Institution = mongoose.model('Institution', InstitutionSchema) export const Institution = mongoose.model('Institution', InstitutionSchema)
+26 -30
View File
@@ -1,7 +1,8 @@
import mongoose from '../infrastructure/Mongoose.mjs' import mongoose from '../infrastructure/Mongoose.mjs'
import settings from '@overleaf/settings' import settings from '@overleaf/settings'
import logger from '@overleaf/logger' 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 { Schema } = mongoose
const { ObjectId } = Schema const { ObjectId } = Schema
@@ -13,37 +14,32 @@ export const PublisherSchema = new Schema(
{ minimize: false } { minimize: false }
) )
// fetch publisher's (brand on v1) data from v1 API. Errors are ignored async function fetchV1DataPromise() {
PublisherSchema.method('fetchV1Data', function (callback) { const url = `${settings.apis.v1.url}/api/v2/brands/${this.slug}`
request( try {
{ const parsedBody = await fetchJson(url, {
baseUrl: settings.apis.v1.url, basicAuth: {
url: `/api/v2/brands/${this.slug}`,
method: 'GET',
auth: {
user: settings.apis.v1.user, user: settings.apis.v1.user,
pass: settings.apis.v1.pass, pass: settings.apis.v1.pass,
sendImmediately: true,
}, },
timeout: settings.apis.v1.timeout, signal: AbortSignal.timeout(settings.apis.v1.timeout),
}, })
(error, response, body) => {
let parsedBody this.name = parsedBody?.name
try { this.partner = parsedBody?.partner
parsedBody = JSON.parse(body) return this
} catch (error1) { } catch (error) {
// log error and carry on without v1 data // log error and carry on without v1 data
error = error1 logger.err(
logger.err( { model: 'Publisher', slug: this.slug, error },
{ model: 'Publisher', slug: this.slug, error }, '[fetchV1DataError]'
'[fetchV1DataError]' )
) }
} return this
this.name = parsedBody != null ? parsedBody.name : undefined }
this.partner = parsedBody != null ? parsedBody.partner : undefined
callback(null, this) PublisherSchema.method('fetchV1DataPromise', fetchV1DataPromise)
}
) PublisherSchema.method('fetchV1Data', callbackify(fetchV1DataPromise))
})
export const Publisher = mongoose.model('Publisher', PublisherSchema) export const Publisher = mongoose.model('Publisher', PublisherSchema)
+7 -3
View File
@@ -1,5 +1,6 @@
import mongoose from '../infrastructure/Mongoose.mjs' import mongoose from '../infrastructure/Mongoose.mjs'
import { TeamInviteSchema } from './TeamInvite.mjs' import { TeamInviteSchema } from './TeamInvite.mjs'
import { callbackify } from '@overleaf/promise-utils'
const { Schema } = mongoose const { Schema } = mongoose
const { ObjectId } = Schema const { ObjectId } = Schema
@@ -123,8 +124,11 @@ export const SubscriptionSchema = new Schema(
) )
// Subscriptions have no v1 data to fetch // Subscriptions have no v1 data to fetch
SubscriptionSchema.method('fetchV1Data', function (callback) { async function fetchV1DataPromise() {
callback(null, this) return this
}) }
SubscriptionSchema.method('fetchV1Data', callbackify(fetchV1DataPromise))
SubscriptionSchema.method('fetchV1DataPromise', fetchV1DataPromise)
export const Subscription = mongoose.model('Subscription', SubscriptionSchema) export const Subscription = mongoose.model('Subscription', SubscriptionSchema)