Merge branch 'master' into production

This commit is contained in:
Scott Erickson 2014-07-10 14:18:50 -07:00
commit 9633373d67
15 changed files with 560 additions and 41 deletions

View file

@ -76,6 +76,8 @@ module.exports.getByPath = (target, path) ->
obj = obj[piece] obj = obj[piece]
obj obj
module.exports.isID = (id) -> _.isString(id) and id.length is 24 and id.match(/[a-f0-9]/gi)?.length is 24
module.exports.round = _.curry (digits, n) -> module.exports.round = _.curry (digits, n) ->
n = +n.toFixed(digits) n = +n.toFixed(digits)

View file

@ -1,6 +1,7 @@
GRAVATAR_URL = 'https://www.gravatar.com/' GRAVATAR_URL = 'https://www.gravatar.com/'
cache = {} cache = {}
CocoModel = require './CocoModel' CocoModel = require './CocoModel'
util = require 'lib/utils'
module.exports = class User extends CocoModel module.exports = class User extends CocoModel
@className: 'User' @className: 'User'
@ -45,6 +46,28 @@ module.exports = class User extends CocoModel
cache[id] = user cache[id] = user
user user
# callbacks can be either success or error
@getByIDOrSlug: (idOrSlug, force, callbacks={}) ->
{me} = require 'lib/auth'
isID = util.isID idOrSlug
if me.id is idOrSlug or me.slug is idOrSlug
callbacks.success me if callbacks.success?
return me
cached = cache[idOrSlug]
user = cached or new @ _id: idOrSlug
if force or not cached
user.loading = true
user.fetch
success: ->
user.loading = false
Backbone.Mediator.publish 'user:fetched'
callbacks.success user if callbacks.success?
error: ->
user.loading = false
callbacks.error user if callbacks.error?
cache[idOrSlug] = user
user
getEnabledEmails: -> getEnabledEmails: ->
@migrateEmails() @migrateEmails()
emails = _.clone(@get('emails')) or {} emails = _.clone(@get('emails')) or {}

View file

@ -1,8 +1,12 @@
c = require './../schemas' c = require './../schemas'
emailSubscriptions = ['announcement', 'tester', 'level_creator', 'developer', 'article_editor', 'translator', 'support', 'notification'] emailSubscriptions = ['announcement', 'tester', 'level_creator', 'developer', 'article_editor', 'translator', 'support', 'notification']
UserSchema = c.object {}, UserSchema = c.object
name: c.shortString({title: 'Display Name', default: ''}) title: 'User'
c.extendNamedProperties UserSchema # let's have the name be the first property
_.extend UserSchema.properties,
email: c.shortString({title: 'Email', format: 'email'}) email: c.shortString({title: 'Email', format: 'email'})
firstName: c.shortString({title: 'First Name'}) firstName: c.shortString({title: 'First Name'})
lastName: c.shortString({title: 'Last Name'}) lastName: c.shortString({title: 'Last Name'})

View file

