Skip to content

Commit 1f5d430

Browse files
Merge pull request #740 from PritiKumr/total-downloads-user
Total downloads user
2 parents d60eece + 9719fed commit 1f5d430

File tree

9 files changed

+120
-6
lines changed

9 files changed

+120
-6
lines changed

app/adapters/user.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import ApplicationAdapter from './application';
22

33
export default ApplicationAdapter.extend({
4+
stats(id) {
5+
return this.ajax(this.urlForStatsAction(id), 'GET');
6+
},
7+
8+
urlForStatsAction(id) {
9+
return `${this.buildURL('user', id)}/stats`;
10+
},
11+
412
queryRecord(store, type, query) {
513
let url = this.urlForFindRecord(query.user_id, 'user');
614
return this.ajax(url, 'GET');

app/controllers/dashboard.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,26 @@ export default Ember.Controller.extend({
1616
this.myCrates = [];
1717
this.myFollowing = [];
1818
this.myFeed = [];
19+
this.myStats = 0;
1920
},
2021

21-
visibleCrates: computed('myCreates', function() {
22+
visibleCrates: computed('myCrates.[]', function() {
2223
return this.get('myCrates').slice(0, TO_SHOW);
2324
}),
2425

25-
visibleFollowing: computed('myFollowing', function() {
26+
visibleFollowing: computed('myFollowing.[]', function() {
2627
return this.get('myFollowing').slice(0, TO_SHOW);
2728
}),
2829

29-
hasMoreCrates: computed('myCreates', function() {
30+
visibleStats: computed('myStats', function() {
31+
return this.get('myStats');
32+
}),
33+
34+
hasMoreCrates: computed('myCrates.[]', function() {
3035
return this.get('myCrates.length') > TO_SHOW;
3136
}),
3237

33-
hasMoreFollowing: computed('myFollowing', function() {
38+
hasMoreFollowing: computed('myFollowing.[]', function() {
3439
return this.get('myFollowing.length') > TO_SHOW;
3540
}),
3641

app/models/user.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@ export default DS.Model.extend({
77
avatar: DS.attr('string'),
88
url: DS.attr('string'),
99
kind: DS.attr('string'),
10+
11+
stats() {
12+
return this.store.adapterFor('user').stats(this.get('id'));
13+
},
1014
});

app/routes/dashboard.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default Ember.Route.extend(AuthenticatedRoute, {
1010
controller.set('fetchingFeed', true);
1111
controller.set('myCrates', this.get('data.myCrates'));
1212
controller.set('myFollowing', this.get('data.myFollowing'));
13+
controller.set('myStats', this.get('data.myStats'));
1314

1415
if (!controller.get('loadingMore')) {
1516
controller.set('myFeed', []);
@@ -30,9 +31,14 @@ export default Ember.Route.extend(AuthenticatedRoute, {
3031
following: 1
3132
});
3233

34+
let myStats = user.stats();
35+
3336
return Ember.RSVP.hash({
3437
myCrates,
35-
myFollowing
36-
}).then((hash) => this.set('data', hash));
38+
myFollowing,
39+
myStats
40+
}).then((hash) => {
41+
this.set('data', hash);
42+
});
3743
}
3844
});

app/styles/me.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@
8787
}
8888
}
8989

90+
#stats {
91+
margin-left: auto;
92+
padding: 10px;
93+
94+
span { margin-left: 10px; }
95+
.num {
96+
font-size: 30px;
97+
font-weight: bold;
98+
}
99+
.downloads {
100+
@include display-flex;
101+
@include align-items(center);
102+
}
103+
}
104+
90105
.me-subheading {
91106
@include display-flex;
92107
.right {

app/templates/dashboard.hbs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@
33
<div id='crates-heading'>
44
{{svg-jar "dashboard"}}
55
<h1>My Dashboard</h1>
6+
<div id="stats">
7+
<div class='downloads'>
8+
<img class="download" src="/assets/download.svg" />
9+
<span class='num'>{{visibleStats.total_downloads}}</span>
10+
<span class='desc small'>Total Downloads</span>
11+
</div>
12+
</div>
613
</div>
714

15+
816
<div id='my-info'>
917
<div id='my-crate-lists' class='crate-lists'>
1018
<div id='my-crates'>

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ pub fn middleware(app: Arc<App>) -> MiddlewareBuilder {
148148
api_router.get("/categories/:category_id", C(category::show));
149149
api_router.get("/category_slugs", C(category::slugs));
150150
api_router.get("/users/:user_id", C(user::show));
151+
api_router.get("/users/:user_id/stats", C(user::stats));
151152
api_router.get("/teams/:team_id", C(user::show_team));
152153
let api_router = Arc::new(R404(api_router));
153154

src/tests/user.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use cargo_registry::krate::EncodableCrate;
88
use cargo_registry::user::{User, NewUser, EncodableUser};
99
use cargo_registry::version::EncodableVersion;
1010

11+
use diesel::prelude::*;
12+
1113
#[derive(RustcDecodable)]
1214
struct AuthResponse {
1315
url: String,
@@ -206,6 +208,48 @@ fn following() {
206208
bad_resp!(middle.call(req.with_query("page=0")));
207209
}
208210

211+
#[test]
212+
fn user_total_downloads() {
213+
use diesel::update;
214+
215+
let (_b, app, middle) = ::app();
216+
let u;
217+
{
218+
let conn = app.diesel_database.get().unwrap();
219+
220+
u = ::new_user("foo").create_or_update(&conn).unwrap();
221+
222+
let mut krate = ::CrateBuilder::new("foo_krate1", u.id).expect_build(&conn);
223+
krate.downloads = 10;
224+
update(&krate).set(&krate).execute(&*conn).unwrap();
225+
226+
let mut krate2 = ::CrateBuilder::new("foo_krate2", u.id).expect_build(&conn);
227+
krate2.downloads = 20;
228+
update(&krate2).set(&krate2).execute(&*conn).unwrap();
229+
230+
let another_user = ::new_user("bar").create_or_update(&conn).unwrap();
231+
232+
let mut another_krate = ::CrateBuilder::new("bar_krate1", another_user.id)
233+
.expect_build(&conn);
234+
another_krate.downloads = 2;
235+
update(&another_krate)
236+
.set(&another_krate)
237+
.execute(&*conn)
238+
.unwrap();
239+
}
240+
241+
let mut req = ::req(app, Method::Get, &format!("/api/v1/users/{}/stats", u.id));
242+
let mut response = ok_resp!(middle.call(&mut req));
243+
244+
#[derive(RustcDecodable)]
245+
struct Response {
246+
total_downloads: i64,
247+
}
248+
let response: Response = ::json(&mut response);
249+
assert_eq!(response.total_downloads, 30);
250+
assert!(response.total_downloads != 32);
251+
}
252+
209253
#[test]
210254
fn updating_existing_user_doesnt_change_api_token() {
211255
let (_b, app, _middle) = ::app();

src/user/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,3 +420,26 @@ pub fn updates(req: &mut Request) -> CargoResult<Response> {
420420
meta: Meta { more: more },
421421
}))
422422
}
423+
424+
/// Handles the `GET /users/:user_id/stats` route.
425+
pub fn stats(req: &mut Request) -> CargoResult<Response> {
426+
use diesel::expression::dsl::sum;
427+
use owner::OwnerKind;
428+
429+
let user_id = &req.params()["user_id"].parse::<i32>().ok().unwrap();
430+
let conn = req.db_conn()?;
431+
432+
let data = crate_owners::table
433+
.inner_join(crates::table)
434+
.filter(crate_owners::owner_id.eq(user_id).and(
435+
crate_owners::owner_kind.eq(OwnerKind::User as i32),
436+
))
437+
.select(sum(crates::downloads))
438+
.first::<i64>(&*conn)?;
439+
440+
#[derive(RustcEncodable)]
441+
struct R {
442+
total_downloads: i64,
443+
}
444+
Ok(req.json(&R { total_downloads: data }))
445+
}

0 commit comments

Comments
 (0)