Skip to content

Commit cf1e3b4

Browse files
committed
Show total crate downloads count in user dashboard
1 parent 7ab2b1b commit cf1e3b4

File tree

9 files changed

+111
-2
lines changed

9 files changed

+111
-2
lines changed

app/adapters/user.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import ApplicationAdapter from './application';
2+
3+
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+
});

app/controllers/dashboard.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default Ember.Controller.extend({
1414
this.myCrates = [];
1515
this.myFollowing = [];
1616
this.myFeed = [];
17+
this.myStats = 0;
1718
},
1819

1920
visibleCrates: computed('myCreates', function() {
@@ -24,6 +25,10 @@ export default Ember.Controller.extend({
2425
return this.get('myFollowing').slice(0, TO_SHOW);
2526
}),
2627

28+
visibleStats: computed('myStats', function() {
29+
return this.get('myStats');
30+
}),
31+
2732
hasMoreCrates: computed('myCreates', function() {
2833
return this.get('myCrates.length') > TO_SHOW;
2934
}),

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
api_token: DS.attr('string'),
88
avatar: DS.attr('string'),
99
url: 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
@@ -86,3 +86,18 @@
8686
&:hover { background-color: darken($bg, 10%); }
8787
}
8888
}
89+
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+
}

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
<img class='logo dashboard' src="/assets/dashboard.png"/>
55
<h1>My Dashboard</h1>
6+
<div id="stats">
7+
<div class='downloads'>
8+
<img class="download" src="/assets/download.png"/>
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
@@ -131,6 +131,7 @@ pub fn middleware(app: Arc<App>) -> MiddlewareBuilder {
131131
api_router.get("/categories/:category_id", C(category::show));
132132
api_router.get("/category_slugs", C(category::slugs));
133133
api_router.get("/users/:user_id", C(user::show));
134+
api_router.get("/users/:user_id/stats", C(user::stats));
134135
let api_router = Arc::new(R404(api_router));
135136

136137
let mut router = RouteBuilder::new();

src/tests/user.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,43 @@ fn following() {
199199

200200
bad_resp!(middle.call(req.with_query("page=0")));
201201
}
202+
203+
#[test]
204+
fn user_total_downloads() {
205+
use diesel::update;
206+
207+
let (_b, app, middle) = ::app();
208+
let u;
209+
{
210+
let conn = app.diesel_database.get().unwrap();
211+
212+
u = ::new_user("foo").create_or_update(&conn).unwrap();
213+
214+
let mut krate = ::new_crate("foo_krate1")
215+
.create_or_update(&conn, None, u.id).unwrap();
216+
krate.downloads = 10;
217+
update(&krate).set(&krate).execute(&*conn).unwrap();
218+
219+
220+
let mut krate2 = ::new_crate("foo_krate2")
221+
.create_or_update(&conn, None, u.id).unwrap();
222+
krate2.downloads = 20;
223+
update(&krate2).set(&krate2).execute(&*conn).unwrap();
224+
225+
let another_user = ::new_user("bar").create_or_update(&conn).unwrap();
226+
227+
let mut another_krate = ::new_crate("bar_krate1")
228+
.create_or_update(&conn, None, another_user.id).unwrap();
229+
another_krate.downloads = 2;
230+
update(&another_krate).set(&another_krate).execute(&*conn).unwrap();
231+
}
232+
233+
let mut req = ::req(app, Method::Get, &format!("/api/v1/users/{}/stats", u.id));
234+
let mut response = ok_resp!(middle.call(&mut req));
235+
236+
#[derive(RustcDecodable)]
237+
struct Response { total_downloads: i64 }
238+
let response: Response = ::json(&mut response);
239+
assert_eq!(response.total_downloads, 30);
240+
assert!(response.total_downloads != 32);
241+
}

src/user/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,25 @@ pub fn updates(req: &mut Request) -> CargoResult<Response> {
378378
Ok(req.json(&R{ versions: versions, meta: Meta { more: more } }))
379379
}
380380

381+
/// Handles the `GET /users/:user_id/stats` route.
382+
pub fn stats(req: &mut Request) -> CargoResult<Response> {
383+
use diesel::expression::dsl::sum;
384+
use owner::OwnerKind;
385+
386+
let user_id = &req.params()["user_id"].parse::<i32>().ok().unwrap();
387+
let conn = req.db_conn()?;
388+
389+
let data = crate_owners::table.inner_join(crates::table)
390+
.filter(crate_owners::owner_id.eq(user_id).and(crate_owners::owner_kind.eq(OwnerKind::User as i32)))
391+
.select(sum(crates::downloads)).first::<i64>(&*conn)?;
392+
393+
#[derive(RustcEncodable)]
394+
struct R {
395+
total_downloads: i64
396+
}
397+
Ok(req.json(&R { total_downloads: data }))
398+
}
399+
381400
#[cfg(test)]
382401
mod tests {
383402
use super::*;

0 commit comments

Comments
 (0)