@ -0,0 +1,128 @@
#terrain-randomise-modal
.choose-option
margin-bottom: 15px
width: 100%
height: 100px
overflow: hidden
background: white
border: 1px solid #333
position: relative
-webkit-transition: opacity 0.3s ease-in-out
-moz-transition: opacity 0.3s ease-in-out
-ms-transition: opacity 0.3s ease-in-out
-o-transition: opacity 0.3s ease-in-out
transition: opacity 0.3s ease-in-out
opacity: 0.4
border-radius: 5px
.only-one
-webkit-transition: opacity 0.3s ease-in-out
-moz-transition: opacity 0.3s ease-in-out
-ms-transition: opacity 0.3s ease-in-out
-o-transition: opacity 0.3s ease-in-out
transition: opacity 0.3s ease-in-out
opacity: 0
.choose-option:hover
opacity: 1
.only-one
opacity: 1
.my-icon
position: relative
left: 0
top: -10px
z-index: 1
.my-team-icon
height: 60px
position: relative
top: -10px
left: 10px
z-index: 0
.opponent-team-icon
height: 60px
position: relative
top: 10px
right: 10px
z-index: 0
float: right
-moz-transform: scaleX(-1)
-o-transform: scaleX(-1)
-webkit-transform: scaleX(-1)
transform: scaleX(-1)
filter: FlipH
-ms-filter: "FlipH"
.opponent-icon
position: relative
float: right
right: 0
top: -10px
-moz-transform: scaleX(-1)
-o-transform: scaleX(-1)
-webkit-transform: scaleX(-1)
transform: scaleX(-1)
filter: FlipH
-ms-filter: "FlipH"
z-index: 1
.name-label
border-bottom: 20px solid lightslategray
height: 0
width: 40%
position: absolute
bottom: 0
color: black
font-weight: bold
text-align: center
z-index: 2
span
position: relative
top: 1px
.my-name
border-right: 15px solid transparent
left: 0
span
left: 3px
.preset-size
border-left: 15px solid transparent
right: 0
//text-align: right
span
right: 3px
.preset-name
border-top: 25px solid darkgray
border-left: 20px solid transparent
border-right: 20px solid transparent
height: 0
width: 30%
position: absolute
left: 35%
top: 0
color: black
text-align: center
font-size: 18px
font-weight: bold
span
position: relative
top: -25px
.easy-option .preset-name
border-top: 25px solid limegreen
.medium-option .preset-name
border-top: 25px solid darkorange
.hard-option .preset-name
border-top: 25px solid black
color: white

View file

@ -79,6 +79,8 @@ block header
a(data-i18n="common.fork")#fork-level-start-button Fork a(data-i18n="common.fork")#fork-level-start-button Fork
li(class=anonymous ? "disabled": "") li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/revert", data-i18n="editor.revert")#revert-button Revert a(data-toggle="coco-modal", data-target="modal/revert", data-i18n="editor.revert")#revert-button Revert
li(class=anonymous ? "disabled": "")
a(data-toggle="coco-modal", data-target="modal/terrain_randomise", data-i18n="editor.randomise")#randomise-button Randomise
li(class=anonymous ? "disabled": "") li(class=anonymous ? "disabled": "")
a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n
li.divider li.divider

View file

@ -0,0 +1,15 @@
extends /templates/modal/modal_base
block modal-header-content
h3(data-i18n="editor.pick_a_terrain") Pick a Terrain
block modal-body-content
div#normal-view
a(href="#")
div.choose-option(data-preset-type="grassy", data-preset-size="small")
div.preset-size.name-label
span(data-i18n="ladder.small") Small
div.preset-name
span(data-i18n="ladder.grassy") Grassy
//- for model in models
block modal-footer

View file

@ -43,6 +43,7 @@ module.exports = class ThangsTabView extends View
'sprite:mouse-up': 'onSpriteMouseUp' 'sprite:mouse-up': 'onSpriteMouseUp'
'sprite:double-clicked': 'onSpriteDoubleClicked' 'sprite:double-clicked': 'onSpriteDoubleClicked'
'surface:stage-mouse-up': 'onStageMouseUp' 'surface:stage-mouse-up': 'onStageMouseUp'
'randomise:terrain-generated': 'onRandomiseTerrain'
events: events:
'click #extant-thangs-filter button': 'onFilterExtantThangs' 'click #extant-thangs-filter button': 'onFilterExtantThangs'
@ -57,6 +58,8 @@ module.exports = class ThangsTabView extends View
'delete, del, backspace': 'deleteSelectedExtantThang' 'delete, del, backspace': 'deleteSelectedExtantThang'
'left': -> @moveAddThangSelection -1 'left': -> @moveAddThangSelection -1
'right': -> @moveAddThangSelection 1 'right': -> @moveAddThangSelection 1
'ctrl+z': 'undoAction'
'ctrl+shift+z': 'redoAction'
constructor: (options) -> constructor: (options) ->
super options super options
@ -221,6 +224,12 @@ module.exports = class ThangsTabView extends View
return unless e.thang return unless e.thang
@editThang thangID: e.thang.id @editThang thangID: e.thang.id
onRandomiseTerrain: (e) ->
for thang in e.thangs
@selectAddThangType thang.id
@addThang @addThangType, thang.pos
@selectAddThangType null
# TODO: figure out a good way to have all Surface clicks and Treema clicks just proxy in one direction, so we can maintain only one way of handling selection and deletion # TODO: figure out a good way to have all Surface clicks and Treema clicks just proxy in one direction, so we can maintain only one way of handling selection and deletion
onExtantThangSelected: (e) -> onExtantThangSelected: (e) ->
@selectedExtantSprite?.setNameLabel? null unless @selectedExtantSprite is e.sprite @selectedExtantSprite?.setNameLabel? null unless @selectedExtantSprite is e.sprite
@ -450,6 +459,12 @@ module.exports = class ThangsTabView extends View
$('#add-thangs-column').toggle() $('#add-thangs-column').toggle()
@onWindowResize e @onWindowResize e
undoAction: (e) ->
@thangsTreema.undo()
redoAction: (e) ->
@thangsTreema.redo()
class ThangsNode extends TreemaNode.nodeMap.array class ThangsNode extends TreemaNode.nodeMap.array
valueClass: 'treema-array-replacement' valueClass: 'treema-array-replacement'
getChildren: -> getChildren: ->

