Procházet zdrojové kódy

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
Jimmy Domagala-Tang před 9 měsíci
rodič
revize
7f3c8a281d

+ 25 - 28
services/web/app/src/Features/Institutions/InstitutionsGetter.mjs

@@ -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)
 }
 
-const InstitutionsGetter = {
-  getConfirmedAffiliations(userId, callback) {
-    UserGetter.getUserFullEmails(userId, function (error, emailsData) {
-      if (error) {
-        return callback(error)
-      }
+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)
 
-      const confirmedAffiliations = emailsData
-        .filter(
-          emailData =>
-            emailData.confirmedAt &&
-            emailData.affiliation &&
-            emailData.affiliation.institution &&
-            emailData.affiliation.institution.confirmed
-        )
-        .map(emailData => emailData.affiliation)
+  return confirmedAffiliations
+}
 
-      callback(null, confirmedAffiliations)
-    })
-  },
+async function getManagedInstitutions(userId) {
+  return await UserMembershipsHandler.promises.getEntitiesByUser(
+    UserMembershipEntityConfigs.institution,
+    userId
+  )
+}
 
+const InstitutionsGetter = {
+  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

+ 28 - 60
services/web/app/src/Features/UserMembership/UserMembershipsHandler.mjs

@@ -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,
+}

+ 12 - 14
services/web/app/src/models/Institution.mjs

@@ -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)

+ 26 - 30
services/web/app/src/models/Publisher.mjs

@@ -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)

+ 7 - 3
services/web/app/src/models/Subscription.mjs

@@ -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)