mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-27 09:35:39 -05:00
Add script that migrates users with email-formatted usernames
This commit is contained in:
parent
76e461398e
commit
a5f18f88be
5 changed files with 293 additions and 4 deletions
167
scripts/node/fixEmailFormattedUsernames.coffee
Normal file
167
scripts/node/fixEmailFormattedUsernames.coffee
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
# Usage:
|
||||||
|
# > coffee -c scripts/node/fixEmailFormattedUsernames.coffee; node scripts/node/fixEmailFormattedUsernames.js run
|
||||||
|
|
||||||
|
require('coffee-script');
|
||||||
|
require('coffee-script/register');
|
||||||
|
|
||||||
|
_ = require 'lodash'
|
||||||
|
sendwithus = require '../../server/sendwithus'
|
||||||
|
log = require 'winston'
|
||||||
|
str = require 'underscore.string'
|
||||||
|
co = require 'co'
|
||||||
|
|
||||||
|
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i
|
||||||
|
|
||||||
|
changedUsernameTemplate = _.template("
|
||||||
|
<p>
|
||||||
|
Hi, CodeCombat user!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Just letting you know we've made a change to your account settings which may change how you log in. Here are your old settings:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Old username: <%= oldUsername %></li>
|
||||||
|
<li>Old email: <%= oldEmail %></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
Your old username was an email address, but to reduce confusion, we now make sure email addresses can't be used as usernames.
|
||||||
|
<b><%= specialMessage %></b>
|
||||||
|
Here are your new settings:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>New username: <%= newUsername %></li>
|
||||||
|
<li>New email: <%= newEmail %></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Please <a href='https://codecombat.com/account/settings'>visit the site</a> if you would like to update your settings.
|
||||||
|
And let us know if you have any questions!
|
||||||
|
</p>
|
||||||
|
")
|
||||||
|
|
||||||
|
exports.run = ->
|
||||||
|
co ->
|
||||||
|
mongoose = require 'mongoose'
|
||||||
|
User = require '../../server/models/User'
|
||||||
|
users = yield User.find({nameLower: {$regex: filter}}).select({name:1, email:1, anonymous:1, slug:1})
|
||||||
|
console.log 'found', users.length, 'users'
|
||||||
|
|
||||||
|
for user in users
|
||||||
|
oldUsername = user.get('name')
|
||||||
|
oldEmail = user.get('email')
|
||||||
|
newUsername = null
|
||||||
|
newEmail = null
|
||||||
|
specialMessage = ''
|
||||||
|
|
||||||
|
if not oldEmail
|
||||||
|
otherUser = yield User.findByEmail(oldUsername)
|
||||||
|
if otherUser
|
||||||
|
specialMessage = "Since you had no email set, we would have made your old username your new email.
|
||||||
|
But '#{oldUsername}' is already used by another account as an email by another account,
|
||||||
|
so instead we changed your username."
|
||||||
|
newUsername = str.slugify(oldUsername)
|
||||||
|
newEmail = ''
|
||||||
|
|
||||||
|
else
|
||||||
|
specialMessage = "Since you had no email set, we simply made your old username your new email instead."
|
||||||
|
newEmail = oldUsername
|
||||||
|
newUsername = ''
|
||||||
|
|
||||||
|
|
||||||
|
else if oldEmail is oldUsername
|
||||||
|
specialMessage = "Since your email and username are the same, we simply removed your username."
|
||||||
|
newUsername = ''
|
||||||
|
newEmail = oldEmail
|
||||||
|
|
||||||
|
|
||||||
|
else if not filter.test(oldEmail)
|
||||||
|
otherEmailUser = yield User.findByEmail(oldUsername)
|
||||||
|
otherUsernameUser = yield User.findByName(oldEmail)
|
||||||
|
if otherEmailUser
|
||||||
|
specialMessage = "Since your old email looks like a username and your old username looks like an email,
|
||||||
|
we would have swapped them on your account.
|
||||||
|
But '#{oldUsername}' is already used as an email by another account,
|
||||||
|
so instead we changed your username."
|
||||||
|
newUsername = str.slugify(oldUsername)
|
||||||
|
newEmail = oldEmail
|
||||||
|
|
||||||
|
else if otherUsernameUser
|
||||||
|
specialMessage = "Since your old email looks like a username and your old username looks like an email,
|
||||||
|
we would have swapped them on your account.
|
||||||
|
But '#{oldEmail}' is already used as a username by another account,
|
||||||
|
so instead we changed your username."
|
||||||
|
newUsername = str.slugify(oldUsername)
|
||||||
|
newEmail = oldEmail
|
||||||
|
else
|
||||||
|
specialMessage = "Since your old email looks like a username and your old username looks like an email,
|
||||||
|
we swapped them on your account."
|
||||||
|
newUsername = oldEmail
|
||||||
|
newEmail = oldUsername
|
||||||
|
|
||||||
|
|
||||||
|
else if oldUsername and oldEmail
|
||||||
|
# Since oldEmail passed the email filter,
|
||||||
|
specialMessage = "Since your old email is valid, we simply removed your username."
|
||||||
|
newUsername = ''
|
||||||
|
newEmail = oldEmail
|
||||||
|
|
||||||
|
|
||||||
|
else
|
||||||
|
console.log('unhandled user', user.toObject())
|
||||||
|
throw new Error('Unhandled user')
|
||||||
|
|
||||||
|
user.set({name: newUsername, email: newEmail})
|
||||||
|
console.log JSON.stringify({
|
||||||
|
oldUsername, oldEmail, newUsername, newEmail, specialMessage, _id: user.id
|
||||||
|
})
|
||||||
|
yield user.save()
|
||||||
|
|
||||||
|
content = changedUsernameTemplate({
|
||||||
|
oldUsername: oldUsername or '<i>(no username)</i>'
|
||||||
|
oldEmail: oldEmail or '<i>(no email)</i>'
|
||||||
|
newUsername: newUsername or '<i>(no username)</i>'
|
||||||
|
newEmail: newEmail or '<i>(no email)</i>'
|
||||||
|
specialMessage
|
||||||
|
})
|
||||||
|
|
||||||
|
context =
|
||||||
|
template: sendwithus.templates.plain_text_email
|
||||||
|
recipient:
|
||||||
|
address: oldUsername
|
||||||
|
sender:
|
||||||
|
address: 'team@codecombat.com'
|
||||||
|
name: 'CodeCombat Team'
|
||||||
|
template_data:
|
||||||
|
subject: 'Your Username Has Changed'
|
||||||
|
contentHTML: content
|
||||||
|
|
||||||
|
# Also send to the original email if it's valid
|
||||||
|
if filter.test(oldEmail)
|
||||||
|
context.cc = [
|
||||||
|
{ address: oldEmail }
|
||||||
|
]
|
||||||
|
|
||||||
|
yield sendwithus.api.sendAsync(context)
|
||||||
|
|
||||||
|
return 'Done'
|
||||||
|
|
||||||
|
if _.last(process.argv) is 'run'
|
||||||
|
database = require '../../server/commons/database'
|
||||||
|
mongoose = require 'mongoose'
|
||||||
|
|
||||||
|
### SET UP ###
|
||||||
|
do (setupLodash = this) ->
|
||||||
|
GLOBAL._ = require 'lodash'
|
||||||
|
_.str = require 'underscore.string'
|
||||||
|
_.mixin _.str.exports()
|
||||||
|
GLOBAL.tv4 = require('tv4').tv4
|
||||||
|
|
||||||
|
database.connect()
|
||||||
|
co ->
|
||||||
|
yield exports.run()
|
||||||
|
process.exit()
|
||||||
|
|
|
@ -10,6 +10,7 @@ Classroom = require '../models/Classroom'
|
||||||
languages = require '../routes/languages'
|
languages = require '../routes/languages'
|
||||||
_ = require 'lodash'
|
_ = require 'lodash'
|
||||||
errors = require '../commons/errors'
|
errors = require '../commons/errors'
|
||||||
|
Promise = require 'bluebird'
|
||||||
|
|
||||||
config = require '../../server_config'
|
config = require '../../server_config'
|
||||||
stripe = require('stripe')(config.stripe.secretKey)
|
stripe = require('stripe')(config.stripe.secretKey)
|
||||||
|
@ -270,6 +271,8 @@ UserSchema.statics.unconflictName = unconflictName = (name, done) ->
|
||||||
suffix = _.random(0, 9) + ''
|
suffix = _.random(0, 9) + ''
|
||||||
unconflictName name + suffix, done
|
unconflictName name + suffix, done
|
||||||
|
|
||||||
|
UserSchema.statics.unconflictNameAsync = Promise.promisify(unconflictName)
|
||||||
|
|
||||||
UserSchema.methods.sendWelcomeEmail = ->
|
UserSchema.methods.sendWelcomeEmail = ->
|
||||||
return if not @get('email')
|
return if not @get('email')
|
||||||
{ welcome_email_student, welcome_email_user } = sendwithus.templates
|
{ welcome_email_student, welcome_email_user } = sendwithus.templates
|
||||||
|
@ -361,9 +364,10 @@ UserSchema.pre('save', (next) ->
|
||||||
@set('email', undefined)
|
@set('email', undefined)
|
||||||
@set('emailLower', undefined)
|
@set('emailLower', undefined)
|
||||||
if name = @get('name')
|
if name = @get('name')
|
||||||
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990
|
if not @allowEmailNames # for testing
|
||||||
if filter.test(name)
|
filter = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$/i # https://news.ycombinator.com/item?id=5763990
|
||||||
return next(new errors.UnprocessableEntity('Name may not be an email'))
|
if filter.test(name)
|
||||||
|
return next(new errors.UnprocessableEntity('Name may not be an email'))
|
||||||
|
|
||||||
@set('nameLower', name.toLowerCase())
|
@set('nameLower', name.toLowerCase())
|
||||||
else
|
else
|
||||||
|
|
|
@ -122,4 +122,4 @@ module.exports.setup = (app) ->
|
||||||
app.put('/db/trial.request/:handle', mw.auth.checkHasPermission(['admin']), mw.trialRequests.put)
|
app.put('/db/trial.request/:handle', mw.auth.checkHasPermission(['admin']), mw.trialRequests.put)
|
||||||
app.get('/db/trial.request/-/users', mw.auth.checkHasPermission(['admin']), mw.trialRequests.getUsers)
|
app.get('/db/trial.request/-/users', mw.auth.checkHasPermission(['admin']), mw.trialRequests.getUsers)
|
||||||
|
|
||||||
app.get('/healthcheck', mw.healthcheck)
|
app.get('/healthcheck', mw.healthcheck)
|
||||||
|
|
|
@ -2,6 +2,7 @@ config = require '../server_config'
|
||||||
sendwithusAPI = require 'sendwithus'
|
sendwithusAPI = require 'sendwithus'
|
||||||
swuAPIKey = config.mail.sendwithusAPIKey
|
swuAPIKey = config.mail.sendwithusAPIKey
|
||||||
log = require 'winston'
|
log = require 'winston'
|
||||||
|
Promise = require 'bluebird'
|
||||||
|
|
||||||
module.exports.setupRoutes = (app) ->
|
module.exports.setupRoutes = (app) ->
|
||||||
return
|
return
|
||||||
|
@ -14,6 +15,8 @@ module.exports.api =
|
||||||
|
|
||||||
if swuAPIKey
|
if swuAPIKey
|
||||||
module.exports.api = new sendwithusAPI swuAPIKey, debug
|
module.exports.api = new sendwithusAPI swuAPIKey, debug
|
||||||
|
|
||||||
|
Promise.promisifyAll(module.exports.api)
|
||||||
|
|
||||||
module.exports.templates =
|
module.exports.templates =
|
||||||
parent_subscribe_email: 'tem_2APERafogvwKhmcnouigud'
|
parent_subscribe_email: 'tem_2APERafogvwKhmcnouigud'
|
||||||
|
|
115
spec/server/scripts/fixEmailFormattedUsernames.spec.coffee
Normal file
115
spec/server/scripts/fixEmailFormattedUsernames.spec.coffee
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
# Don't need to run this regularly, only running script once.
|
||||||
|
|
||||||
|
#utils = require '../utils'
|
||||||
|
#User = require '../../../server/models/User'
|
||||||
|
#request = require '../request'
|
||||||
|
#sendwithus = require '../../../server/sendwithus'
|
||||||
|
#fixEmailFormattedUsernames = require '../../../scripts/node/fixEmailFormattedUsernames'
|
||||||
|
#
|
||||||
|
#describe '/scripts/node/fixEmailFormattedUsernames', ->
|
||||||
|
#
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# yield utils.clearModels([User])
|
||||||
|
# console.log('spy on send async')
|
||||||
|
# spyOn(sendwithus.api, 'sendAsync').and.callThrough()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# afterEach ->
|
||||||
|
# expect(sendwithus.api.sendAsync).toHaveBeenCalled()
|
||||||
|
#
|
||||||
|
# describe "when a user has no email set", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @user = new User({name: 'an@email.com', points:100})
|
||||||
|
# @user.allowEmailNames = true
|
||||||
|
# yield @user.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it 'moves the email-formatted username to be the user\'s email', utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('an@email.com')
|
||||||
|
# expect(user.get('name')).toBeUndefined()
|
||||||
|
# expect(user.get('points')).toBe(100) # make sure properties aren't removed
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# describe "when another user exists with that email", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @otherUser = new User({email: 'an@email.com'})
|
||||||
|
# yield @otherUser.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "slugifies the target user's username", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBeUndefined()
|
||||||
|
# expect(user.get('name')).toBe('anemailcom')
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# describe "when a user has the same email and username", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @user = new User({name: 'an@email.com', email: 'an@email.com'})
|
||||||
|
# @user.allowEmailNames = true
|
||||||
|
# yield @user.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "removes the user's username", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('an@email.com')
|
||||||
|
# expect(user.get('name')).toBeUndefined()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# describe "when the user has an email that isn't formatted like an email", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @user = new User({name: 'an@email.com', email: 'a name'})
|
||||||
|
# @user.allowEmailNames = true
|
||||||
|
# yield @user.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "swaps the two", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('an@email.com')
|
||||||
|
# expect(user.get('name')).toBe('a name')
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# describe "when another user already has the email-formatted name as an email", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @otherUser = new User({email: 'an@email.com'})
|
||||||
|
# yield @otherUser.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "slugifies the target user's username", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('a name')
|
||||||
|
# expect(user.get('name')).toBe('anemailcom')
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# describe "when another user already has the non-email-formatted email as a username", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @otherUser = new User({name: 'a name'})
|
||||||
|
# yield @otherUser.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "slugifies the target user's username", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('a name')
|
||||||
|
# expect(user.get('name')).toBe('anemailcom')
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# describe "when the user has a different but well formatted email set", ->
|
||||||
|
# beforeEach utils.wrap (done) ->
|
||||||
|
# @user = new User({name: 'an@email.com', email: 'another@email.com'})
|
||||||
|
# @user.allowEmailNames = true
|
||||||
|
# yield @user.save()
|
||||||
|
# done()
|
||||||
|
#
|
||||||
|
# it "removes the target user's username", utils.wrap (done) ->
|
||||||
|
# yield fixEmailFormattedUsernames.run()
|
||||||
|
# user = yield User.findById(@user.id)
|
||||||
|
# expect(user.get('email')).toBe('another@email.com')
|
||||||
|
# expect(user.get('name')).toBeUndefined()
|
||||||
|
# done()
|
Loading…
Reference in a new issue