View file

@ -0,0 +1,188 @@
ModalView = require 'views/kinds/ModalView'
template = require 'templates/modal/terrain_randomise'
CocoModel = require 'models/CocoModel'
clusters = {
'rocks': ['Rock 1', 'Rock 2', 'Rock 3', 'Rock 4', 'Rock 5', 'Rock Cluster 1', 'Rock Cluster 2', 'Rock Cluster 3']
'trees': ['Tree 1', 'Tree 2', 'Tree 3', 'Tree 4']
'shrubs': ['Shrub 1', 'Shrub 2', 'Shrub 3']
'houses': ['House 1', 'House 2', 'House 3', 'House 4']
'animals': ['Cow', 'Horse']
'wood': ['Firewood 1', 'Firewood 2', 'Firewood 3', 'Barrel']
'farm': ['Farm']
}
presets = {
# 'dungeon': {
# 'type':'dungeon'
# 'borders':['Dungeon Wall']
# 'floors':['Dungeon Floor']
# 'decorations':[]
# }
'grassy': {
'type':'grassy'
'borders':['Tree 1', 'Tree 2', 'Tree 3']
'floors':['Grass01', 'Grass02', 'Grass03']
'decorations': {
'house': {
'num':[1,2] #min-max
'width': 20
'height': 20
'clusters': {
'houses':[1,1]
'trees':[1,2]
'shrubs':[0,3]
'rocks':[1,2]
}
}
'farm': {
'num':[1,2] #min-max
'width': 20
'height': 20
'clusters': {
'farm':[1,1]
'shrubs':[2,3]
'wood':[2,4]
'animals':[2,3]
}
}
}
}
}
sizes = {
'small': {
'x':80
'y':68
}
'large': {
'x':160
'y':136
}
'floorSize': {
'x':20
'y':20
}
'borderSize': {
'x':4
'y':4
}
}
module.exports = class TerrainRandomiseModal extends ModalView
id: 'terrain-randomise-modal'
template: template
thangs = []
events:
'click .choose-option': 'onRandomise'
onRevertModel: (e) ->
id = $(e.target).val()
CocoModel.backedUp[id].revert()
$(e.target).closest('tr').remove()
@reloadOnClose = true
onRandomise: (e) ->
target = $(e.target)
presetType = target.attr 'data-preset-type'
presetSize = target.attr 'data-preset-size'
@randomiseThangs presetType, presetSize
Backbone.Mediator.publish('randomise:terrain-generated',
'thangs': @thangs
)
@hide()
randomiseThangs: (presetName, presetSize) ->
preset = presets[presetName]
presetSize = sizes[presetSize]
@thangs = []
@randomiseFloor preset, presetSize
@randomiseBorder preset, presetSize
@randomiseDecorations preset, presetSize
randomiseFloor: (preset, presetSize) ->
for i in _.range(0, presetSize.x, sizes.floorSize.x)
for j in _.range(0, presetSize.y, sizes.floorSize.y)
@thangs.push {
'id': @getRandomThang(preset.floors)
'pos': {
'x': i
'y': j
}
}
randomiseBorder: (preset, presetSize) ->
for i in _.range(0-sizes.floorSize.x/2+sizes.borderSize.x, presetSize.x-sizes.floorSize.x/2, sizes.borderSize.x)
@thangs.push {
'id': @getRandomThang(preset.borders)
'pos': {
'x': i
'y': 0-sizes.floorSize.x/2
}
}
@thangs.push {
'id': @getRandomThang(preset.borders)
'pos': {
'x': i
'y': presetSize.y - sizes.borderSize.y
}
}
for i in _.range(0-sizes.floorSize.y/2, presetSize.y-sizes.borderSize.y, sizes.borderSize.y)
@thangs.push {
'id': @getRandomThang(preset.borders)
'pos': {
'x': 0-sizes.floorSize.x/2+sizes.borderSize.x
'y': i
}
}
@thangs.push {
'id': @getRandomThang(preset.borders)
'pos': {
'x': presetSize.x - sizes.borderSize.x - sizes.floorSize.x/2
'y': i
}
}
randomiseDecorations: (preset, presetSize)->
for name, decoration of preset.decorations
for num in _.range(_.random(decoration.num[0], decoration.num[1]))
center =
{
'x':_.random(decoration.width, presetSize.x - decoration.width),
'y':_.random(decoration.height, presetSize.y - decoration.height)
}
min =
{
'x':center.x - decoration.width/2
'y':center.y - decoration.height/2
}
max =
{
'x':center.x + decoration.width/2
'y':center.y + decoration.height/2
}
for cluster, range of decoration.clusters
for i in _.range(_.random(range[0], range[1]))
@thangs.push {
'id':@getRandomThang(clusters[cluster])
'pos':{
'x':_.random(min.x, max.x)
'y':_.random(min.y, max.y)
}
}
getRandomThang: (thangList) ->
return thangList[_.random(0, thangList.length-1)]
getRenderData: ->
c = super()
models = _.values CocoModel.backedUp
models = (m for m in models when m.hasLocalChanges())
c.models = models
c
onHidden: ->
location.reload() if @reloadOnClose

