mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-02-17 08:50:58 -05:00
Added a new 'artisan' permission, which can edit most of the things admins can, but with less uncomfortable ultimate power.
This commit is contained in:
parent
7e889a2a80
commit
df4b98a9bf
24 changed files with 41 additions and 36 deletions
|
@ -34,7 +34,7 @@ class LiveEditingMarkup extends TreemaNode.nodeMap.ace
|
|||
valEl.append($('<div class="preview"></div>').hide())
|
||||
|
||||
addImageUpload: (valEl) ->
|
||||
return unless me.isAdmin()
|
||||
return unless me.isAdmin() or me.isArtisan()
|
||||
valEl.append(
|
||||
$('<div class="pick-image-button"></div>').append(
|
||||
$('<button>Pick Image</button>')
|
||||
|
|
|
@ -5,6 +5,7 @@ module.exports = class Achievement extends CocoModel
|
|||
@className: 'Achievement'
|
||||
@schema: require 'schemas/models/achievement'
|
||||
urlRoot: '/db/achievement'
|
||||
editableByArtisans: true
|
||||
|
||||
isRepeatable: ->
|
||||
@get('proportionalTo')?
|
||||
|
@ -12,7 +13,7 @@ module.exports = class Achievement extends CocoModel
|
|||
getExpFunction: ->
|
||||
func = @get('function', true)
|
||||
return utils.functionCreators[func.kind](func.parameters) if func.kind of utils.functionCreators
|
||||
|
||||
|
||||
save: ->
|
||||
@populateI18N()
|
||||
super(arguments...)
|
||||
|
|
|
@ -5,3 +5,4 @@ module.exports = class Article extends CocoModel
|
|||
@schema: require 'schemas/models/article'
|
||||
urlRoot: '/db/article'
|
||||
saveBackups: true
|
||||
editableByArtisans: true
|
||||
|
|
|
@ -233,6 +233,7 @@ class CocoModel extends Backbone.Model
|
|||
# actor is a User object
|
||||
actor ?= me
|
||||
return true if actor.isAdmin()
|
||||
return true if actor.isArtisan() and @editableByArtisans
|
||||
for permission in (@get('permissions', true) ? [])
|
||||
if permission.target is 'public' or actor.get('_id') is permission.target
|
||||
return true if permission.access in ['owner', 'read']
|
||||
|
@ -243,6 +244,7 @@ class CocoModel extends Backbone.Model
|
|||
# actor is a User object
|
||||
actor ?= me
|
||||
return true if actor.isAdmin()
|
||||
return true if actor.isArtisan() and @editableByArtisans
|
||||
for permission in (@get('permissions', true) ? [])
|
||||
if permission.target is 'public' or actor.get('_id') is permission.target
|
||||
return true if permission.access in ['owner', 'write']
|
||||
|
|
|
@ -10,6 +10,7 @@ module.exports = class Level extends CocoModel
|
|||
'dungeons-of-kithgard': '5411cb3769152f1707be029c'
|
||||
'defense-of-plainswood': '541b67f71ccc8eaae19f3c62'
|
||||
urlRoot: '/db/level'
|
||||
editableByArtisans: true
|
||||
|
||||
serialize: (supermodel, session, otherSession, cached=false) ->
|
||||
o = @denormalize supermodel, session, otherSession # hot spot to optimize
|
||||
|
|
|
@ -17,6 +17,7 @@ module.exports = class LevelComponent extends CocoModel
|
|||
@MissileID: '524cc2593ea855e0ab000142'
|
||||
@FindsPathsID: '52872b0ead92b98561000002'
|
||||
urlRoot: '/db/level.component'
|
||||
editableByArtisans: true
|
||||
|
||||
set: (key, val, options) ->
|
||||
if _.isObject key
|
||||
|
|
|
@ -5,6 +5,7 @@ module.exports = class LevelSystem extends CocoModel
|
|||
@className: 'LevelSystem'
|
||||
@schema: require 'schemas/models/level_system'
|
||||
urlRoot: '/db/level.system'
|
||||
editableByArtisans: true
|
||||
|
||||
set: (key, val, options) ->
|
||||
if _.isObject key
|
||||
|
|
|
@ -27,6 +27,7 @@ module.exports = class ThangType extends CocoModel
|
|||
'simple-boots': '53e237bf53457600003e3f05'
|
||||
urlRoot: '/db/thang.type'
|
||||
building: {}
|
||||
editableByArtisans: true
|
||||
|
||||
initialize: ->
|
||||
super()
|
||||
|
|
|
@ -12,6 +12,7 @@ module.exports = class User extends CocoModel
|
|||
notyErrors: false
|
||||
|
||||
isAdmin: -> 'admin' in @get('permissions', true)
|
||||
isArtisan: -> 'artisan' in @get('permissions', true)
|
||||
isInGodMode: -> 'godmode' in @get('permissions', true)
|
||||
isAnonymous: -> @get('anonymous', true)
|
||||
displayName: -> @get('name', true)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
extends /templates/base
|
||||
|
||||
block content
|
||||
if me.isAdmin()
|
||||
if !unauthorized
|
||||
ol.breadcrumb
|
||||
li
|
||||
a(href="/editor", data-i18n="editor.main_title") CodeCombat Editors
|
||||
|
@ -13,7 +13,7 @@ block content
|
|||
button.achievement-tool-button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-all-button Recalculate All
|
||||
button.achievement-tool-button(data-i18n="", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#recalculate-button Recalculate
|
||||
button.achievement-tool-button(data-i18n="common.delete", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#delete-button Delete
|
||||
button.achievement-tool-button(data-i18n="common.save", disabled=me.isAdmin() === true ? undefined : "true").btn.btn-primary#save-button Save
|
||||
button.achievement-tool-button(data-i18n="common.save", disabled=(me.isAdmin() === true || me.isArtisan() === true) ? undefined : "true").btn.btn-primary#save-button Save
|
||||
|
||||
h3(data-i18n="achievement.edit_achievement_title") Edit Achievement
|
||||
span
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
button.btn.btn-primary#new-achievement-button(disabled=me.isAdmin() === true ? undefined : "true" data-i18n="editor.new_achievement_title") Create a New Achievement
|
||||
button.btn.btn-primary#new-achievement-button(disabled=(me.isAdmin() === true || me.isArtisan() === true) ? undefined : "true" data-i18n="editor.new_achievement_title") Create a New Achievement
|
||||
|
||||
if !achievements.models.length
|
||||
.panel
|
||||
|
|
|
@ -11,7 +11,7 @@ nav.navbar.navbar-default(role='navigation')
|
|||
a(href="#system-patches" data-toggle="tab" data-i18n="resources.patches")#system-patches-tab Patches
|
||||
|
||||
ul.nav.navbar-nav.navbar-right
|
||||
if !me.isAdmin()
|
||||
if !me.isAdmin() && !me.isArtisan()
|
||||
li#patch-system-button
|
||||
a(data-i18n="[title]common.submit_patch")
|
||||
span.glyphicon-floppy-disk.glyphicon
|
||||
|
|
|
@ -52,7 +52,7 @@ block header
|
|||
span.glyphicon-chevron-down.glyphicon
|
||||
ul.dropdown-menu
|
||||
li.dropdown-header(data-i18n="common.actions") Actions
|
||||
li(class=!me.isAdmin() ? "disabled": "")
|
||||
li(class=!me.isAdmin() && !me.isArtisan() ? "disabled": "")
|
||||
a(data-i18n="common.fork")#fork-start-button Fork
|
||||
li(class=!authorized ? "disabled": "")
|
||||
a(data-toggle="coco-modal", data-target="modal/RevertModal", data-i18n="editor.revert", disabled=!authorized)#revert-button Revert
|
||||
|
|
|
@ -83,8 +83,8 @@ else
|
|||
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
|
||||
if me.get('anonymous') === false || me.get('iosIdentifierForVendor') || isIPadApp
|
||||
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
|
||||
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account")
|
||||
if me.isAdmin()
|
||||
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account")
|
||||
button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
|
||||
else if me.get('anonymous', true)
|
||||
button.btn.settings(data-toggle='coco-modal', data-target='core/AuthModal', data-i18n="[title]play.settings")
|
||||
|
|
|
@ -251,7 +251,7 @@ module.exports = class CocoView extends Backbone.View
|
|||
@_lastLoading = null
|
||||
|
||||
showReadOnly: ->
|
||||
return if me.isAdmin()
|
||||
return if me.isAdmin() or me.isArtisan()
|
||||
warning = $.i18n.t 'editor.read_only_warning2', defaultValue: 'Note: you can\'t save any edits here, because you\'re not logged in.'
|
||||
noty text: warning, layout: 'center', type: 'information', killer: true, timeout: 5000
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ module.exports = class AchievementEditView extends RootView
|
|||
getRenderData: (context={}) ->
|
||||
context = super(context)
|
||||
context.achievement = @achievement
|
||||
context.authorized = me.isAdmin()
|
||||
context.authorized = me.isAdmin() or me.isArtisan()
|
||||
context
|
||||
|
||||
afterRender: ->
|
||||
|
|
|
@ -15,6 +15,6 @@ module.exports = class AchievementSearchView extends SearchView
|
|||
context.currentNewSignup = 'editor.new_achievement_title_login'
|
||||
context.currentSearch = 'editor.achievement_search_title'
|
||||
context.newModelsAdminOnly = true
|
||||
context.unauthorized = true unless me.isAdmin()
|
||||
context.unauthorized = true unless me.isAdmin() or me.isArtisan()
|
||||
@$el.i18n()
|
||||
context
|
||||
|
|
|
@ -22,18 +22,18 @@ class AchievementHandler extends Handler
|
|||
'i18n'
|
||||
'i18nCoverage'
|
||||
]
|
||||
|
||||
|
||||
allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||
jsonSchema = require '../../app/schemas/models/achievement.coffee'
|
||||
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.method in ['GET', 'PUT'] or req.user?.isAdmin()
|
||||
req.method in ['GET', 'PUT'] or req.user?.isAdmin() or req.user?.isArtisan()
|
||||
|
||||
hasAccessToDocument: (req, document, method=null) ->
|
||||
method = (method or req.method).toLowerCase()
|
||||
return true if method is 'get'
|
||||
return true if req.user?.isAdmin()
|
||||
return true if req.user?.isAdmin() or req.user?.isArtisan()
|
||||
return true if method is 'put' and @isJustFillingTranslations(req, document)
|
||||
return
|
||||
|
||||
|
@ -49,7 +49,7 @@ class AchievementHandler extends Handler
|
|||
super req, res
|
||||
|
||||
delete: (req, res, slugOrID) ->
|
||||
return @sendForbiddenError res unless req.user?.isAdmin()
|
||||
return @sendForbiddenError res unless req.user?.isAdmin() or req.user?.isArtisan()
|
||||
@getDocumentForIdOrSlug slugOrID, (err, document) => # Check first
|
||||
return @sendDatabaseError(res, err) if err
|
||||
return @sendNotFoundError(res) unless document?
|
||||
|
|
|
@ -10,7 +10,7 @@ ArticleHandler = class ArticleHandler extends Handler
|
|||
req.method is 'GET' or req.user?.isAdmin()
|
||||
|
||||
hasAccessToDocument: (req, document, method=null) ->
|
||||
return true if req.method is 'GET' or method is 'get' or req.user?.isAdmin()
|
||||
return true if req.method is 'GET' or method is 'get' or req.user?.isAdmin() or req.user?.isArtisan()
|
||||
return false
|
||||
|
||||
module.exports = new ArticleHandler()
|
||||
|
|
|
@ -21,7 +21,7 @@ LevelSystemHandler = class LevelSystemHandler extends Handler
|
|||
props
|
||||
|
||||
hasAccess: (req) ->
|
||||
req.method is 'GET' or req.user?.isAdmin()
|
||||
req.method is 'GET' or req.user?.isAdmin() or req.user?.isArtisan()
|
||||
|
||||
|
||||
module.exports = new LevelSystemHandler()
|
||||
|
|
|
@ -43,7 +43,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
|
|||
hasAccessToDocument: (req, document, method=null) ->
|
||||
method = (method or req.method).toLowerCase()
|
||||
return true if method is 'get'
|
||||
return true if req.user?.isAdmin()
|
||||
return true if req.user?.isAdmin() or req.user?.isArtisan()
|
||||
return true if method is 'post' and @isJustFillingTranslations(req, document)
|
||||
return
|
||||
|
||||
|
|
|
@ -42,6 +42,10 @@ UserSchema.methods.isAdmin = ->
|
|||
p = @get('permissions')
|
||||
return p and 'admin' in p
|
||||
|
||||
UserSchema.methods.isArtisan = ->
|
||||
p = @get('permissions')
|
||||
return p and 'artisan' in p
|
||||
|
||||
UserSchema.methods.isAnonymous = ->
|
||||
@get 'anonymous'
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
config = require '../../../server_config'
|
||||
require '../common'
|
||||
|
||||
|
@ -19,7 +18,7 @@ describe '/db/user, editing stripe.couponID property', ->
|
|||
#- shared data between tests
|
||||
joeData = null
|
||||
firstSubscriptionID = null
|
||||
|
||||
|
||||
it 'does not work for non-admins', (done) ->
|
||||
loginJoe (joe) ->
|
||||
joeData = joe.toObject()
|
||||
|
@ -29,14 +28,14 @@ describe '/db/user, editing stripe.couponID property', ->
|
|||
expect(res.statusCode).toBe(200) # fails silently
|
||||
expect(res.body.stripe).toBeUndefined() # but still fails
|
||||
done()
|
||||
|
||||
|
||||
it 'does not work with invalid coupons', (done) ->
|
||||
loginAdmin (admin) ->
|
||||
joeData.stripe = { couponID: 'DNE' }
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
expect(res.statusCode).toBe(404)
|
||||
done()
|
||||
|
||||
|
||||
it 'sets the couponID on a user without an existing stripe object', (done) ->
|
||||
joeData.stripe = { couponID: '20pct' }
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
|
@ -44,7 +43,7 @@ describe '/db/user, editing stripe.couponID property', ->
|
|||
expect(res.statusCode).toBe(200)
|
||||
expect(body.stripe.couponID).toBe('20pct')
|
||||
done()
|
||||
|
||||
|
||||
it 'just updates the couponID when it changes and there is no existing subscription', (done) ->
|
||||
joeData.stripe.couponID = '500off'
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
|
@ -80,10 +79,10 @@ describe '/db/user, editing stripe.couponID property', ->
|
|||
expect(res.statusCode).toBe(200)
|
||||
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
||||
expect(customer.discount).toBeDefined()
|
||||
expect(customer.discount.coupon.id).toBe('500off')
|
||||
expect(customer.discount?.coupon.id).toBe('500off')
|
||||
done()
|
||||
|
||||
|
||||
|
||||
|
||||
it 'updates the discount on the customer when an admin changes the couponID', (done) ->
|
||||
loginAdmin (admin) ->
|
||||
joeData.stripe.couponID = '20pct'
|
||||
|
@ -93,7 +92,7 @@ describe '/db/user, editing stripe.couponID property', ->
|
|||
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
||||
expect(customer.discount.coupon.id).toBe('20pct')
|
||||
done()
|
||||
|
||||
|
||||
it 'removes discounts from the customer when an admin removes the couponID', (done) ->
|
||||
delete joeData.stripe.couponID
|
||||
request.put {uri: userURL, json: joeData }, (err, res, body) ->
|
||||
|
@ -111,4 +110,3 @@ describe '/db/user, editing stripe.couponID property', ->
|
|||
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
||||
expect(customer.discount.coupon.id).toBe('20pct')
|
||||
done()
|
||||
|
||||
|
|
|
@ -71,13 +71,13 @@ describe '/db/patch', ->
|
|||
it 'does not add duplicate watchers', (done) ->
|
||||
watchingURL = getURL("/db/article/#{articles[0]._id}/watch")
|
||||
request.put {uri: watchingURL, json: {on: true}}, (err, res, body) ->
|
||||
expect(body.watchers.length).toBe(4)
|
||||
expect(body.watchers.length).toBe(3)
|
||||
done()
|
||||
|
||||
it 'allows removing yourself', (done) ->
|
||||
watchingURL = getURL("/db/article/#{articles[0]._id}/watch")
|
||||
request.put {uri: watchingURL, json: {on: false}}, (err, res, body) ->
|
||||
expect(body.watchers.length).toBe(3)
|
||||
expect(body.watchers.length).toBe(2)
|
||||
done()
|
||||
|
||||
it 'allows the submitter to withdraw the pull request', (done) ->
|
||||
|
@ -157,9 +157,3 @@ describe '/db/patch', ->
|
|||
Patch.findOne({}).exec (err, article) ->
|
||||
expect(article.get('status')).toBe 'accepted'
|
||||
done()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue