mirror of
				https://github.com/discourse/discourse.git
				synced 2025-02-25 18:55:32 -06:00 
			
		
		
		
	Merge pull request #2271 from vikhyat/badge-system
Badge system updates
This commit is contained in:
		@@ -81,7 +81,7 @@
 | 
			
		||||
    <div class='display-row'>
 | 
			
		||||
      <div class='field'>{{i18n admin.badges.title}}</div>
 | 
			
		||||
      <div class='value'>
 | 
			
		||||
        TODO featured badges
 | 
			
		||||
        {{i18n badges.badge_count count=badge_count}}
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class='controls'>
 | 
			
		||||
        {{#link-to 'adminUser.badges' this class="btn"}}{{i18n admin.badges.edit_badges}}{{/link-to}}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
Discourse.UserBadgeComponent = Ember.Component.extend({
 | 
			
		||||
  tagName: 'span',
 | 
			
		||||
 | 
			
		||||
  badgeTypeClassName: function() {
 | 
			
		||||
    return "badge-type-" + this.get('badge.badge_type.name').toLowerCase();
 | 
			
		||||
  }.property('badge.badge_type.name')
 | 
			
		||||
});
 | 
			
		||||
@@ -8,6 +8,9 @@ Discourse.NotificationController = Discourse.ObjectController.extend({
 | 
			
		||||
  }.property(),
 | 
			
		||||
 | 
			
		||||
  link: function() {
 | 
			
		||||
    if (this.get('data.badge_id')) {
 | 
			
		||||
      return '<a href="/badges/' + this.get('data.badge_id') + '/' + this.get('data.badge_name').replace(/[^A-Za-z0-9_]+/g, '-').toLowerCase() + '">' + this.get('data.badge_name') + '</a>';
 | 
			
		||||
    }
 | 
			
		||||
    if (this.blank("data.topic_title")) {
 | 
			
		||||
      return "";
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,10 @@ Discourse.UserController = Discourse.ObjectController.extend({
 | 
			
		||||
    return this.get('viewingSelf') || Discourse.User.currentProp('admin');
 | 
			
		||||
  }.property('viewingSelf'),
 | 
			
		||||
 | 
			
		||||
  showBadges: function() {
 | 
			
		||||
    return Discourse.SiteSettings.enable_badges;
 | 
			
		||||
  }.property(),
 | 
			
		||||
 | 
			
		||||
  privateMessageView: function() {
 | 
			
		||||
    return (this.get('userActionType') === Discourse.UserAction.TYPES.messages_sent) ||
 | 
			
		||||
           (this.get('userActionType') === Discourse.UserAction.TYPES.messages_received);
 | 
			
		||||
 
 | 
			
		||||
@@ -164,8 +164,21 @@ Discourse.Badge.reopenClass({
 | 
			
		||||
    @returns {Promise} a promise that resolves to an array of `Discourse.Badge`
 | 
			
		||||
  **/
 | 
			
		||||
  findAll: function() {
 | 
			
		||||
    return Discourse.ajax('/admin/badges').then(function(badgesJson) {
 | 
			
		||||
    return Discourse.ajax('/badges.json').then(function(badgesJson) {
 | 
			
		||||
      return Discourse.Badge.createFromJson(badgesJson);
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
    Returns a `Discourse.Badge` that has the given ID.
 | 
			
		||||
 | 
			
		||||
    @method findById
 | 
			
		||||
    @param {Number} id ID of the badge
 | 
			
		||||
    @returns {Promise} a promise that resolves to a `Discourse.Badge`
 | 
			
		||||
  **/
 | 
			
		||||
  findById: function(id) {
 | 
			
		||||
    return Discourse.ajax("/badges/" + id).then(function(badgeJson) {
 | 
			
		||||
      return Discourse.Badge.createFromJson(badgeJson);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,9 @@ Discourse.UserBadge.reopenClass({
 | 
			
		||||
    userBadges = userBadges.map(function(userBadgeJson) {
 | 
			
		||||
      var userBadge = Discourse.UserBadge.create(userBadgeJson);
 | 
			
		||||
      userBadge.set('badge', badges[userBadge.get('badge_id')]);
 | 
			
		||||
      if (userBadge.get('user_id')) {
 | 
			
		||||
        userBadge.set('user', users[userBadge.get('user_id')]);
 | 
			
		||||
      }
 | 
			
		||||
      if (userBadge.get('granted_by_id')) {
 | 
			
		||||
        userBadge.set('granted_by', users[userBadge.get('granted_by_id')]);
 | 
			
		||||
      }
 | 
			
		||||
@@ -71,6 +74,7 @@ Discourse.UserBadge.reopenClass({
 | 
			
		||||
    Find all badges for a given username.
 | 
			
		||||
 | 
			
		||||
    @method findByUsername
 | 
			
		||||
    @param {String} username
 | 
			
		||||
    @returns {Promise} a promise that resolves to an array of `Discourse.UserBadge`.
 | 
			
		||||
  **/
 | 
			
		||||
  findByUsername: function(username) {
 | 
			
		||||
@@ -79,6 +83,19 @@ Discourse.UserBadge.reopenClass({
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
    Find all badge grants for a given badge ID.
 | 
			
		||||
 | 
			
		||||
    @method findById
 | 
			
		||||
    @param {String} badgeId
 | 
			
		||||
    @returns {Promise} a promise that resolves to an array of `Discourse.UserBadge`.
 | 
			
		||||
  **/
 | 
			
		||||
  findByBadgeId: function(badgeId) {
 | 
			
		||||
    return Discourse.ajax("/user_badges.json?badge_id=" + badgeId).then(function(json) {
 | 
			
		||||
      return Discourse.UserBadge.createFromJson(json);
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
    Grant the badge having id `badgeId` to the user identified by `username`.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,8 @@ Discourse.Route.buildRoutes(function() {
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.route('badges');
 | 
			
		||||
 | 
			
		||||
    this.resource('userPrivateMessages', { path: '/private-messages' }, function() {
 | 
			
		||||
      this.route('mine');
 | 
			
		||||
      this.route('unread');
 | 
			
		||||
@@ -94,4 +96,8 @@ Discourse.Route.buildRoutes(function() {
 | 
			
		||||
 | 
			
		||||
  this.route('signup', {path: '/signup'});
 | 
			
		||||
  this.route('login', {path: '/login'});
 | 
			
		||||
 | 
			
		||||
  this.resource('badges', function() {
 | 
			
		||||
    this.route('show', {path: '/:id/:slug'});
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
/**
 | 
			
		||||
  Shows a list of all badges.
 | 
			
		||||
 | 
			
		||||
  @class BadgesIndexRoute
 | 
			
		||||
  @extends Discourse.Route
 | 
			
		||||
  @namespace Discourse
 | 
			
		||||
  @module Discourse
 | 
			
		||||
**/
 | 
			
		||||
Discourse.BadgesIndexRoute = Discourse.Route.extend({
 | 
			
		||||
  model: function() {
 | 
			
		||||
    return Discourse.Badge.findAll();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										24
									
								
								app/assets/javascripts/discourse/routes/badges_show_route.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/assets/javascripts/discourse/routes/badges_show_route.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
/**
 | 
			
		||||
  Shows a particular badge.
 | 
			
		||||
 | 
			
		||||
  @class BadgesShowRoute
 | 
			
		||||
  @extends Discourse.Route
 | 
			
		||||
  @namespace Discourse
 | 
			
		||||
  @module Discourse
 | 
			
		||||
**/
 | 
			
		||||
Discourse.BadgesShowRoute = Ember.Route.extend({
 | 
			
		||||
  serialize: function(model) {
 | 
			
		||||
    return {id: model.get('id'), slug: model.get('name').replace(/[^A-Za-z0-9_]+/g, '-').toLowerCase()};
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  model: function(params) {
 | 
			
		||||
    return Discourse.Badge.findById(params.id);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  setupController: function(controller, model) {
 | 
			
		||||
    Discourse.UserBadge.findByBadgeId(model.get('id')).then(function(userBadges) {
 | 
			
		||||
      controller.set('userBadges', userBadges);
 | 
			
		||||
    });
 | 
			
		||||
    controller.set('model', model);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										25
									
								
								app/assets/javascripts/discourse/routes/user_badges_route.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/assets/javascripts/discourse/routes/user_badges_route.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
/**
 | 
			
		||||
  This route shows a user's badges.
 | 
			
		||||
 | 
			
		||||
  @class UserBadgesRoute
 | 
			
		||||
  @extends Discourse.Route
 | 
			
		||||
  @namespace Discourse
 | 
			
		||||
  @module Discourse
 | 
			
		||||
**/
 | 
			
		||||
Discourse.UserBadgesRoute = Discourse.Route.extend({
 | 
			
		||||
  model: function() {
 | 
			
		||||
    return Discourse.UserBadge.findByUsername(this.modelFor('user').get('username_lower'));
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  setupController: function(controller, model) {
 | 
			
		||||
    this.controllerFor('user').set('indexStream', false);
 | 
			
		||||
    if (this.controllerFor('user_activity').get('content')) {
 | 
			
		||||
      this.controllerFor('user_activity').set('userActionType', -1);
 | 
			
		||||
    }
 | 
			
		||||
    controller.set('model', model);
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  renderTemplate: function() {
 | 
			
		||||
    this.render('user/badges', {into: 'user', outlet: 'userOutlet'});
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
<div class='container'>
 | 
			
		||||
  <h1>{{i18n badges.title}}</h1>
 | 
			
		||||
 | 
			
		||||
  <table class='badges-listing'>
 | 
			
		||||
    {{#each}}
 | 
			
		||||
      <tr>
 | 
			
		||||
        <td class='badge'>{{user-badge badge=this}}</td>
 | 
			
		||||
        <td class='description'>{{description}}</td>
 | 
			
		||||
        <td class='grant-count'>{{i18n badges.awarded count=grant_count}}</td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    {{/each}}
 | 
			
		||||
  </table>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
<div class='container'>
 | 
			
		||||
  <h1>
 | 
			
		||||
    {{#link-to 'badges.index'}}{{i18n badges.title}}{{/link-to}}
 | 
			
		||||
    <i class='fa fa-angle-right'></i>
 | 
			
		||||
    {{name}}
 | 
			
		||||
  </h1>
 | 
			
		||||
 | 
			
		||||
  <table class='badges-listing'>
 | 
			
		||||
    <tr>
 | 
			
		||||
      <td class='badge'>{{user-badge badge=this}}</td>
 | 
			
		||||
      <td class='description'>{{description}}</td>
 | 
			
		||||
      <td class='grant-count'>{{i18n badges.awarded count=grant_count}}</td>
 | 
			
		||||
    </tr>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
  {{#if userBadges}}
 | 
			
		||||
    <h2>{{i18n users}}</h2>
 | 
			
		||||
    <br>
 | 
			
		||||
    {{#each userBadges}}
 | 
			
		||||
      {{#link-to 'userActivity' user}}
 | 
			
		||||
        {{avatar user imageSize="large"}}
 | 
			
		||||
      {{/link-to}}
 | 
			
		||||
    {{/each}}
 | 
			
		||||
  {{else}}
 | 
			
		||||
    <div class='spinner'>{{i18n loading}}</div>
 | 
			
		||||
  {{/if}}
 | 
			
		||||
</div>
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
{{#link-to 'badges.show' badge}}
 | 
			
		||||
  <span {{bind-attr class=":user-badge badgeTypeClassName" data-badge-name="badge.name" title="badge.description"}}>
 | 
			
		||||
    <i class='fa fa-certificate'></i>
 | 
			
		||||
    {{badge.name}}
 | 
			
		||||
  </span>
 | 
			
		||||
{{/link-to}}
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
    {{#if showBadges}}
 | 
			
		||||
      <div class="badge-section">
 | 
			
		||||
        {{#each user.featured_user_badges}}
 | 
			
		||||
        <span class="user-badge badge-type-{{unbound badge.badge_type_id}}"><i class='fa fa-certificate'></i> {{badge.name}}</span>
 | 
			
		||||
          {{user-badge badge=badge}}
 | 
			
		||||
        {{/each}}
 | 
			
		||||
        {{#if showMoreBadges}}
 | 
			
		||||
          <span class="btn more-user-badges">{{i18n badges.more_badges count=moreBadgesCount}}</span>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
<section class='user-content user-badges-list'>
 | 
			
		||||
  {{#each}}
 | 
			
		||||
    {{user-badge badge=badge}}
 | 
			
		||||
  {{/each}}
 | 
			
		||||
</section>
 | 
			
		||||
@@ -13,6 +13,16 @@
 | 
			
		||||
        {{#each stat in statsExcludingPms}}
 | 
			
		||||
          {{discourse-activity-filter content=stat user=model userActionType=userActionType indexStream=indexStream}}
 | 
			
		||||
        {{/each}}
 | 
			
		||||
        {{#if showBadges}}
 | 
			
		||||
          {{#link-to 'user.badges' tagName="li"}}
 | 
			
		||||
            {{#link-to 'user.badges'}}
 | 
			
		||||
              <i class='glyph fa fa-certificate'></i>
 | 
			
		||||
              {{i18n badges.title}}
 | 
			
		||||
              <span class='count'>({{badge_count}})</span>
 | 
			
		||||
              <span class='fa fa-chevron-right'></span>
 | 
			
		||||
            {{/link-to}}
 | 
			
		||||
          {{/link-to}}
 | 
			
		||||
        {{/if}}
 | 
			
		||||
      </ul>
 | 
			
		||||
 | 
			
		||||
      {{#if canSeePrivateMessages}}
 | 
			
		||||
 
 | 
			
		||||
@@ -102,39 +102,12 @@
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
        margin-bottom: -8px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .more-user-badges {
 | 
			
		||||
      @extend .user-badge;
 | 
			
		||||
      padding: 4px 8px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-badge, .more-user-badges {
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  line-height: 16px;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  .fa {
 | 
			
		||||
    padding-right: 5px;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-badge {
 | 
			
		||||
  padding: 3px 8px;
 | 
			
		||||
  border: 1px solid $secondary-border-color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.more-user-badges {
 | 
			
		||||
  padding: 4px 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.badge-type-1 .fa-certificate {
 | 
			
		||||
  color: #A67D3D;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.badge-type-2 .fa-certificate {
 | 
			
		||||
  color: silver;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.badge-type-1 .fa-certificate {
 | 
			
		||||
  color: gold;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										75
									
								
								app/assets/stylesheets/desktop/user-badges.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								app/assets/stylesheets/desktop/user-badges.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
/* Default badge styles. */
 | 
			
		||||
.user-badge {
 | 
			
		||||
  padding: 3px 8px;
 | 
			
		||||
  color: $primary_text_color;
 | 
			
		||||
  border: 1px solid $secondary-border-color;
 | 
			
		||||
  font-size: $base-font-size * 0.86;
 | 
			
		||||
  line-height: 16px;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  background-color: $primary_background_color;
 | 
			
		||||
 | 
			
		||||
  .fa {
 | 
			
		||||
    padding-right: 3px;
 | 
			
		||||
    font-size: 1.4em;
 | 
			
		||||
    vertical-align: bottom;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.badge-type-gold .fa {
 | 
			
		||||
    color: #ffd700;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.badge-type-silver .fa {
 | 
			
		||||
    color: #c0c0c0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &.badge-type-bronze .fa {
 | 
			
		||||
    color: #cd7f32;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* User badge listing. */
 | 
			
		||||
.user-badges-list {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
 | 
			
		||||
  .user-badge {
 | 
			
		||||
    max-width: 80px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    vertical-align: top;
 | 
			
		||||
    margin: 10px;
 | 
			
		||||
    border: none;
 | 
			
		||||
 | 
			
		||||
    .fa {
 | 
			
		||||
      display: block;
 | 
			
		||||
      font-size: 50px;
 | 
			
		||||
      margin-bottom: 5px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Badge listing in /badges. */
 | 
			
		||||
table.badges-listing {
 | 
			
		||||
  margin: 20px 0;
 | 
			
		||||
  border-bottom: 1px solid $primary-border-color;
 | 
			
		||||
 | 
			
		||||
  .user-badge {
 | 
			
		||||
    font-size: $base-font-size;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  td {
 | 
			
		||||
    padding: 10px 20px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  td.grant-count {
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    color: $secondary_text_color;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  td.badge, td.grant-count {
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  tr {
 | 
			
		||||
    border-top: 1px solid $primary-border-color;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,4 @@
 | 
			
		||||
class Admin::BadgesController < Admin::AdminController
 | 
			
		||||
  def index
 | 
			
		||||
    badges = Badge.all.to_a
 | 
			
		||||
    render_serialized(badges, BadgeSerializer, root: "badges")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def badge_types
 | 
			
		||||
    badge_types = BadgeType.all.to_a
 | 
			
		||||
    render_serialized(badge_types, BadgeTypeSerializer, root: "badge_types")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								app/controllers/badges_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/controllers/badges_controller.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
class BadgesController < ApplicationController
 | 
			
		||||
  def index
 | 
			
		||||
    badges = Badge.all.to_a
 | 
			
		||||
    render_serialized(badges, BadgeSerializer, root: "badges")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def show
 | 
			
		||||
    params.require(:id)
 | 
			
		||||
    badge = Badge.find(params[:id])
 | 
			
		||||
    render_serialized(badge, BadgeSerializer, root: "badge")
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -1,8 +1,14 @@
 | 
			
		||||
class UserBadgesController < ApplicationController
 | 
			
		||||
  def index
 | 
			
		||||
    params.require(:username)
 | 
			
		||||
    user = fetch_user_from_params
 | 
			
		||||
    render_serialized(user.user_badges, UserBadgeSerializer, root: "user_badges")
 | 
			
		||||
    params.permit(:username)
 | 
			
		||||
    if params[:username]
 | 
			
		||||
      user = fetch_user_from_params
 | 
			
		||||
      user_badges = user.user_badges
 | 
			
		||||
    else
 | 
			
		||||
      badge = fetch_badge_from_params
 | 
			
		||||
      user_badges = badge.user_badges.order('granted_at DESC').limit(20).to_a
 | 
			
		||||
    end
 | 
			
		||||
    render_serialized(user_badges, UserBadgeSerializer, root: "user_badges")
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def create
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ class BadgeType < ActiveRecord::Base
 | 
			
		||||
  has_many :badges
 | 
			
		||||
 | 
			
		||||
  validates :name, presence: true, uniqueness: true
 | 
			
		||||
  validates :color_hexcode, presence: true
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
# == Schema Information
 | 
			
		||||
@@ -11,7 +10,6 @@ end
 | 
			
		||||
#
 | 
			
		||||
#  id            :integer          not null, primary key
 | 
			
		||||
#  name          :string(255)      not null
 | 
			
		||||
#  color_hexcode :string(255)      not null
 | 
			
		||||
#  created_at    :datetime
 | 
			
		||||
#  updated_at    :datetime
 | 
			
		||||
#
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ class Notification < ActiveRecord::Base
 | 
			
		||||
    @types ||= Enum.new(
 | 
			
		||||
      :mentioned, :replied, :quoted, :edited, :liked, :private_message,
 | 
			
		||||
      :invited_to_private_message, :invitee_accepted, :posted, :moved_post,
 | 
			
		||||
      :linked
 | 
			
		||||
      :linked, :granted_badge
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -492,6 +492,14 @@ class User < ActiveRecord::Base
 | 
			
		||||
    Summarize.new(bio_cooked).summary
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def badge_count
 | 
			
		||||
    user_badges.count
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def featured_user_badges
 | 
			
		||||
    user_badges.joins(:badge).order('badges.badge_type_id ASC, badges.grant_count ASC').includes(:granted_by, badge: :badge_type).limit(3)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def self.count_by_signup_date(sinceDaysAgo=30)
 | 
			
		||||
    where('created_at > ?', sinceDaysAgo.days.ago).group('date(created_at)').order('date(created_at)').count
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,8 @@ class AdminDetailedUserSerializer < AdminUserSerializer
 | 
			
		||||
             :can_delete_all_posts,
 | 
			
		||||
             :can_be_deleted,
 | 
			
		||||
             :suspend_reason,
 | 
			
		||||
             :primary_group_id
 | 
			
		||||
             :primary_group_id,
 | 
			
		||||
             :badge_count
 | 
			
		||||
 | 
			
		||||
  has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
 | 
			
		||||
  has_one :api_key, serializer: ApiKeySerializer, embed: :objects
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
class BadgeSerializer < ApplicationSerializer
 | 
			
		||||
  attributes :id, :name, :description
 | 
			
		||||
  attributes :id, :name, :description, :grant_count
 | 
			
		||||
 | 
			
		||||
  has_one :badge_type
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
class BadgeTypeSerializer < ApplicationSerializer
 | 
			
		||||
  attributes :id, :name, :color_hexcode
 | 
			
		||||
  attributes :id, :name
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
class UserBadgeSerializer < ApplicationSerializer
 | 
			
		||||
  attributes :id, :granted_at
 | 
			
		||||
 | 
			
		||||
  has_one :user
 | 
			
		||||
  has_one :badge
 | 
			
		||||
  has_one :granted_by, serializer: BasicUserSerializer, root: :users
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -131,12 +131,4 @@ class UserSerializer < BasicUserSerializer
 | 
			
		||||
    CategoryUser.lookup(object, :watching).pluck(:category_id)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def badge_count
 | 
			
		||||
    object.user_badges.count
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def featured_user_badges
 | 
			
		||||
    # The three rarest badges this user has received should be featured.
 | 
			
		||||
    object.user_badges.joins(:badge).order('badges.grant_count ASC').includes(:granted_by, badge: :badge_type).limit(3)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,10 @@ class BadgeGranter
 | 
			
		||||
        if @granted_by != Discourse.system_user
 | 
			
		||||
          StaffActionLogger.new(@granted_by).log_badge_grant(user_badge)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        @user.notifications.create(notification_type: Notification.types[:granted_badge],
 | 
			
		||||
                                   data: { badge_id: @badge.id,
 | 
			
		||||
                                           badge_name: @badge.name }.to_json)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
@@ -32,10 +36,14 @@ class BadgeGranter
 | 
			
		||||
  def self.revoke(user_badge, options={})
 | 
			
		||||
    UserBadge.transaction do
 | 
			
		||||
      user_badge.destroy!
 | 
			
		||||
      Badge.decrement_counter 'grant_count', user_badge.badge.id
 | 
			
		||||
      Badge.decrement_counter 'grant_count', user_badge.badge_id
 | 
			
		||||
      if options[:revoked_by]
 | 
			
		||||
        StaffActionLogger.new(options[:revoked_by]).log_badge_revoke(user_badge)
 | 
			
		||||
      end
 | 
			
		||||
      # Revoke badge -- This is inefficient, but not very easy to optimize unless
 | 
			
		||||
      # the data hash is converted into a hstore.
 | 
			
		||||
      notification = user_badge.user.notifications.where(notification_type: Notification.types[:granted_badge]).where("data LIKE ?", "%" + user_badge.badge_id.to_s + "%").select {|n| n.data_hash["badge_id"] == user_badge.badge_id }.first
 | 
			
		||||
      notification && notification.destroy
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -598,6 +598,7 @@ en:
 | 
			
		||||
      moved_post: "<i title='moved post' class='fa fa-arrow-right'></i> {{username}} moved {{link}}"
 | 
			
		||||
      total_flagged: "total flagged posts"
 | 
			
		||||
      linked: "<i title='linked post' class='fa fa-arrow-left'></i> {{username}} {{link}}"
 | 
			
		||||
      granted_badge: "<i title='badge granted' class='fa fa-certificate'></i> {{link}}"
 | 
			
		||||
 | 
			
		||||
    upload_selector:
 | 
			
		||||
      title: "Add an image"
 | 
			
		||||
@@ -1768,12 +1769,16 @@ en:
 | 
			
		||||
        mark_watching: '<b>m</b> then <b>w</b> Mark topic as watching'
 | 
			
		||||
 | 
			
		||||
    badges:
 | 
			
		||||
      title: Badges
 | 
			
		||||
      badge_count:
 | 
			
		||||
        one:   "1 Badge"
 | 
			
		||||
        other: "%{count} Badges"
 | 
			
		||||
      more_badges:
 | 
			
		||||
        one:   "+1 More"
 | 
			
		||||
        other: "+%{count} More"
 | 
			
		||||
      awarded:
 | 
			
		||||
        one:   "1 awarded"
 | 
			
		||||
        other: "%{count} awarded"
 | 
			
		||||
      example_badge:
 | 
			
		||||
        name: Example Badge
 | 
			
		||||
        description: This is a generic example badge.
 | 
			
		||||
 
 | 
			
		||||
@@ -888,6 +888,7 @@ en:
 | 
			
		||||
    invited_to_private_message: "%{display_username} invited you to a private message: %{link}"
 | 
			
		||||
    invitee_accepted: "%{display_username} accepted your invitation"
 | 
			
		||||
    linked: "%{display_username} linked you in %{link}"
 | 
			
		||||
    granted_badge: "You were granted the badge %{link}"
 | 
			
		||||
 | 
			
		||||
  search:
 | 
			
		||||
    within_post: "#%{post_number} by %{username}: %{excerpt}"
 | 
			
		||||
@@ -1474,9 +1475,3 @@ en:
 | 
			
		||||
    message_to_blank: "message.to is blank"
 | 
			
		||||
    text_part_body_blank: "text_part.body is blank"
 | 
			
		||||
    body_blank: "body is blank"
 | 
			
		||||
 | 
			
		||||
  badges:
 | 
			
		||||
    types:
 | 
			
		||||
      gold: Gold
 | 
			
		||||
      silver: Silver
 | 
			
		||||
      bronze: Bronze
 | 
			
		||||
 
 | 
			
		||||
@@ -195,6 +195,7 @@ Discourse::Application.routes.draw do
 | 
			
		||||
  post "users/:username/send_activation_email" => "users#send_activation_email", constraints: {username: USERNAME_ROUTE_FORMAT}
 | 
			
		||||
  get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
 | 
			
		||||
  get "users/:username/activity/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
 | 
			
		||||
  get "users/:username/badges" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
 | 
			
		||||
  delete "users/:username" => "users#destroy", constraints: {username: USERNAME_ROUTE_FORMAT}
 | 
			
		||||
 | 
			
		||||
  get "uploads/:site/:id/:sha.:extension" => "uploads#show", constraints: {site: /\w+/, id: /\d+/, sha: /[a-z0-9]{15,16}/i, extension: /\w{2,}/}
 | 
			
		||||
@@ -242,6 +243,8 @@ Discourse::Application.routes.draw do
 | 
			
		||||
  end
 | 
			
		||||
  resources :user_actions
 | 
			
		||||
 | 
			
		||||
  resources :badges, only: [:index]
 | 
			
		||||
  get "/badges/:id(/:slug)" => "badges#show"
 | 
			
		||||
  resources :user_badges, only: [:index, :create, :destroy]
 | 
			
		||||
 | 
			
		||||
  # We've renamed popular to latest. If people access it we want a permanent redirect.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,14 @@
 | 
			
		||||
BadgeType.seed do |b|
 | 
			
		||||
  b.id = 1
 | 
			
		||||
  b.name = I18n.t('badges.types.gold')
 | 
			
		||||
  b.color_hexcode = "ffd700"
 | 
			
		||||
  b.name = "Gold"
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
BadgeType.seed do |b|
 | 
			
		||||
  b.id = 2
 | 
			
		||||
  b.name = I18n.t('badges.types.silver')
 | 
			
		||||
  b.color_hexcode = "c0c0c0"
 | 
			
		||||
  b.name = "Silver"
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
BadgeType.seed do |b|
 | 
			
		||||
  b.id = 3
 | 
			
		||||
  b.name = I18n.t('badges.types.bronze')
 | 
			
		||||
  b.color_hexcode = "cd7f32"
 | 
			
		||||
  b.name = "Bronze"
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
class RemoveColorHexcodeFromBadgeTypes < ActiveRecord::Migration
 | 
			
		||||
  def change
 | 
			
		||||
    remove_column :badge_types, :color_hexcode, :string
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -9,18 +9,6 @@ describe Admin::BadgesController do
 | 
			
		||||
    let!(:user) { log_in(:admin) }
 | 
			
		||||
    let!(:badge) { Fabricate(:badge) }
 | 
			
		||||
 | 
			
		||||
    context '.index' do
 | 
			
		||||
      it 'returns success' do
 | 
			
		||||
        xhr :get, :index
 | 
			
		||||
        response.should be_success
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it 'returns JSON' do
 | 
			
		||||
        xhr :get, :index
 | 
			
		||||
        ::JSON.parse(response.body)["badges"].should be_present
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context '.badge_types' do
 | 
			
		||||
      it 'returns success' do
 | 
			
		||||
        xhr :get, :badge_types
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								spec/controllers/badges_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								spec/controllers/badges_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe BadgesController do
 | 
			
		||||
  let!(:badge) { Fabricate(:badge) }
 | 
			
		||||
 | 
			
		||||
  context 'index' do
 | 
			
		||||
    it 'should return a list of all badges' do
 | 
			
		||||
      xhr :get, :index
 | 
			
		||||
 | 
			
		||||
      response.status.should == 200
 | 
			
		||||
      parsed = JSON.parse(response.body)
 | 
			
		||||
      parsed["badges"].length.should == 1
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'show' do
 | 
			
		||||
    it "should return a badge" do
 | 
			
		||||
      xhr :get, :show, id: badge.id
 | 
			
		||||
      response.status.should == 200
 | 
			
		||||
      parsed = JSON.parse(response.body)
 | 
			
		||||
      parsed["badge"].should be_present
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@@ -5,21 +5,27 @@ describe UserBadgesController do
 | 
			
		||||
  let(:badge) { Fabricate(:badge) }
 | 
			
		||||
 | 
			
		||||
  context 'index' do
 | 
			
		||||
    before do
 | 
			
		||||
      @user_badge = BadgeGranter.grant(badge, user)
 | 
			
		||||
    end
 | 
			
		||||
    let!(:user_badge) { UserBadge.create(badge: badge, user: user, granted_by: Discourse.system_user, granted_at: Time.now) }
 | 
			
		||||
 | 
			
		||||
    it 'requires username to be specified' do
 | 
			
		||||
    it 'requires username or badge_id to be specified' do
 | 
			
		||||
      expect { xhr :get, :index }.to raise_error
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'returns the user\'s badges' do
 | 
			
		||||
    it 'returns user_badges for a user' do
 | 
			
		||||
      xhr :get, :index, username: user.username
 | 
			
		||||
 | 
			
		||||
      response.status.should == 200
 | 
			
		||||
      parsed = JSON.parse(response.body)
 | 
			
		||||
      parsed["user_badges"].length.should == 1
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'returns user_badges for a badge' do
 | 
			
		||||
      xhr :get, :index, badge_id: badge.id
 | 
			
		||||
 | 
			
		||||
      response.status.should == 200
 | 
			
		||||
      parsed = JSON.parse(response.body)
 | 
			
		||||
      parsed["user_badges"].length.should == 1
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'create' do
 | 
			
		||||
@@ -62,21 +68,19 @@ describe UserBadgesController do
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  context 'destroy' do
 | 
			
		||||
    before do
 | 
			
		||||
      @user_badge = BadgeGranter.grant(badge, user)
 | 
			
		||||
    end
 | 
			
		||||
    let!(:user_badge) { UserBadge.create(badge: badge, user: user, granted_by: Discourse.system_user, granted_at: Time.now) }
 | 
			
		||||
 | 
			
		||||
    it 'checks that the user is authorized to revoke a badge' do
 | 
			
		||||
      xhr :delete, :destroy, id: @user_badge.id
 | 
			
		||||
      xhr :delete, :destroy, id: user_badge.id
 | 
			
		||||
      response.status.should == 403
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'revokes the badge' do
 | 
			
		||||
      log_in :admin
 | 
			
		||||
      StaffActionLogger.any_instance.expects(:log_badge_revoke).once
 | 
			
		||||
      xhr :delete, :destroy, id: @user_badge.id
 | 
			
		||||
      xhr :delete, :destroy, id: user_badge.id
 | 
			
		||||
      response.status.should == 200
 | 
			
		||||
      UserBadge.where(id: @user_badge.id).first.should be_nil
 | 
			
		||||
      UserBadge.where(id: user_badge.id).first.should be_nil
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
Fabricator(:badge_type) do
 | 
			
		||||
  name { sequence(:name) {|i| "Silver #{i}" } }
 | 
			
		||||
  color_hexcode "c0c0c0"
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
Fabricator(:badge) do
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,6 @@ require_dependency 'badge'
 | 
			
		||||
 | 
			
		||||
describe Badge do
 | 
			
		||||
 | 
			
		||||
  it { should belong_to :badge_type }
 | 
			
		||||
  it { should have_many(:user_badges).dependent(:destroy) }
 | 
			
		||||
 | 
			
		||||
  context 'validations' do
 | 
			
		||||
    before(:each) { Fabricate(:badge) }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,7 @@ require_dependency 'badge_type'
 | 
			
		||||
 | 
			
		||||
describe BadgeType do
 | 
			
		||||
 | 
			
		||||
  it { should have_many :badges }
 | 
			
		||||
 | 
			
		||||
  it { should validate_presence_of :name }
 | 
			
		||||
  it { should validate_uniqueness_of :name }
 | 
			
		||||
  it { should validate_presence_of :color_hexcode }
 | 
			
		||||
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,6 @@ require_dependency 'user_badge'
 | 
			
		||||
 | 
			
		||||
describe UserBadge do
 | 
			
		||||
 | 
			
		||||
  it { should belong_to :badge }
 | 
			
		||||
  it { should belong_to :user }
 | 
			
		||||
  it { should belong_to :granted_by }
 | 
			
		||||
 | 
			
		||||
  context 'validations' do
 | 
			
		||||
    before(:each) { BadgeGranter.grant(Fabricate(:badge), Fabricate(:user)) }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -40,9 +40,10 @@ describe BadgeGranter do
 | 
			
		||||
      user_badge.should_not be_present
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'increments grant_count on the badge' do
 | 
			
		||||
    it 'increments grant_count on the badge and creates a notification' do
 | 
			
		||||
      BadgeGranter.grant(badge, user)
 | 
			
		||||
      badge.reload.grant_count.should eq(1)
 | 
			
		||||
      user.notifications.where(notification_type: Notification.types[:granted_badge]).first.data_hash["badge_id"].should == badge.id
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
@@ -52,12 +53,13 @@ describe BadgeGranter do
 | 
			
		||||
    let(:admin) { Fabricate(:admin) }
 | 
			
		||||
    let!(:user_badge) { BadgeGranter.grant(badge, user) }
 | 
			
		||||
 | 
			
		||||
    it 'revokes the badge and decrements grant_count' do
 | 
			
		||||
    it 'revokes the badge, deletes the notification and decrements grant_count' do
 | 
			
		||||
      badge.reload.grant_count.should eq(1)
 | 
			
		||||
      StaffActionLogger.any_instance.expects(:log_badge_revoke).with(user_badge)
 | 
			
		||||
      BadgeGranter.revoke(user_badge, revoked_by: admin)
 | 
			
		||||
      UserBadge.where(user: user, badge: badge).first.should_not be_present
 | 
			
		||||
      badge.reload.grant_count.should eq(0)
 | 
			
		||||
      user.notifications.where(notification_type: Notification.types[:granted_badge]).should be_empty
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
  end
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ test('translatedDescription', function() {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('createFromJson array', function() {
 | 
			
		||||
  var badgesJson = {"badge_types":[{"id":6,"name":"Silver 1","color_hexcode":"#c0c0c0"}],"badges":[{"id":1126,"name":"Badge 1","description":null,"badge_type_id":6}]};
 | 
			
		||||
  var badgesJson = {"badge_types":[{"id":6,"name":"Silver 1"}],"badges":[{"id":1126,"name":"Badge 1","description":null,"badge_type_id":6}]};
 | 
			
		||||
 | 
			
		||||
  var badges = Discourse.Badge.createFromJson(badgesJson);
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +36,7 @@ test('createFromJson array', function() {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('createFromJson single', function() {
 | 
			
		||||
  var badgeJson = {"badge_types":[{"id":6,"name":"Silver 1","color_hexcode":"#c0c0c0"}],"badge":{"id":1126,"name":"Badge 1","description":null,"badge_type_id":6}};
 | 
			
		||||
  var badgeJson = {"badge_types":[{"id":6,"name":"Silver 1"}],"badge":{"id":1126,"name":"Badge 1","description":null,"badge_type_id":6}};
 | 
			
		||||
 | 
			
		||||
  var badge = Discourse.Badge.createFromJson(badgeJson);
 | 
			
		||||
 | 
			
		||||
@@ -44,7 +44,7 @@ test('createFromJson single', function() {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('updateFromJson', function() {
 | 
			
		||||
  var badgeJson = {"badge_types":[{"id":6,"name":"Silver 1","color_hexcode":"#c0c0c0"}],"badge":{"id":1126,"name":"Badge 1","description":null,"badge_type_id":6}};
 | 
			
		||||
  var badgeJson = {"badge_types":[{"id":6,"name":"Silver 1"}],"badge":{"id":1126,"name":"Badge 1","description":null,"badge_type_id":6}};
 | 
			
		||||
  var badge = Discourse.Badge.create({name: "Badge 1"});
 | 
			
		||||
  badge.updateFromJson(badgeJson);
 | 
			
		||||
  equal(badge.get('id'), 1126, "id is set");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
module("Discourse.UserBadge");
 | 
			
		||||
 | 
			
		||||
var singleBadgeJson = {"badges":[{"id":874,"name":"Badge 2","description":null,"badge_type_id":7}],"badge_types":[{"id":7,"name":"Silver 2","color_hexcode":"#c0c0c0"}],"users":[{"id":13470,"username":"anne3","avatar_template":"//www.gravatar.com/avatar/a4151b1fd72089c54e2374565a87da7f.png?s={size}\u0026r=pg\u0026d=identicon"}],"user_badge":{"id":665,"granted_at":"2014-03-09T20:30:01.190-04:00","badge_id":874,"granted_by_id":13470}},
 | 
			
		||||
    multipleBadgesJson = {"badges":[{"id":880,"name":"Badge 8","description":null,"badge_type_id":13}],"badge_types":[{"id":13,"name":"Silver 8","color_hexcode":"#c0c0c0"}],"users":[],"user_badges":[{"id":668,"granted_at":"2014-03-09T20:30:01.420-04:00","badge_id":880,"granted_by_id":null}]};
 | 
			
		||||
var singleBadgeJson = {"badges":[{"id":874,"name":"Badge 2","description":null,"badge_type_id":7}],"badge_types":[{"id":7,"name":"Silver 2"}],"users":[{"id":13470,"username":"anne3","avatar_template":"//www.gravatar.com/avatar/a4151b1fd72089c54e2374565a87da7f.png?s={size}\u0026r=pg\u0026d=identicon"}],"user_badge":{"id":665,"granted_at":"2014-03-09T20:30:01.190-04:00","badge_id":874,"granted_by_id":13470}},
 | 
			
		||||
    multipleBadgesJson = {"badges":[{"id":880,"name":"Badge 8","description":null,"badge_type_id":13}],"badge_types":[{"id":13,"name":"Silver 8"}],"users":[],"user_badges":[{"id":668,"granted_at":"2014-03-09T20:30:01.420-04:00","badge_id":880,"granted_by_id":null}]};
 | 
			
		||||
 | 
			
		||||
test('createFromJson single', function() {
 | 
			
		||||
  var userBadge = Discourse.UserBadge.createFromJson(singleBadgeJson);
 | 
			
		||||
@@ -25,6 +25,14 @@ test('findByUsername', function() {
 | 
			
		||||
  ok(Discourse.ajax.calledOnce, "makes an AJAX call");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('findByBadgeId', function() {
 | 
			
		||||
  this.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve(multipleBadgesJson));
 | 
			
		||||
  Discourse.UserBadge.findByBadgeId(880).then(function(badges) {
 | 
			
		||||
    ok(Array.isArray(badges), "returns an array");
 | 
			
		||||
  });
 | 
			
		||||
  ok(Discourse.ajax.calledOnce, "makes an AJAX call");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test('grant', function() {
 | 
			
		||||
  this.stub(Discourse, 'ajax').returns(Ember.RSVP.resolve(singleBadgeJson));
 | 
			
		||||
  Discourse.UserBadge.grant(1, "username").then(function(userBadge) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user