View file

@ -0,0 +1,56 @@
load('bower_components/lodash/dist/lodash.js');
load('bower_components/underscore.string/dist/underscore.string.min.js');
var slugs = {};
var num = 0;
var unconflictName;
unconflictName = function(name) {
var otherUser, suffix;
otherUser = db.users.findOne({
slug: _.string.slugify(name)
});
if (!otherUser) {
return name;
}
suffix = _.random(0, 9) + '';
return unconflictName(name + suffix);
};
var params = {
name:1,
emails:1,
email:1,
slug:1,
dateCreated:1
};
db.users.find({anonymous:false}, params).sort({_id:1}).forEach(function (user) {
num += 1;
var slug = _.string.slugify(user.name);
if(!slug) return;
var update = {};
if(slugs[slug]) {
originalName = slugs[slug];
conflictingName = user.name;
availableName = unconflictName(conflictingName);
conflictingSlug = slug;
slug = _.string.slugify(availableName);
update.name = availableName;
update.nameLower = availableName.toLowerCase();
if (!(user.emails && user.emails.anyNotes === false))
db.changedEmails.insert({email:user.email, user:user._id, name:user.name});
print(_.str.sprintf('\n\n\tConflict! Username "%s" conflicts with "%s" (both sluggify to "%s"). Changing to "%s"\n\n\n',
conflictingName, originalName, conflictingSlug, availableName));
}
update.slug = slug;
slugs[slug] = user.name;
if(user.slug) return;
print(_.str.sprintf('Setting user %s (%s) to slug %s with update %s', user.name, user.dateCreated, slug, JSON.stringify({$set:update})));
var res = db.users.update({_id:user._id}, {$set:update});
if(res.hasWriteError()) {
print("\n\n\n\n\n\n\n\n\n\nOH NOOOOOOOOO\n\n\n\n\n\n\n");
db.changedEmails.insert({email:user.email, user:user._id, name:user.name, error:true});
}
});

