Skip to content

Commit 54fe971

Browse files
committed
rust-lang#494 WIP - Option to favorite and unfavorite user
1 parent 120f800 commit 54fe971

File tree

12 files changed

+155
-7
lines changed

12 files changed

+155
-7
lines changed

app/adapters/user.js

+12
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,16 @@ export default ApplicationAdapter.extend({
1313
let url = this.urlForFindRecord(query.user_id, 'user');
1414
return this.ajax(url, 'GET');
1515
},
16+
17+
favorite(id) {
18+
return this.ajax(this.urlForFavoriteAction(id), 'PUT');
19+
},
20+
21+
unfavorite(id) {
22+
return this.ajax(this.urlForFavoriteAction(id), 'DELETE');
23+
},
24+
25+
urlForFavoriteAction(id) {
26+
return `${this.buildURL('user', id)}/favorite`;
27+
},
1628
});

app/controllers/user.js

+16
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,20 @@ export default Controller.extend(PaginationMixin, {
2121
return 'Alphabetical';
2222
}
2323
}),
24+
25+
fetchingFavorite: false,
26+
favorited: false,
27+
28+
actions: {
29+
toggleFavorite() {
30+
this.set('fetchingFavorite', true);
31+
32+
let owner = this.get('user');
33+
let op = this.toggleProperty('favorited') ?
34+
owner.favorite() : owner.unfavorite();
35+
36+
return op.finally(() => this.set('fetchingFavorite', false));
37+
},
38+
},
39+
2440
});

app/models/user.js

+8
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,12 @@ export default DS.Model.extend({
1111
stats() {
1212
return this.store.adapterFor('user').stats(this.get('id'));
1313
},
14+
15+
favorite() {
16+
return this.store.adapterFor('user').favorite(this.get('id'));
17+
},
18+
19+
unfavorite() {
20+
return this.store.adapterFor('user').unfavorite(this.get('id'));
21+
},
1422
});

app/routes/user.js

+13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
<<<<<<< 120f8008fb08c21fc6cd239c1100692a0ff487e6
12
import Route from '@ember/routing/route';
23
import { inject as service } from '@ember/service';
34
import RSVP from 'rsvp';
5+
import ajax from 'ic-ajax';
46

