diff --git a/app/assets/javascripts/discourse/models/badge.js b/app/assets/javascripts/discourse/models/badge.js
index 7fa14bf6142..4bc61a95eca 100644
--- a/app/assets/javascripts/discourse/models/badge.js
+++ b/app/assets/javascripts/discourse/models/badge.js
@@ -167,5 +167,18 @@ Discourse.Badge.reopenClass({
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);
+ });
}
});
diff --git a/app/assets/javascripts/discourse/models/user_badge.js b/app/assets/javascripts/discourse/models/user_badge.js
index 3d521c1fa12..b19a3a25a7c 100644
--- a/app/assets/javascripts/discourse/models/user_badge.js
+++ b/app/assets/javascripts/discourse/models/user_badge.js
@@ -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`.
diff --git a/app/assets/javascripts/discourse/routes/application_routes.js b/app/assets/javascripts/discourse/routes/application_routes.js
index 53fc50ef30c..261b9291749 100644
--- a/app/assets/javascripts/discourse/routes/application_routes.js
+++ b/app/assets/javascripts/discourse/routes/application_routes.js
@@ -95,5 +95,7 @@ Discourse.Route.buildRoutes(function() {
this.route('signup', {path: '/signup'});
this.route('login', {path: '/login'});
- this.route('badges');
+ this.resource('badges', function() {
+ this.route('show', {path: '/:id/:slug'});
+ });
});
diff --git a/app/assets/javascripts/discourse/routes/badges_route.js b/app/assets/javascripts/discourse/routes/badges_index_route.js
similarity index 68%
rename from app/assets/javascripts/discourse/routes/badges_route.js
rename to app/assets/javascripts/discourse/routes/badges_index_route.js
index 4852fc4bea6..6563af0d777 100644
--- a/app/assets/javascripts/discourse/routes/badges_route.js
+++ b/app/assets/javascripts/discourse/routes/badges_index_route.js
@@ -1,12 +1,12 @@
/**
Shows a list of all badges.
- @class BadgesRoute
+ @class BadgesIndexRoute
@extends Discourse.Route
@namespace Discourse
@module Discourse
**/
-Discourse.BadgesRoute = Discourse.Route.extend({
+Discourse.BadgesIndexRoute = Discourse.Route.extend({
model: function() {
return Discourse.Badge.findAll();
}
diff --git a/app/assets/javascripts/discourse/routes/badges_show_route.js b/app/assets/javascripts/discourse/routes/badges_show_route.js
new file mode 100644
index 00000000000..d0769325040
--- /dev/null
+++ b/app/assets/javascripts/discourse/routes/badges_show_route.js
@@ -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);
+ }
+});
diff --git a/app/assets/javascripts/discourse/templates/badges.js.handlebars b/app/assets/javascripts/discourse/templates/badges/index.js.handlebars
similarity index 100%
rename from app/assets/javascripts/discourse/templates/badges.js.handlebars
rename to app/assets/javascripts/discourse/templates/badges/index.js.handlebars
diff --git a/app/assets/javascripts/discourse/templates/badges/show.js.handlebars b/app/assets/javascripts/discourse/templates/badges/show.js.handlebars
new file mode 100644
index 00000000000..ce161dcf02b
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/badges/show.js.handlebars
@@ -0,0 +1,27 @@
+
+
+ {{#link-to 'badges.index'}}{{i18n badges.title}}{{/link-to}}
+
+ {{name}}
+
+
+
+
+ {{user-badge badge=this}} |
+ {{description}} |
+ {{i18n badges.awarded count=grant_count}} |
+
+
+
+ {{#if userBadges}}
+
{{i18n users}}
+
+ {{#each userBadges}}
+ {{#link-to 'userActivity' user}}
+ {{avatar user imageSize="large"}}
+ {{/link-to}}
+ {{/each}}
+ {{else}}
+
{{i18n loading}}
+ {{/if}}
+
diff --git a/app/assets/javascripts/discourse/templates/components/user-badge.js.handlebars b/app/assets/javascripts/discourse/templates/components/user-badge.js.handlebars
index b934b3b1faa..ca2070b7e3d 100644
--- a/app/assets/javascripts/discourse/templates/components/user-badge.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/components/user-badge.js.handlebars
@@ -1,4 +1,6 @@
-
-
- {{badge.name}}
-
+{{#link-to 'badges.show' badge}}
+
+
+ {{badge.name}}
+
+{{/link-to}}
diff --git a/app/assets/stylesheets/desktop/user-badges.scss b/app/assets/stylesheets/desktop/user-badges.scss
index 9141d4f0ecc..941e715a2a0 100644
--- a/app/assets/stylesheets/desktop/user-badges.scss
+++ b/app/assets/stylesheets/desktop/user-badges.scss
@@ -1,5 +1,6 @@
.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;
@@ -27,7 +28,7 @@
}
table.badges-listing {
- margin-top: 20px;
+ margin: 20px 0;
border-bottom: 1px solid $primary-border-color;
.user-badge {
diff --git a/app/controllers/badges_controller.rb b/app/controllers/badges_controller.rb
index c256cbed60f..a8908afd4f8 100644
--- a/app/controllers/badges_controller.rb
+++ b/app/controllers/badges_controller.rb
@@ -3,4 +3,10 @@ class BadgesController < ApplicationController
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
diff --git a/app/controllers/user_badges_controller.rb b/app/controllers/user_badges_controller.rb
index ef5bbf3345f..daba1ddc9f6 100644
--- a/app/controllers/user_badges_controller.rb
+++ b/app/controllers/user_badges_controller.rb
@@ -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
diff --git a/app/serializers/user_badge_serializer.rb b/app/serializers/user_badge_serializer.rb
index d91dfb9162b..eda18b11ea9 100644
--- a/app/serializers/user_badge_serializer.rb
+++ b/app/serializers/user_badge_serializer.rb
@@ -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
diff --git a/config/routes.rb b/config/routes.rb
index 3afb6af3722..2bf6bfa3565 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -243,6 +243,7 @@ Discourse::Application.routes.draw do
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.
diff --git a/spec/controllers/badges_controller_spec.rb b/spec/controllers/badges_controller_spec.rb
index 60bd0ed368a..945d641512b 100644
--- a/spec/controllers/badges_controller_spec.rb
+++ b/spec/controllers/badges_controller_spec.rb
@@ -12,4 +12,13 @@ describe BadgesController do
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
diff --git a/spec/controllers/user_badges_controller_spec.rb b/spec/controllers/user_badges_controller_spec.rb
index 311d448f41c..87fc240e6ee 100644
--- a/spec/controllers/user_badges_controller_spec.rb
+++ b/spec/controllers/user_badges_controller_spec.rb
@@ -7,17 +7,25 @@ describe UserBadgesController do
context 'index' do
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
diff --git a/test/javascripts/models/user_badge_test.js b/test/javascripts/models/user_badge_test.js
index eb56100df94..9c7c94de646 100644
--- a/test/javascripts/models/user_badge_test.js
+++ b/test/javascripts/models/user_badge_test.js
@@ -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) {