From d325623699ecb20b65367c7f120b2f677fe29ac4 Mon Sep 17 00:00:00 2001 From: laituan245 Date: Fri, 20 Feb 2015 06:14:34 +0900 Subject: [PATCH 1/3] Added ability to delete account --- app/locale/en.coffee | 4 ++ app/styles/account/account-settings-view.sass | 3 ++ .../account/account-settings-view.jade | 10 ++++ app/views/account/AccountSettingsView.coffee | 54 +++++++++++++++++-- server/users/user_handler.coffee | 13 ++++- 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 120ab8128..9c6c9142e 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -604,13 +604,17 @@ autosave: "Changes Save Automatically" me_tab: "Me" picture_tab: "Picture" + delete_account_tab: "Delete Your Account" + wrong_email: "Wrong Email" upload_picture: "Upload a picture" + delete_this_account: "Delete this account permanently" god_mode: "God Mode" password_tab: "Password" emails_tab: "Emails" admin: "Admin" new_password: "New Password" new_password_verify: "Verify" + type_in_email: "Type in your email to confirm the deletion" email_subscriptions: "Email Subscriptions" email_subscriptions_none: "No Email Subscriptions." email_announcements: "Announcements" diff --git a/app/styles/account/account-settings-view.sass b/app/styles/account/account-settings-view.sass index 66e8ac787..b09be588e 100644 --- a/app/styles/account/account-settings-view.sass +++ b/app/styles/account/account-settings-view.sass @@ -34,6 +34,9 @@ .panel-title font-size: 20px + #delete-account-panel-title + color: #F00 + //- Panel specific stuff .profile-photo diff --git a/app/templates/account/account-settings-view.jade b/app/templates/account/account-settings-view.jade index c53405651..980af0464 100644 --- a/app/templates/account/account-settings-view.jade +++ b/app/templates/account/account-settings-view.jade @@ -48,6 +48,16 @@ else label.control-label(for="password2", data-i18n="account_settings.new_password_verify") Verify input#password2.form-control(name="password2", type="password") + .panel.panel-default + .panel-heading + .panel-title#delete-account-panel-title(data-i18n="account_settings.delete_account_tab") + .panel-body + .form + .form-group + label.control-label(for="email1", data-i18n="account_settings.type_in_email") Type in your email to confirm the deletion + input#email1.form-control(name="email1", type="text") + button#delete-account-button.btn.form-control.btn-primary(data-i18n="account_settings.delete_this_account") + .col-md-6 #email-panel.panel.panel-default diff --git a/app/views/account/AccountSettingsView.coffee b/app/views/account/AccountSettingsView.coffee index 48d913813..9a09f15f9 100644 --- a/app/views/account/AccountSettingsView.coffee +++ b/app/views/account/AccountSettingsView.coffee @@ -4,6 +4,8 @@ template = require 'templates/account/account-settings-view' forms = require 'core/forms' User = require 'models/User' AuthModal = require 'views/core/AuthModal' +ConfirmModal = require 'views/editor/modal/ConfirmModal' +{logoutUser, me} = require('core/auth') module.exports = class AccountSettingsView extends CocoView id: 'account-settings-view' @@ -16,6 +18,7 @@ module.exports = class AccountSettingsView extends CocoView 'click #toggle-all-button': 'toggleEmailSubscriptions' 'click .profile-photo': 'onEditProfilePhoto' 'click #upload-photo-button': 'onEditProfilePhoto' + 'click #delete-account-button': 'confirmAccountDeletion' constructor: (options) -> super options @@ -35,10 +38,12 @@ module.exports = class AccountSettingsView extends CocoView #- Form input callbacks - onInputChanged: (e) -> $(e.target).addClass 'changed' - @trigger 'input-changed' + if (JSON.stringify(document.getElementById('email1').className)).indexOf("changed") > -1 + $(e.target).removeClass 'changed' + else + @trigger 'input-changed' toggleEmailSubscriptions: => subs = @getSubscriptions() @@ -61,7 +66,50 @@ module.exports = class AccountSettingsView extends CocoView #- Just copied from OptionsView, TODO refactor - + + confirmAccountDeletion: -> + forms.clearFormAlerts(@$el) + myEmail = me.get 'email' + email1 = document.getElementById('email1').value + if Boolean(email1) and email1 is myEmail + renderData = + 'confirmTitle': 'Are you really sure?' + 'confirmBody': 'This will completely delete your account. This action CANNOT be undone. Are you entirely sure?' + 'confirmDecline': 'Not really' + 'confirmConfirm': 'Definitely' + confirmModal = new ConfirmModal renderData + confirmModal.on 'confirm', @deleteAccount + @openModalView confirmModal + else + message = $.i18n.t('account_settings.wrong_email', defaultValue: 'Wrong Email.') + err = [message: message, property: 'email1', formatted: true] + forms.applyErrorsToForm(@$el, err) + $('.nano').nanoScroller({scrollTo: @$el.find('.has-error')}) + + deleteAccount: -> + myID = me.id + $.ajax + type: 'DELETE' + success: -> + noty + timeout: 5000 + text: 'Your account is gone.' + type: 'success' + layout: 'topCenter' + _.delay -> + Backbone.Mediator.publish("auth:logging-out", {}) + window.tracker?.trackEvent 'Log Out', category:'Homepage', ['Google Analytics'] if @id is 'home-view' + logoutUser($('#login-email').val()) + window.location = '../'; + , 500 + error: (jqXHR, status, error) -> + console.error jqXHR + timeout: 5000 + text: "Deleting account failed with error code #{jqXHR.status}" + type: 'error' + layout: 'topCenter' + url: "/db/user/#{myID}" + onEditProfilePhoto: (e) -> return if window.application.isIPadApp # TODO: have an iPad-native way of uploading a photo, since we don't want to load FilePicker on iPad (memory) photoContainer = @$el.find('.profile-photo') diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index ecd228697..059fdf45c 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -28,6 +28,8 @@ candidateProperties = [ UserHandler = class UserHandler extends Handler modelClass: User + allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] + getEditableProperties: (req, document) -> props = super req, document props.push 'permissions' unless config.isProduction @@ -211,11 +213,20 @@ UserHandler = class UserHandler extends Handler @put(req, res) hasAccessToDocument: (req, document) -> - if req.route.method in ['put', 'post', 'patch'] + if req.route.method in ['put', 'post', 'patch', 'delete'] return true if req.user?.isAdmin() return req.user?._id.equals(document._id) return true + delete: (req, res, userID) -> + @getDocumentForIdOrSlug userID, (err, user) => # Check first + return @sendDatabaseError res, err if err + return @sendNotFoundError res unless user + return @sendForbiddenError res unless @hasAccessToDocument(req, user) + user.remove (err, user) => + return @sendDatabaseError(res, err) if err + @sendNoContent res + getByRelationship: (req, res, args...) -> return @agreeToCLA(req, res) if args[1] is 'agreeToCLA' return @agreeToEmployerAgreement(req, res) if args[1] is 'agreeToEmployerAgreement' From eb5338108371d2e3a1c60fc22ab078aa5d07cf69 Mon Sep 17 00:00:00 2001 From: laituan245 Date: Tue, 24 Feb 2015 22:36:12 +0900 Subject: [PATCH 2/3] Don't delete the User object, just remove all the properties except for _id --- app/views/account/AccountSettingsView.coffee | 2 +- server/users/user_handler.coffee | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/views/account/AccountSettingsView.coffee b/app/views/account/AccountSettingsView.coffee index 9a09f15f9..ddeded5b3 100644 --- a/app/views/account/AccountSettingsView.coffee +++ b/app/views/account/AccountSettingsView.coffee @@ -100,7 +100,7 @@ module.exports = class AccountSettingsView extends CocoView Backbone.Mediator.publish("auth:logging-out", {}) window.tracker?.trackEvent 'Log Out', category:'Homepage', ['Google Analytics'] if @id is 'home-view' logoutUser($('#login-email').val()) - window.location = '../'; + window.location = '../' , 500 error: (jqXHR, status, error) -> console.error jqXHR diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index 059fdf45c..db5381d01 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -219,11 +219,18 @@ UserHandler = class UserHandler extends Handler return true delete: (req, res, userID) -> + # Instead of just deleting the User object, we should remove all the properties except for _id @getDocumentForIdOrSlug userID, (err, user) => # Check first return @sendDatabaseError res, err if err return @sendNotFoundError res unless user return @sendForbiddenError res unless @hasAccessToDocument(req, user) - user.remove (err, user) => + obj = user.toObject() + for prop, val of obj + if !(prop is '_id') + user.set(prop, undefined) + user.set('anonymous', true) + delete obj.dateCreated + user.save (err) => return @sendDatabaseError(res, err) if err @sendNoContent res From a588a3b2c8d02e6a9ad6b57eb59929cb4d774d95 Mon Sep 17 00:00:00 2001 From: laituan245 Date: Wed, 25 Feb 2015 01:54:30 +0900 Subject: [PATCH 3/3] Some updates to the PR #2359 --- app/core/auth.coffee | 7 ++++++- app/schemas/models/user.coffee | 1 + app/views/account/AccountSettingsView.coffee | 1 - server/users/user_handler.coffee | 10 +++++++--- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/core/auth.coffee b/app/core/auth.coffee index 06390af82..75e321de0 100644 --- a/app/core/auth.coffee +++ b/app/core/auth.coffee @@ -50,7 +50,12 @@ module.exports.loginUser = (userObject, failure=genericFailure, nextURL=null) -> module.exports.logoutUser = -> FB?.logout?() - res = $.post('/auth/logout', {}, -> window.location.reload()) + callback = -> + if (window.location.href.indexOf("/account/settings") > -1) + window.location = '/' + else + window.location.reload() + res = $.post('/auth/logout', {}, callback) res.fail(genericFailure) onSetVolume = (e) -> diff --git a/app/schemas/models/user.coffee b/app/schemas/models/user.coffee index 01b0765d9..f1c745f2c 100644 --- a/app/schemas/models/user.coffee +++ b/app/schemas/models/user.coffee @@ -276,6 +276,7 @@ _.extend UserSchema.properties, earned: c.RewardSchema 'earned by achievements' purchased: c.RewardSchema 'purchased with gems or money' + deleted: {type: 'boolean'} spent: {type: 'number'} stripeCustomerID: { type: 'string' } # TODO: Migrate away from this property diff --git a/app/views/account/AccountSettingsView.coffee b/app/views/account/AccountSettingsView.coffee index ddeded5b3..327eb7c99 100644 --- a/app/views/account/AccountSettingsView.coffee +++ b/app/views/account/AccountSettingsView.coffee @@ -100,7 +100,6 @@ module.exports = class AccountSettingsView extends CocoView Backbone.Mediator.publish("auth:logging-out", {}) window.tracker?.trackEvent 'Log Out', category:'Homepage', ['Google Analytics'] if @id is 'home-view' logoutUser($('#login-email').val()) - window.location = '../' , 500 error: (jqXHR, status, error) -> console.error jqXHR diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index db5381d01..b8cc6b6fa 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -220,16 +220,20 @@ UserHandler = class UserHandler extends Handler delete: (req, res, userID) -> # Instead of just deleting the User object, we should remove all the properties except for _id + # And add a `deleted: true` property @getDocumentForIdOrSlug userID, (err, user) => # Check first return @sendDatabaseError res, err if err return @sendNotFoundError res unless user return @sendForbiddenError res unless @hasAccessToDocument(req, user) obj = user.toObject() for prop, val of obj - if !(prop is '_id') - user.set(prop, undefined) - user.set('anonymous', true) + user.set(prop, undefined) unless prop is '_id' + user.set('deleted', true) + + # Hack to get saving of Users to work. Probably should replace these props with strings + # so that validation doesn't get hung up on Date objects in the documents. delete obj.dateCreated + user.save (err) => return @sendDatabaseError(res, err) if err @sendNoContent res