View file

@ -41,9 +41,12 @@ module.exports.NamedPlugin = (schema) ->
err.response = {message: ' is a reserved name', property: 'name'} err.response = {message: ' is a reserved name', property: 'name'}
err.code = 422 err.code = 422
return next(err) return next(err)
if newSlug isnt @get('slug') if newSlug not in [@get('slug'), ''] and not @get 'anonymous'
@set('slug', newSlug) @set('slug', newSlug)
@checkSlugConflicts(next) @checkSlugConflicts(next)
else if newSlug is '' and @get 'slug'
@set 'slug', undefined
next()
else else
next() next()
) )

View file

@ -159,15 +159,11 @@ module.exports.setup = (app) ->
module.exports.loginUser = loginUser = (req, res, user, send=true, next=null) -> module.exports.loginUser = loginUser = (req, res, user, send=true, next=null) ->
user.save((err) -> user.save((err) ->
if err return errors.serverError res, err if err?
return @sendDatabaseError(res, err)
req.logIn(user, (err) -> req.logIn(user, (err) ->
if err return errors.serverError res, err if err?
return @sendDatabaseError(res, err) return res.send user if send
if send
return @sendSuccess(res, user)
next() if next next() if next
) )
) )

View file

@ -4,6 +4,7 @@ crypto = require 'crypto'
{salt, isProduction} = require '../../server_config' {salt, isProduction} = require '../../server_config'
mail = require '../commons/mail' mail = require '../commons/mail'
log = require 'winston' log = require 'winston'
plugins = require '../plugins/plugins'
sendwithus = require '../sendwithus' sendwithus = require '../sendwithus'
@ -29,6 +30,9 @@ UserSchema.methods.isAdmin = ->
p = @get('permissions') p = @get('permissions')
return p and 'admin' in p return p and 'admin' in p
UserSchema.methods.isAnonymous = ->
@get 'anonymous'
UserSchema.methods.trackActivity = (activityName, increment) -> UserSchema.methods.trackActivity = (activityName, increment) ->
now = new Date() now = new Date()
increment ?= parseInt increment or 1 increment ?= parseInt increment or 1
@ -108,6 +112,30 @@ UserSchema.statics.updateMailChimp = (doc, callback) ->
mc?.lists.subscribe params, onSuccess, onFailure mc?.lists.subscribe params, onSuccess, onFailure
UserSchema.statics.unconflictName = unconflictName = (name, done) ->
User.findOne {slug: _.str.slugify(name)}, (err, otherUser) ->
return done err if err?
return done null, name unless otherUser
suffix = _.random(0, 9) + ''
unconflictName name + suffix, done
UserSchema.methods.register = (done) ->
@set('anonymous', false)
@set('permissions', ['admin']) if not isProduction
if (name = @get 'name')? and name isnt ''
unconflictName name, (err, uniqueName) =>
return done err if err
@set 'name', uniqueName
done()
else done()
data =
email_id: sendwithus.templates.welcome_email
recipient:
address: @get 'email'
sendwithus.api.send data, (err, result) ->
log.error "sendwithus post-save error: #{err}, result: #{result}" if err
UserSchema.pre('save', (next) -> UserSchema.pre('save', (next) ->
@set('emailLower', @get('email')?.toLowerCase()) @set('emailLower', @get('email')?.toLowerCase())
@set('nameLower', @get('name')?.toLowerCase()) @set('nameLower', @get('name')?.toLowerCase())
@ -115,16 +143,10 @@ UserSchema.pre('save', (next) ->
if @get('password') if @get('password')
@set('passwordHash', User.hashPassword(pwd)) @set('passwordHash', User.hashPassword(pwd))
@set('password', undefined) @set('password', undefined)
if @get('email') and @get('anonymous') if @get('email') and @get('anonymous') # a user registers
@set('anonymous', false) @register next
@set('permissions', ['admin']) if not isProduction else
data = next()
email_id: sendwithus.templates.welcome_email
recipient:
address: @get 'email'
sendwithus.api.send data, (err, result) ->
log.error "sendwithus post-save error: #{err}, result: #{result}" if err
next()
) )
UserSchema.post 'save', (doc) -> UserSchema.post 'save', (doc) ->
@ -136,6 +158,8 @@ UserSchema.statics.hashPassword = (password) ->
shasum.update(salt + password) shasum.update(salt + password)
shasum.digest('hex') shasum.digest('hex')
UserSchema.plugin plugins.NamedPlugin
module.exports = User = mongoose.model('User', UserSchema) module.exports = User = mongoose.model('User', UserSchema)
AchievablePlugin = require '../plugins/achievements' AchievablePlugin = require '../plugins/achievements'

View file

@ -104,7 +104,8 @@ UserHandler = class UserHandler extends Handler
return callback(null, req, user) unless req.body.name return callback(null, req, user) unless req.body.name
nameLower = req.body.name?.toLowerCase() nameLower = req.body.name?.toLowerCase()
return callback(null, req, user) unless nameLower return callback(null, req, user) unless nameLower
return callback(null, req, user) if nameLower is user.get('nameLower') and not user.get('anonymous') return callback(null, req, user) if user.get 'anonymous' # anonymous users can have any name
return callback(null, req, user) if nameLower is user.get('nameLower')
User.findOne({nameLower: nameLower, anonymous: false}).exec (err, otherUser) -> User.findOne({nameLower: nameLower, anonymous: false}).exec (err, otherUser) ->
log.error "Database error setting user name: #{err}" if err log.error "Database error setting user name: #{err}" if err
return callback(res: 'Database error.', code: 500) if err return callback(res: 'Database error.', code: 500) if err
@ -116,7 +117,7 @@ UserHandler = class UserHandler extends Handler
] ]
getById: (req, res, id) -> getById: (req, res, id) ->
if req.user?._id.equals(id) if Handler.isID(id) and req.user?._id.equals(id)
return @sendSuccess(res, @formatEntity(req, req.user, 256)) return @sendSuccess(res, @formatEntity(req, req.user, 256))
super(req, res, id) super(req, res, id)

