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) {