From ee800f74486c433fa887d3b27f2385f26fbe93f3 Mon Sep 17 00:00:00 2001 From: James Allen Date: Fri, 30 Nov 2018 11:29:40 +0100 Subject: [PATCH] Merge pull request #1176 from sharelatex/ja-manage-v1-subs Manage v1 subscriptions from v2 GitOrigin-RevId: 1fd63b3630f781e8b4cc3dc1413966540e8d0076 --- .../SubscriptionController.coffee | 19 +++++- .../Subscription/SubscriptionRouter.coffee | 2 + .../SubscriptionViewModelBuilder.coffee | 9 ++- .../Subscription/V1SubscriptionManager.coffee | 14 ++++- .../infrastructure/ExpressLocals.coffee | 2 + .../web/app/views/subscriptions/dashboard.pug | 3 + .../dashboard/_v1_subscription_status.pug | 60 +++++++++++++++++++ .../dashboard/_v1_subscriptions.pug | 8 --- .../coffee/SubscriptionTests.coffee | 34 ++++++++++- .../coffee/helpers/MockV1Api.coffee | 14 +++++ .../SubscriptionControllerTests.coffee | 1 + 11 files changed, 151 insertions(+), 15 deletions(-) create mode 100644 services/web/app/views/subscriptions/dashboard/_v1_subscription_status.pug diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee index 3a4280e4ec..bea41ecb6e 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee @@ -12,6 +12,7 @@ UserGetter = require "../User/UserGetter" FeaturesUpdater = require './FeaturesUpdater' planFeatures = require './planFeatures' GroupPlansData = require './GroupPlansData' +V1SubscriptionManager = require "./V1SubscriptionManager" module.exports = SubscriptionController = @@ -97,7 +98,8 @@ module.exports = SubscriptionController = managedGroupSubscriptions, confirmedMemberInstitutions, managedInstitutions, - v1Subscriptions + v1Subscriptions, + v1SubscriptionStatus } = results logger.log { user, @@ -106,7 +108,8 @@ module.exports = SubscriptionController = managedGroupSubscriptions, confirmedMemberInstitutions, managedInstitutions, - v1Subscriptions + v1Subscriptions, + v1SubscriptionStatus }, "showing subscription dashboard" plans = SubscriptionViewModelBuilder.buildViewModel() data = { @@ -118,7 +121,8 @@ module.exports = SubscriptionController = managedGroupSubscriptions, confirmedMemberInstitutions, managedInstitutions, - v1Subscriptions + v1Subscriptions, + v1SubscriptionStatus } res.render "subscriptions/dashboard", data @@ -158,6 +162,15 @@ module.exports = SubscriptionController = return next(err) res.redirect "/user/subscription" + cancelV1Subscription: (req, res, next) -> + user_id = AuthenticationController.getLoggedInUserId(req) + logger.log {user_id}, "canceling v1 subscription" + V1SubscriptionManager.cancelV1Subscription user_id, (err)-> + if err? + logger.err err:err, user_id:user_id, "something went wrong canceling v1 subscription" + return next(err) + res.redirect "/user/subscription" + updateSubscription: (req, res, next)-> _origin = req?.query?.origin || null user = AuthenticationController.getSessionUser(req) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee index d81b5f3f59..c4155b58cb 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee @@ -40,6 +40,8 @@ module.exports = webRouter.post '/user/subscription/cancel', AuthenticationController.requireLogin(), SubscriptionController.cancelSubscription webRouter.post '/user/subscription/reactivate', AuthenticationController.requireLogin(), SubscriptionController.reactivateSubscription + webRouter.post '/user/subscription/v1/cancel', AuthenticationController.requireLogin(), SubscriptionController.cancelV1Subscription + webRouter.put '/user/subscription/extend', AuthenticationController.requireLogin(), SubscriptionController.extendTrial webRouter.get "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.renderUpgradeToAnnualPlanPage diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionViewModelBuilder.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionViewModelBuilder.coffee index deebb04aa5..3f65cc5cf5 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionViewModelBuilder.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionViewModelBuilder.coffee @@ -51,6 +51,10 @@ module.exports = return cb(error) if error? # Only return one argument to async.auto, otherwise it returns an array cb(null, subscriptions) + v1SubscriptionStatus: (cb) -> + V1SubscriptionManager.getSubscriptionStatusFromV1 user._id, (error, status, v1Id) -> + return cb(error) if error? + cb(null, status) }, (err, results) -> return callback(err) if err? { @@ -60,6 +64,7 @@ module.exports = confirmedMemberInstitutions, managedInstitutions, v1Subscriptions, + v1SubscriptionStatus, recurlySubscription, plan } = results @@ -68,6 +73,7 @@ module.exports = confirmedMemberInstitutions ?= [] managedInstitutions ?= [] v1Subscriptions ?= {} + v1SubscriptionStatus ?= {} if personalSubscription?.toObject? @@ -97,7 +103,8 @@ module.exports = memberGroupSubscriptions, confirmedMemberInstitutions, managedInstitutions, - v1Subscriptions + v1Subscriptions, + v1SubscriptionStatus } buildViewModel : -> diff --git a/services/web/app/coffee/Features/Subscription/V1SubscriptionManager.coffee b/services/web/app/coffee/Features/Subscription/V1SubscriptionManager.coffee index 2d25c8b3e5..0e63259734 100644 --- a/services/web/app/coffee/Features/Subscription/V1SubscriptionManager.coffee +++ b/services/web/app/coffee/Features/Subscription/V1SubscriptionManager.coffee @@ -39,6 +39,18 @@ module.exports = V1SubscriptionManager = url: (v1Id) -> "/api/v1/sharelatex/users/#{v1Id}/subscriptions" }, callback + getSubscriptionStatusFromV1: (userId, callback=(err, status) ->) -> + V1SubscriptionManager._v1Request userId, { + method: 'GET', + url: (v1Id) -> "/api/v1/sharelatex/users/#{v1Id}/subscription_status" + }, callback + + cancelV1Subscription: (userId, callback=(err)->) -> + V1SubscriptionManager._v1Request userId, { + method: 'DELETE', + url: (v1Id) -> "/api/v1/sharelatex/users/#{v1Id}/subscription" + }, callback + v1IdForUser: (userId, callback=(err, v1Id) ->) -> UserGetter.getUser userId, {'overleaf.id': 1}, (err, user) -> return callback(err) if err? @@ -76,7 +88,7 @@ module.exports = V1SubscriptionManager = pass: settings.apis.v1.pass sendImmediately: true json: true, - timeout: 5 * 1000 + timeout: 15 * 1000 }, (error, response, body) -> if error? # Specially handle no connection err, so warning can be shown diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 9539ce61d1..2eb51b9bf8 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -16,6 +16,7 @@ hashedFiles = {} Path = require 'path' Features = require "./Features" Modules = require "./Modules" +moment = require 'moment' jsPath = if Settings.useMinifiedJs @@ -126,6 +127,7 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)-> res.locals.fullJsPath = Url.resolve(staticFilesBase, jsPath) res.locals.lib = PackageVersions.lib + res.locals.moment = moment res.locals.buildJsPath = (jsFile, opts = {})-> path = Path.join(jsPath, jsFile) diff --git a/services/web/app/views/subscriptions/dashboard.pug b/services/web/app/views/subscriptions/dashboard.pug index 3becea865a..17e8114036 100644 --- a/services/web/app/views/subscriptions/dashboard.pug +++ b/services/web/app/views/subscriptions/dashboard.pug @@ -35,6 +35,9 @@ block content -if (settings.overleaf && v1Subscriptions) include ./dashboard/_v1_subscriptions + -if (v1SubscriptionStatus) + include ./dashboard/_v1_subscription_status + -if (!hasAnySubscription) p You're on the #{settings.appName} Free plan. | diff --git a/services/web/app/views/subscriptions/dashboard/_v1_subscription_status.pug b/services/web/app/views/subscriptions/dashboard/_v1_subscription_status.pug new file mode 100644 index 0000000000..4b2d01f7ca --- /dev/null +++ b/services/web/app/views/subscriptions/dashboard/_v1_subscription_status.pug @@ -0,0 +1,60 @@ +- if (v1SubscriptionStatus['team']) + p + | You have a legacy group licence from Overleaf v1. + - if (v1SubscriptionStatus['team']['will_end_at']) + p + | Your current group licence ends on + | + strong= moment(v1SubscriptionStatus['team']['will_end_at']).format('Do MMM YY') + | + | and will + | + - if (v1SubscriptionStatus['team']['will_renew']) + | be automatically renewed. + - else + | not be automatically renewed. + - if (v1SubscriptionStatus['can_cancel_team']) + p + form(method="POST", action="/user/subscription/v1/cancel") + input(type="hidden", name="_csrf", value=csrfToken) + button().btn.btn-danger Stop automatic renewal + - else + p + | Please + | + a(href="/contact") contact support + | + | to make changes to your plan + hr + +- if (v1SubscriptionStatus['product']) + p + | You have a legacy Overleaf v1 + | + strong= v1SubscriptionStatus['product']['display_name'] + | + | plan. + p + | Your plan ends on + | + strong= moment(v1SubscriptionStatus['product']['will_end_at']).format('Do MMM YY') + | + | and will + | + - if (v1SubscriptionStatus['product']['will_renew']) + | be automatically renewed. + - else + | not be automatically renewed. + - if (v1SubscriptionStatus['can_cancel']) + p + form(method="POST", action="/user/subscription/v1/cancel") + input(type="hidden", name="_csrf", value=csrfToken) + button().btn.btn-danger Stop automatic renewal + - else + p + | Please + | + a(href="/contact") contact support + | + | to make changes to your plan + hr diff --git a/services/web/app/views/subscriptions/dashboard/_v1_subscriptions.pug b/services/web/app/views/subscriptions/dashboard/_v1_subscriptions.pug index 1d0377df8c..e543c15960 100644 --- a/services/web/app/views/subscriptions/dashboard/_v1_subscriptions.pug +++ b/services/web/app/views/subscriptions/dashboard/_v1_subscriptions.pug @@ -1,11 +1,3 @@ -- if (v1Subscriptions.has_subscription) - -hasAnySubscription = true - p - | You are subscribed to Overleaf through Overleaf v1 - p - a.btn.btn-primary(href='/sign_in_to_v1?return_to=/users/edit%23status') Manage v1 Subscription - hr - - if (v1Subscriptions.teams && v1Subscriptions.teams.length > 0) -hasAnySubscription = true for team in v1Subscriptions.teams diff --git a/services/web/test/acceptance/coffee/SubscriptionTests.coffee b/services/web/test/acceptance/coffee/SubscriptionTests.coffee index bb19c3c779..f68d5ea8f0 100644 --- a/services/web/test/acceptance/coffee/SubscriptionTests.coffee +++ b/services/web/test/acceptance/coffee/SubscriptionTests.coffee @@ -234,7 +234,8 @@ describe 'Subscriptions', -> before (done) -> v1Id = MockV1Api.nextV1Id() MockV1Api.setUser v1Id, { - subscription: {} + subscription: {}, + subscription_status: {} } MockV1Api.setAffiliations [{ email: 'confirmed-affiliation-email@stanford.example.edu' @@ -281,6 +282,10 @@ describe 'Subscriptions', -> name: 'Test team' }] } + subscription_status: @subscription_status = { + product: { 'mock': 'product' } + team: null + } } @user.setV1Id v1Id, (error) => return done(error) if error? @@ -295,4 +300,29 @@ describe 'Subscriptions', -> expect(@data.memberGroupSubscriptions).to.deep.equal [] it 'should return a v1Subscriptions', -> - expect(@data.v1Subscriptions).to.deep.equal @subscription \ No newline at end of file + expect(@data.v1Subscriptions).to.deep.equal @subscription + + it 'should return a v1SubscriptionStatus', -> + expect(@data.v1SubscriptionStatus).to.deep.equal @subscription_status + + describe.only 'canceling', -> + before (done) -> + @user = new User() + MockV1Api.setUser v1Id = MockV1Api.nextV1Id(), @v1_user = {} + async.series [ + (cb) => @user.login(cb) + (cb) => @user.setV1Id(v1Id, cb) + ], (error) => + @user.request { + method: 'POST', + url: '/user/subscription/v1/cancel' + }, (error, @response) => + return done(error) if error? + done() + + it 'should tell v1 to cancel the subscription', -> + expect(@v1_user.canceled).to.equal true + + it 'should redirect to the subscription dashboard', -> + expect(@response.statusCode).to.equal 302 + expect(@response.headers.location).to.equal '/user/subscription' diff --git a/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee b/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee index dffebdc929..04c1b6446b 100644 --- a/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee +++ b/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee @@ -53,6 +53,20 @@ module.exports = MockV1Api = else res.sendStatus 404 + app.get "/api/v1/sharelatex/users/:v1_user_id/subscription_status", (req, res, next) => + user = @users[req.params.v1_user_id] + if user?.subscription_status? + res.json user.subscription_status + else + res.sendStatus 404 + + app.delete "/api/v1/sharelatex/users/:v1_user_id/subscription", (req, res, next) => + user = @users[req.params.v1_user_id] + if user? + user.canceled = true + res.sendStatus 200 + else + res.sendStatus 404 app.post "/api/v1/sharelatex/users/:v1_user_id/sync", (req, res, next) => @syncUserFeatures(req.params.v1_user_id) diff --git a/services/web/test/unit/coffee/Subscription/SubscriptionControllerTests.coffee b/services/web/test/unit/coffee/Subscription/SubscriptionControllerTests.coffee index 7fbb1df361..74a85967ae 100644 --- a/services/web/test/unit/coffee/Subscription/SubscriptionControllerTests.coffee +++ b/services/web/test/unit/coffee/Subscription/SubscriptionControllerTests.coffee @@ -79,6 +79,7 @@ describe "SubscriptionController", -> "./RecurlyWrapper": @RecurlyWrapper = {} "./FeaturesUpdater": @FeaturesUpdater = {} "./GroupPlansData": @GroupPlansData = {} + "./V1SubscriptionManager": @V1SubscriptionManager = {} @res = new MockResponse()