View file

View file

@ -44,7 +44,7 @@ describe 'User.updateMailChimp', ->
describe 'POST /db/user', -> describe 'POST /db/user', ->
createAnonNameUser = (done)-> createAnonNameUser = (name, done)->
request.post getURL('/auth/logout'), -> request.post getURL('/auth/logout'), ->
request.get getURL('/auth/whoami'), -> request.get getURL('/auth/whoami'), ->
req = request.post(getURL('/db/user'), (err, response) -> req = request.post(getURL('/db/user'), (err, response) ->
@ -52,11 +52,11 @@ describe 'POST /db/user', ->
request.get getURL('/auth/whoami'), (request, response, body) -> request.get getURL('/auth/whoami'), (request, response, body) ->
res = JSON.parse(response.body) res = JSON.parse(response.body)
expect(res.anonymous).toBeTruthy() expect(res.anonymous).toBeTruthy()
expect(res.name).toEqual('Jim') expect(res.name).toEqual(name)
done() done()
) )
form = req.form() form = req.form()
form.append('name', 'Jim') form.append('name', name)
it 'preparing test : clears the db first', (done) -> it 'preparing test : clears the db first', (done) ->
clearModels [User], (err) -> clearModels [User], (err) ->
@ -105,30 +105,18 @@ describe 'POST /db/user', ->
done() done()
it 'should allow setting anonymous user name', (done) -> it 'should allow setting anonymous user name', (done) ->
createAnonNameUser(done) createAnonNameUser('Jim', done)
it 'should allow multiple anonymous users with same name', (done) -> it 'should allow multiple anonymous users with same name', (done) ->
createAnonNameUser(done) createAnonNameUser('Jim', done)
it 'should not allow setting existing user name to anonymous user', (done) ->
createAnonUser = ->
request.post getURL('/auth/logout'), ->
request.get getURL('/auth/whoami'), ->
req = request.post(getURL('/db/user'), (err, response) ->
expect(response.statusCode).toBe(409)
done()
)
form = req.form()
form.append('name', 'Jim')
it 'should allow setting existing user name to anonymous user', (done) ->
req = request.post(getURL('/db/user'), (err, response, body) -> req = request.post(getURL('/db/user'), (err, response, body) ->
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
request.get getURL('/auth/whoami'), (request, response, body) -> request.get getURL('/auth/whoami'), (request, response, body) ->
res = JSON.parse(response.body) res = JSON.parse(response.body)
expect(res.anonymous).toBeFalsy() expect(res.anonymous).toBeFalsy()
createAnonUser() createAnonNameUser 'Jim', done
) )
form = req.form() form = req.form()
form.append('email', 'new@user.com') form.append('email', 'new@user.com')
@ -213,6 +201,55 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl
form.append('email', 'New@email.com') form.append('email', 'New@email.com')
form.append('name', 'Wilhelm') form.append('name', 'Wilhelm')
it 'should not allow two users with the same name slug', (done) ->
loginSam (sam) ->
samsName = sam.get 'name'
sam.set 'name', 'admin'
request.put {uri:getURL(urlUser + '/' + sam.id), json: sam.toObject()}, (err, response) ->
expect(err).toBeNull()
expect(response.statusCode).toBe 409
# Restore Sam
sam.set 'name', samsName
done()
it 'should silently rename an anonymous user if their name conflicts upon signup', (done) ->
request.post getURL('/auth/logout'), ->
request.get getURL('/auth/whoami'), ->
req = request.post getURL('/db/user'), (err, response) ->
expect(response.statusCode).toBe(200)
request.get getURL('/auth/whoami'), (err, response) ->
expect(err).toBeNull()
guy = JSON.parse(response.body)
expect(guy.anonymous).toBeTruthy()
expect(guy.name).toEqual 'admin'
guy.email = 'blub@blub' # Email means registration
req = request.post {url: getURL('/db/user'), json: guy}, (err, response) ->
expect(err).toBeNull()
finalGuy = response.body
expect(finalGuy.anonymous).toBeFalsy()
expect(finalGuy.name).not.toEqual guy.name
expect(finalGuy.name.length).toBe guy.name.length + 1
done()
form = req.form()
form.append('name', 'admin')
it 'should be able to unset a slug by setting an empty name', (done) ->
loginSam (sam) ->
samsName = sam.get 'name'
sam.set 'name', ''
request.put {uri:getURL(urlUser + '/' + sam.id), json: sam.toObject()}, (err, response) ->
expect(err).toBeNull()
expect(response.statusCode).toBe 200
newSam = response.body
# Restore Sam
sam.set 'name', samsName
request.put {uri:getURL(urlUser + '/' + sam.id), json: sam.toObject()}, (err, response) ->
expect(err).toBeNull()
done()
describe 'GET /db/user', -> describe 'GET /db/user', ->
it 'logs in as admin', (done) -> it 'logs in as admin', (done) ->
@ -267,3 +304,28 @@ describe 'GET /db/user', ->
expect(response.statusCode).toBe(422) expect(response.statusCode).toBe(422)
done() done()
) )
it 'can fetch myself by id completely', (done) ->
loginSam (sam) ->
request.get {url: getURL(urlUser + '/' + sam.id)}, (err, response) ->
expect(err).toBeNull()
expect(response.statusCode).toBe(200)
done()
it 'can fetch myself by slug completely', (done) ->
loginSam (sam) ->
request.get {url: getURL(urlUser + '/sam')}, (err, response) ->
expect(err).toBeNull()
expect(response.statusCode).toBe(200)
guy = JSON.parse response.body
expect(guy._id).toBe sam.get('_id').toHexString()
expect(guy.name).toBe sam.get 'name'
done()
# TODO Ruben should be able to fetch other users but probably with restricted data access
# Add to the test case above an extra data check
xit 'can fetch another user with restricted fields'