diff --git a/app/lib/utils.coffee b/app/lib/utils.coffee index a63a1274d..430a9d8fb 100644 --- a/app/lib/utils.coffee +++ b/app/lib/utils.coffee @@ -76,6 +76,8 @@ module.exports.getByPath = (target, path) -> obj = obj[piece] 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) -> n = +n.toFixed(digits) diff --git a/app/models/User.coffee b/app/models/User.coffee index 1086e1373..cdaf1e86a 100644 --- a/app/models/User.coffee +++ b/app/models/User.coffee @@ -1,6 +1,7 @@ GRAVATAR_URL = 'https://www.gravatar.com/' cache = {} CocoModel = require './CocoModel' +util = require 'lib/utils' module.exports = class User extends CocoModel @className: 'User' @@ -45,6 +46,28 @@ module.exports = class User extends CocoModel cache[id] = 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: -> @migrateEmails() emails = _.clone(@get('emails')) or {} diff --git a/app/schemas/models/user.coffee b/app/schemas/models/user.coffee index edb2279bb..b34ec1dea 100644 --- a/app/schemas/models/user.coffee +++ b/app/schemas/models/user.coffee @@ -1,8 +1,12 @@ c = require './../schemas' emailSubscriptions = ['announcement', 'tester', 'level_creator', 'developer', 'article_editor', 'translator', 'support', 'notification'] -UserSchema = c.object {}, - name: c.shortString({title: 'Display Name', default: ''}) +UserSchema = c.object + title: 'User' + +c.extendNamedProperties UserSchema # let's have the name be the first property + +_.extend UserSchema.properties, email: c.shortString({title: 'Email', format: 'email'}) firstName: c.shortString({title: 'First Name'}) lastName: c.shortString({title: 'Last Name'}) diff --git a/app/styles/terrain_randomise.sass b/app/styles/terrain_randomise.sass new file mode 100644 index 000000000..e6cf29bec --- /dev/null +++ b/app/styles/terrain_randomise.sass @@ -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 \ No newline at end of file diff --git a/app/templates/editor/level/edit.jade b/app/templates/editor/level/edit.jade index d04b8f4bd..e446f7613 100644 --- a/app/templates/editor/level/edit.jade +++ b/app/templates/editor/level/edit.jade @@ -79,6 +79,8 @@ block header a(data-i18n="common.fork")#fork-level-start-button Fork li(class=anonymous ? "disabled": "") 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": "") a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n li.divider diff --git a/app/templates/modal/terrain_randomise.jade b/app/templates/modal/terrain_randomise.jade new file mode 100644 index 000000000..9ac509c68 --- /dev/null +++ b/app/templates/modal/terrain_randomise.jade @@ -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 diff --git a/app/views/editor/level/thangs_tab_view.coffee b/app/views/editor/level/thangs_tab_view.coffee index 8e905654a..e594b00be 100644 --- a/app/views/editor/level/thangs_tab_view.coffee +++ b/app/views/editor/level/thangs_tab_view.coffee @@ -43,6 +43,7 @@ module.exports = class ThangsTabView extends View 'sprite:mouse-up': 'onSpriteMouseUp' 'sprite:double-clicked': 'onSpriteDoubleClicked' 'surface:stage-mouse-up': 'onStageMouseUp' + 'randomise:terrain-generated': 'onRandomiseTerrain' events: 'click #extant-thangs-filter button': 'onFilterExtantThangs' @@ -57,6 +58,8 @@ module.exports = class ThangsTabView extends View 'delete, del, backspace': 'deleteSelectedExtantThang' 'left': -> @moveAddThangSelection -1 'right': -> @moveAddThangSelection 1 + 'ctrl+z': 'undoAction' + 'ctrl+shift+z': 'redoAction' constructor: (options) -> super options @@ -221,6 +224,12 @@ module.exports = class ThangsTabView extends View return unless e.thang @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 onExtantThangSelected: (e) -> @selectedExtantSprite?.setNameLabel? null unless @selectedExtantSprite is e.sprite @@ -450,6 +459,12 @@ module.exports = class ThangsTabView extends View $('#add-thangs-column').toggle() @onWindowResize e + undoAction: (e) -> + @thangsTreema.undo() + + redoAction: (e) -> + @thangsTreema.redo() + class ThangsNode extends TreemaNode.nodeMap.array valueClass: 'treema-array-replacement' getChildren: -> diff --git a/app/views/modal/terrain_randomise_modal.coffee b/app/views/modal/terrain_randomise_modal.coffee new file mode 100644 index 000000000..d737f929f --- /dev/null +++ b/app/views/modal/terrain_randomise_modal.coffee @@ -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 diff --git a/scripts/mongodb/migrations/2014-07-09-name-conflicts.js b/scripts/mongodb/migrations/2014-07-09-name-conflicts.js new file mode 100644 index 000000000..bf15c1a9e --- /dev/null +++ b/scripts/mongodb/migrations/2014-07-09-name-conflicts.js @@ -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}); + } +}); \ No newline at end of file diff --git a/server/plugins/plugins.coffee b/server/plugins/plugins.coffee index e36bb16de..2fdce3bef 100644 --- a/server/plugins/plugins.coffee +++ b/server/plugins/plugins.coffee @@ -41,9 +41,12 @@ module.exports.NamedPlugin = (schema) -> err.response = {message: ' is a reserved name', property: 'name'} err.code = 422 return next(err) - if newSlug isnt @get('slug') + if newSlug not in [@get('slug'), ''] and not @get 'anonymous' @set('slug', newSlug) @checkSlugConflicts(next) + else if newSlug is '' and @get 'slug' + @set 'slug', undefined + next() else next() ) diff --git a/server/routes/auth.coffee b/server/routes/auth.coffee index 5c3298f44..25c67e778 100644 --- a/server/routes/auth.coffee +++ b/server/routes/auth.coffee @@ -159,15 +159,11 @@ module.exports.setup = (app) -> module.exports.loginUser = loginUser = (req, res, user, send=true, next=null) -> user.save((err) -> - if err - return @sendDatabaseError(res, err) + return errors.serverError res, err if err? req.logIn(user, (err) -> - if err - return @sendDatabaseError(res, err) - - if send - return @sendSuccess(res, user) + return errors.serverError res, err if err? + return res.send user if send next() if next ) ) diff --git a/server/users/User.coffee b/server/users/User.coffee index 421335a21..13f4c11fb 100644 --- a/server/users/User.coffee +++ b/server/users/User.coffee @@ -4,6 +4,7 @@ crypto = require 'crypto' {salt, isProduction} = require '../../server_config' mail = require '../commons/mail' log = require 'winston' +plugins = require '../plugins/plugins' sendwithus = require '../sendwithus' @@ -29,6 +30,9 @@ UserSchema.methods.isAdmin = -> p = @get('permissions') return p and 'admin' in p +UserSchema.methods.isAnonymous = -> + @get 'anonymous' + UserSchema.methods.trackActivity = (activityName, increment) -> now = new Date() increment ?= parseInt increment or 1 @@ -108,6 +112,30 @@ UserSchema.statics.updateMailChimp = (doc, callback) -> 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) -> @set('emailLower', @get('email')?.toLowerCase()) @set('nameLower', @get('name')?.toLowerCase()) @@ -115,16 +143,10 @@ UserSchema.pre('save', (next) -> if @get('password') @set('passwordHash', User.hashPassword(pwd)) @set('password', undefined) - if @get('email') and @get('anonymous') - @set('anonymous', false) - @set('permissions', ['admin']) if not isProduction - 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 - next() + if @get('email') and @get('anonymous') # a user registers + @register next + else + next() ) UserSchema.post 'save', (doc) -> @@ -136,6 +158,8 @@ UserSchema.statics.hashPassword = (password) -> shasum.update(salt + password) shasum.digest('hex') +UserSchema.plugin plugins.NamedPlugin + module.exports = User = mongoose.model('User', UserSchema) AchievablePlugin = require '../plugins/achievements' diff --git a/server/users/user_handler.coffee b/server/users/user_handler.coffee index f8b608255..c145855b9 100644 --- a/server/users/user_handler.coffee +++ b/server/users/user_handler.coffee @@ -104,7 +104,8 @@ UserHandler = class UserHandler extends Handler return callback(null, req, user) unless req.body.name nameLower = req.body.name?.toLowerCase() 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) -> log.error "Database error setting user name: #{err}" if err return callback(res: 'Database error.', code: 500) if err @@ -116,7 +117,7 @@ UserHandler = class UserHandler extends Handler ] 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)) super(req, res, id) diff --git a/test/app/models/User.spec.coffee b/test/app/models/User.spec.coffee new file mode 100644 index 000000000..e69de29bb diff --git a/test/server/functional/user.spec.coffee b/test/server/functional/user.spec.coffee index 5395566a4..2bbb3a1ab 100644 --- a/test/server/functional/user.spec.coffee +++ b/test/server/functional/user.spec.coffee @@ -44,7 +44,7 @@ describe 'User.updateMailChimp', -> describe 'POST /db/user', -> - createAnonNameUser = (done)-> + createAnonNameUser = (name, done)-> request.post getURL('/auth/logout'), -> request.get getURL('/auth/whoami'), -> req = request.post(getURL('/db/user'), (err, response) -> @@ -52,11 +52,11 @@ describe 'POST /db/user', -> request.get getURL('/auth/whoami'), (request, response, body) -> res = JSON.parse(response.body) expect(res.anonymous).toBeTruthy() - expect(res.name).toEqual('Jim') + expect(res.name).toEqual(name) done() ) form = req.form() - form.append('name', 'Jim') + form.append('name', name) it 'preparing test : clears the db first', (done) -> clearModels [User], (err) -> @@ -105,30 +105,18 @@ describe 'POST /db/user', -> done() it 'should allow setting anonymous user name', (done) -> - createAnonNameUser(done) + createAnonNameUser('Jim', done) it 'should allow multiple anonymous users with same name', (done) -> - createAnonNameUser(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') + createAnonNameUser('Jim', done) + it 'should allow setting existing user name to anonymous user', (done) -> req = request.post(getURL('/db/user'), (err, response, body) -> expect(response.statusCode).toBe(200) request.get getURL('/auth/whoami'), (request, response, body) -> res = JSON.parse(response.body) expect(res.anonymous).toBeFalsy() - createAnonUser() + createAnonNameUser 'Jim', done ) form = req.form() form.append('email', 'new@user.com') @@ -213,6 +201,55 @@ ghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl form.append('email', 'New@email.com') 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', -> it 'logs in as admin', (done) -> @@ -267,3 +304,28 @@ describe 'GET /db/user', -> expect(response.statusCode).toBe(422) 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' + + +