瀏覽代碼

Merge pull request #14383 from overleaf/jpa-server-pro-feature-refresh-migration

[web] add migration for Server Pro/CE to refresh features once

GitOrigin-RevId: 799e6aef2ad9ad6806ec369911d56f7a40945098
Jakob Ackermann 3 年之前
父節點
當前提交
c5bb18045e

+ 11 - 0
services/web/migrations/20230817081910_back_fill_gitBridge_feature_server_pro.js

@@ -0,0 +1,11 @@
+exports.tags = ['server-ce', 'server-pro']
+
+exports.migrate = async () => {
+  // Run-time import as SaaS does not ship with the server-ce-scripts module
+  const runScript = require('../modules/server-ce-scripts/scripts/upgrade-user-features')
+  await runScript(false, {
+    gitBridge: 1,
+  })
+}
+
+exports.rollback = async () => {}

+ 60 - 0
services/web/modules/server-ce-scripts/scripts/upgrade-user-features.js

@@ -0,0 +1,60 @@
+const Settings = require('@overleaf/settings')
+const logger = require('@overleaf/logger')
+const { db, waitForDb } = require('../../../app/src/infrastructure/mongodb')
+const {
+  mergeFeatures,
+  compareFeatures,
+} = require('../../../app/src/Features/Subscription/FeaturesHelper')
+const DRY_RUN = !process.argv.includes('--dry-run=false')
+
+async function main(DRY_RUN, defaultFeatures) {
+  await waitForDb()
+
+  logger.info({ defaultFeatures }, 'default features')
+
+  const cursor = db.users.find(
+    {},
+    { projection: { _id: 1, email: 1, features: 1 } }
+  )
+  for await (const user of cursor) {
+    const newFeatures = mergeFeatures(user.features, defaultFeatures)
+    const diff = compareFeatures(newFeatures, user.features)
+    if (Object.keys(diff).length > 0) {
+      logger.warn(
+        {
+          userId: user._id,
+          email: user.email,
+          oldFeatures: user.features,
+          newFeatures,
+        },
+        'user features upgraded'
+      )
+
+      if (!DRY_RUN) {
+        await db.users.updateOne(
+          { _id: user._id },
+          { $set: { features: newFeatures } }
+        )
+      }
+    }
+  }
+}
+
+module.exports = main
+
+if (require.main === module) {
+  if (DRY_RUN) {
+    console.error('---')
+    console.error('Dry-run enabled, use --dry-run=false to commit changes')
+    console.error('---')
+  }
+  main(DRY_RUN, Settings.defaultFeatures)
+    .then(() => {
+      console.log('Done.')
+      process.exit(0)
+    })
+    .catch(error => {
+      console.error({ error })
+      process.exit(1)
+    })
+}

+ 137 - 0
services/web/modules/server-ce-scripts/test/acceptance/src/ServerCEScriptsTests.js

@@ -1,3 +1,4 @@
+const Settings = require('@overleaf/settings')
 const { execSync } = require('child_process')
 const { execSync } = require('child_process')
 const { expect } = require('chai')
 const { expect } = require('chai')
 const { db } = require('../../../../../app/src/infrastructure/mongodb')
 const { db } = require('../../../../../app/src/infrastructure/mongodb')
@@ -230,4 +231,140 @@ describe('ServerCEScripts', function () {
       })
       })
     })
     })
   })
   })
+
+  describe('upgrade-user-features', function () {
+    let userLatest, userSP1, userCustomTimeoutLower, userCustomTimeoutHigher
+    beforeEach('create users', async function () {
+      userLatest = new User()
+      userSP1 = new User()
+      userCustomTimeoutLower = new User()
+      userCustomTimeoutHigher = new User()
+
+      await Promise.all([
+        userLatest.ensureUserExists(),
+        userSP1.ensureUserExists(),
+        userCustomTimeoutLower.ensureUserExists(),
+        userCustomTimeoutHigher.ensureUserExists(),
+      ])
+    })
+
+    const serverPro1Features = {
+      collaborators: -1,
+      dropbox: true,
+      versioning: true,
+      compileTimeout: 180,
+      compileGroup: 'standard',
+      references: true,
+      templates: true,
+      trackChanges: true,
+    }
+
+    beforeEach('downgrade userSP1', async function () {
+      await userSP1.mongoUpdate({ $set: { features: serverPro1Features } })
+    })
+
+    beforeEach('downgrade userCustomTimeoutLower', async function () {
+      run(
+        `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userCustomTimeoutLower.id} --compile-timeout=42`
+      )
+    })
+
+    beforeEach('upgrade userCustomTimeoutHigher', async function () {
+      run(
+        `node modules/server-ce-scripts/scripts/change-compile-timeout --user-id=${userCustomTimeoutHigher.id} --compile-timeout=360`
+      )
+    })
+
+    async function getFeatures() {
+      return [
+        await userLatest.getFeatures(),
+        await userSP1.getFeatures(),
+        await userCustomTimeoutLower.getFeatures(),
+        await userCustomTimeoutHigher.getFeatures(),
+      ]
+    }
+
+    let initialFeatures
+    beforeEach('collect initial features', async function () {
+      initialFeatures = await getFeatures()
+    })
+
+    it('should have prepared the right features', async function () {
+      expect(initialFeatures).to.deep.equal([
+        Settings.defaultFeatures,
+        serverPro1Features,
+        Object.assign({}, Settings.defaultFeatures, {
+          compileTimeout: 42,
+        }),
+        Object.assign({}, Settings.defaultFeatures, {
+          compileTimeout: 360,
+        }),
+      ])
+    })
+
+    describe('dry-run', function () {
+      let output
+      beforeEach('run script', function () {
+        output = run(
+          `node modules/server-ce-scripts/scripts/upgrade-user-features`
+        )
+      })
+
+      it('should update SP1 features', function () {
+        expect(output).to.include(userSP1.id)
+      })
+
+      it('should update lowerTimeout features', function () {
+        expect(output).to.include(userCustomTimeoutLower.id)
+      })
+
+      it('should not update latest features', function () {
+        expect(output).to.not.include(userLatest.id)
+      })
+
+      it('should not update higherTimeout features', function () {
+        expect(output).to.not.include(userCustomTimeoutHigher.id)
+      })
+
+      it('should not change any features in the db', async function () {
+        expect(await getFeatures()).to.deep.equal(initialFeatures)
+      })
+    })
+
+    describe('live run', function () {
+      let output
+      beforeEach('run script', function () {
+        output = run(
+          `node modules/server-ce-scripts/scripts/upgrade-user-features --dry-run=false`
+        )
+      })
+
+      it('should update SP1 features', function () {
+        expect(output).to.include(userSP1.id)
+      })
+
+      it('should update lowerTimeout features', function () {
+        expect(output).to.include(userCustomTimeoutLower.id)
+      })
+
+      it('should not update latest features', function () {
+        expect(output).to.not.include(userLatest.id)
+      })
+
+      it('should not update higherTimeout features', function () {
+        expect(output).to.not.include(userCustomTimeoutHigher.id)
+      })
+
+      it('should update features in the db', async function () {
+        expect(await getFeatures()).to.deep.equal([
+          Settings.defaultFeatures,
+          Settings.defaultFeatures,
+          Settings.defaultFeatures,
+          Object.assign({}, Settings.defaultFeatures, {
+            compileTimeout: 360,
+          }),
+        ])
+      })
+    })
+  })
 })
 })