57
export default Route.extend({
68
flashMessages: service(),
@@ -36,5 +38,16 @@ export default Route.extend({
3638

3739
controller.set('fetchingFeed', true);
3840
controller.set('crates', this.get('data.crates'));
41+
controller.set('user', model.user);
42+
controller.set(
43+
'allowFavorting',
44+
this.session.get('currentUser') != model.user
45+
);
46+
47+
if (controller.get('allowFavorting')) {
48+
ajax(`/api/v1/users/${model.user.id}/favorited`)
49+
.then((d) => controller.set('favorited', d.favorited))
50+
.finally(() => controller.set('fetchingFavorite', false));
51+
}
3952
},
4053
});

app/templates/user.hbs

+26-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
<div id='crates-heading'>
2-
{{user-avatar user=model.user size='medium'}}
3-
<h1>
4-
{{ model.user.login }}
5-
</h1>
6-
{{#user-link user=model.user}}
7-
<img alt="GitHub profile" title="GitHub profile" src="/assets/GitHub-Mark-32px.png"/>
8-
{{/user-link}}
2+
<div class="wide">
3+
<div class='info'>
4+
{{user-avatar user=model.user size='medium'}}
5+
<h1>
6+
{{ model.user.login }}
7+
</h1>
8+
{{#user-link user=model.user}}
9+
<img alt="GitHub profile" title="GitHub profile" src="/assets/GitHub-Mark-32px.png"/>
10+
{{/user-link}}
11+
</div>
12+
<div class='right'>
13+
{{#if allowFavorting}}
14+
<button class='tan-button' {{action 'toggleFavorite' this}}>
15+
{{#if fetchingFavorite}}
16+
<img src="/assets/ajax-loader.gif"/>
17+
{{else}}
18+
{{#if favorited}}
19+
Unfavorite
20+
{{else}}
21+
Favorite
22+
{{/if}}
23+
{{/if}}
24+
</button>
25+
{{/if}}
26+
</div>
27+
</div>
928
</div>
1029

1130
<div id='user-profile'>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE favorite_users;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CREATE TABLE favorite_users (
2+
user_id INTEGER NOT NULL,
3+
target_id INTEGER NOT NULL,
4+
CONSTRAINT favorites_pkey PRIMARY KEY (user_id, target_id),
5+
CONSTRAINT fk_favorites_user_id FOREIGN KEY (user_id)
6+
REFERENCES users (id),
7+
CONSTRAINT fk_favorites_target_id FOREIGN KEY (target_id)
8+
REFERENCES users (id)
9+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP INDEX index_favorites_user_id;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CREATE INDEX index_favorites_user_id ON favorite_users (user_id);

src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ pub fn middleware(app: Arc<App>) -> MiddlewareBuilder {
153153
api_router.get("/users/:user_id", C(user::show));
154154
api_router.get("/users/:user_id/stats", C(user::stats));
155155
api_router.get("/teams/:team_id", C(user::show_team));
156+
api_router.get("/users/:user_id/favorited", C(user::favorited));
157+
api_router.put("/users/:user_id/favorite", C(user::favorite));
158+
api_router.delete("/users/:user_id/favorite", C(user::unfavorite));
156159
let api_router = Arc::new(R404(api_router));
157160

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

src/schema.rs

+8
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,11 @@ table! {
183183
license -> Nullable<Varchar>,
184184
}
185185
}
186+
187+
table! {
188+
favorite_users (user_id,
189+
target_id) {
190+
user_id -> Int4,
191+
target_id -> Int4,
192+
}
193+
}

src/user/mod.rs

+57
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use conduit::{Request, Response};
22
use conduit_cookie::RequestSession;
33
use conduit_router::RequestParams;
44
use diesel::prelude::*;
5+
use diesel;
6+
57
use diesel::pg::PgConnection;
68
use pg::GenericConnection;
79
use pg::rows::Row;
@@ -375,6 +377,61 @@ pub fn show_team(req: &mut Request) -> CargoResult<Response> {
375377
Ok(req.json(&R { team: team.encodable() }))
376378
}
377379

380+
#[derive(Insertable, Queryable, Identifiable, Associations)]
381+
#[belongs_to(User)]
382+
#[primary_key(user_id, target_id)]
383+
#[table_name="favorite_users"]
384+
pub struct FavoriteUser {
385+
user_id: i32,
386+
target_id: i32,
387+
}
388+
389+
fn favorite_target(req: &mut Request) -> CargoResult<FavoriteUser> {
390+
let user = req.user()?;
391+
let target_user_id: i32 = req.params()["user_id"].parse().expect("User ID not found");
392+
Ok(FavoriteUser {
393+
user_id: user.id,
394+
target_id: target_user_id,
395+
})
396+
}
397+
398+
/// Handles the `PUT /users/:user_id/favorite` route.
399+
pub fn favorite(req: &mut Request) -> CargoResult<Response> {
400+
use diesel::pg::upsert::OnConflictExtension;
401+
402+
let favorite = favorite_target(req)?;
403+
let conn = req.db_conn()?;
404+
diesel::insert(&favorite.on_conflict_do_nothing())
405+
.into(favorite_users::table)
406+
.execute(&*conn)?;
407+
#[derive(RustcEncodable)]
408+
struct R { ok: bool }
409+
Ok(req.json(&R { ok: true }))
410+
}
411+
412+
/// Handles the `DELETE /users/:user_id/favorite` route.
413+
pub fn unfavorite(req: &mut Request) -> CargoResult<Response> {
414+
let favorite = favorite_target(req)?;
415+
let conn = req.db_conn()?;
416+
diesel::delete(&favorite).execute(&*conn)?;
417+
#[derive(RustcEncodable)]
418+
struct R { ok: bool }
419+
Ok(req.json(&R { ok: true }))
420+
}
421+
422+
/// Handles the `GET /users/:user_id/favorited` route.
423+
pub fn favorited(req: &mut Request) -> CargoResult<Response> {
424+
use diesel::expression::dsl::exists;
425+
426+
let fav = favorite_target(req)?;
427+
let conn = req.db_conn()?;
428+
let favorited = diesel::select(exists(favorite_users::table.find(fav.id())))
429+
.get_result(&*conn)?;
430+
#[derive(RustcEncodable)]
431+
struct R { favorited: bool }
432+
Ok(req.json(&R { favorited: favorited }))
433+
}
434+
378435
/// Handles the `GET /me/updates` route.
379436
pub fn updates(req: &mut Request) -> CargoResult<Response> {
380437
use diesel::expression::dsl::any;

0 commit comments

Comments
 (0)