Skip to content

Commit bbf5b22

Browse files
Merge pull request #622 from sgrif/sg-follow-the-diesel
Follow the Diesel
2 parents c3168de + c10cf87 commit bbf5b22

File tree

8 files changed

+148
-120
lines changed

8 files changed

+148
-120
lines changed

app/controllers/dashboard.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ export default Ember.Controller.extend({
3838
var page = (this.get('myFeed').length / 10) + 1;
3939

4040
ajax(`/me/updates?page=${page}`).then((data) => {
41-
data.crates.forEach(crate =>
42-
this.store.push(this.store.normalize('crate', crate)));
43-
4441
var versions = data.versions.map(version =>
4542
this.store.push(this.store.normalize('version', version)));
4643

app/models/version.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import DS from 'ember-data';
2+
import Ember from 'ember';
23

34
export default DS.Model.extend({
45
num: DS.attr('string'),
@@ -15,6 +16,10 @@ export default DS.Model.extend({
1516
dependencies: DS.hasMany('dependency', { async: true }),
1617
version_downloads: DS.hasMany('version-download', { async: true }),
1718

19+
crateName: Ember.computed('crate', function() {
20+
return this.belongsTo('crate').id();
21+
}),
22+
1823
getDownloadUrl() {
1924
return this.store.adapterFor('version').getDownloadUrl(this.get('dl_path'));
2025
},

app/templates/dashboard.hbs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@
4949
{{#each myFeed as |version|}}
5050
<div class='row'>
5151
<div class='info'>
52-
{{link-to version.crate.name 'crate.version' version.num}}
52+
{{#link-to 'crate.version' version.crateName version.num}}
53+
{{ version.crateName }}
5354
<span class='small'>{{ version.num }}</span>
55+
{{/link-to}}
5456
<span class='date small'>
55-
{{from-now version.created_at}}
57+
{{moment-from-now version.created_at}}
5658
</span>
5759
</div>
5860
</div>

src/krate.rs

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ use std::collections::HashMap;
44

55
use conduit::{Request, Response};
66
use conduit_router::RequestParams;
7-
use diesel::pg::PgConnection;
7+
use diesel::associations::Identifiable;
88
use diesel::pg::upsert::*;
9+
use diesel::pg::{Pg, PgConnection};
910
use diesel::prelude::*;
11+
use diesel;
1012
use diesel_full_text_search::*;
1113
use license_exprs;
1214
use pg::GenericConnection;
@@ -35,7 +37,8 @@ use util::{RequestUtils, CargoResult, internal, ChainError, human};
3537
use version::EncodableVersion;
3638
use {Model, User, Keyword, Version, Category, Badge, Replica};
3739

38-
#[derive(Clone, Queryable, Identifiable, AsChangeset)]
40+
#[derive(Clone, Queryable, Identifiable, AsChangeset, Associations)]
41+
#[has_many(versions)]
3942
pub struct Crate {
4043
pub id: i32,
4144
pub name: String,
@@ -64,6 +67,8 @@ pub const ALL_COLUMNS: AllColumns = (crates::id, crates::name,
6467
crates::readme, crates::license, crates::repository,
6568
crates::max_upload_size);
6669

70+
type CrateQuery<'a> = crates::BoxedQuery<'a, Pg, <AllColumns as Expression>::SqlType>;
71+
6772
#[derive(RustcEncodable, RustcDecodable)]
6873
pub struct EncodableCrate {
6974
pub id: String,
@@ -224,6 +229,15 @@ impl<'a> NewCrate<'a> {
224229
}
225230

226231
impl Crate {
232+
pub fn by_name(name: &str) -> CrateQuery {
233+
crates::table
234+
.select(ALL_COLUMNS)
235+
.filter(
236+
canon_crate_name(crates::name).eq(
237+
canon_crate_name(name))
238+
).into_boxed()
239+
}
240+
227241
pub fn find_by_name(conn: &GenericConnection,
228242
name: &str) -> CargoResult<Crate> {
229243
let stmt = conn.prepare("SELECT * FROM crates \
@@ -1073,44 +1087,61 @@ fn user_and_crate(req: &mut Request) -> CargoResult<(User, Crate)> {
10731087
Ok((user.clone(), krate))
10741088
}
10751089

1090+
#[derive(Insertable, Queryable, Identifiable, Associations)]
1091+
#[belongs_to(User)]
1092+
#[primary_key(user_id, crate_id)]
1093+
#[table_name="follows"]
1094+
pub struct Follow {
1095+
user_id: i32,
1096+
crate_id: i32,
1097+
}
1098+
1099+
fn follow_target(req: &mut Request) -> CargoResult<Follow> {
1100+
let user = req.user()?;
1101+
let conn = req.db_conn()?;
1102+
let crate_name = &req.params()["crate_id"];
1103+
let crate_id = Crate::by_name(crate_name)
1104+
.select(crates::id)
1105+
.first(conn)?;
1106+
Ok(Follow {
1107+
user_id: user.id,
1108+
crate_id: crate_id,
1109+
})
1110+
}
1111+
10761112
/// Handles the `PUT /crates/:crate_id/follow` route.
10771113
pub fn follow(req: &mut Request) -> CargoResult<Response> {
1078-
let (user, krate) = user_and_crate(req)?;
1079-
let tx = req.tx()?;
1080-
let stmt = tx.prepare("SELECT 1 FROM follows
1081-
WHERE user_id = $1 AND crate_id = $2")?;
1082-
let rows = stmt.query(&[&user.id, &krate.id])?;
1083-
if !rows.iter().next().is_some() {
1084-
tx.execute("INSERT INTO follows (user_id, crate_id)
1085-
VALUES ($1, $2)", &[&user.id, &krate.id])?;
1086-
}
1114+
let follow = follow_target(req)?;
1115+
let conn = req.db_conn()?;
1116+
diesel::insert(&follow.on_conflict_do_nothing())
1117+
.into(follows::table)
1118+
.execute(conn)?;
10871119
#[derive(RustcEncodable)]
10881120
struct R { ok: bool }
10891121
Ok(req.json(&R { ok: true }))
10901122
}
10911123

10921124
/// Handles the `DELETE /crates/:crate_id/follow` route.
10931125
pub fn unfollow(req: &mut Request) -> CargoResult<Response> {
1094-
let (user, krate) = user_and_crate(req)?;
1095-
let tx = req.tx()?;
1096-
tx.execute("DELETE FROM follows
1097-
WHERE user_id = $1 AND crate_id = $2",
1098-
&[&user.id, &krate.id])?;
1126+
let follow = follow_target(req)?;
1127+
let conn = req.db_conn()?;
1128+
diesel::delete(&follow).execute(conn)?;
10991129
#[derive(RustcEncodable)]
11001130
struct R { ok: bool }
11011131
Ok(req.json(&R { ok: true }))
11021132
}
11031133

11041134
/// Handles the `GET /crates/:crate_id/following` route.
11051135
pub fn following(req: &mut Request) -> CargoResult<Response> {
1106-
let (user, krate) = user_and_crate(req)?;
1107-
let tx = req.tx()?;
1108-
let stmt = tx.prepare("SELECT 1 FROM follows
1109-
WHERE user_id = $1 AND crate_id = $2")?;
1110-
let rows = stmt.query(&[&user.id, &krate.id])?;
1136+
use diesel::expression::dsl::exists;
1137+
1138+
let follow = follow_target(req)?;
1139+
let conn = req.db_conn()?;
1140+
let following = diesel::select(exists(follows::table.find(follow.id())))
1141+
.get_result(conn)?;
11111142
#[derive(RustcEncodable)]
11121143
struct R { following: bool }
1113-
Ok(req.json(&R { following: rows.iter().next().is_some() }))
1144+
Ok(req.json(&R { following: following }))
11141145
}
11151146

11161147
/// Handles the `GET /crates/:crate_id/versions` route.

src/tests/all.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#![deny(warnings)]
22

3+
#[macro_use] extern crate diesel;
4+
#[macro_use] extern crate diesel_codegen;
35
extern crate bufstream;
46
extern crate cargo_registry;
57
extern crate conduit;
68
extern crate conduit_middleware;
79
extern crate conduit_test;
810
extern crate curl;
9-
extern crate diesel;
1011
extern crate dotenv;
1112
extern crate git2;
1213
extern crate postgres;

src/tests/krate.rs

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -769,63 +769,53 @@ fn dependencies() {
769769

770770
#[test]
771771
fn following() {
772-
// #[derive(RustcDecodable)] struct F { following: bool }
773-
// #[derive(RustcDecodable)] struct O { ok: bool }
772+
#[derive(RustcDecodable)] struct F { following: bool }
773+
#[derive(RustcDecodable)] struct O { ok: bool }
774774

775775
let (_b, app, middle) = ::app();
776776
let mut req = ::req(app.clone(), Method::Get, "/api/v1/crates/foo_following/following");
777777

778778
let user;
779-
let krate;
780779
{
781780
let conn = app.diesel_database.get().unwrap();
782781
user = ::new_user("foo").create_or_update(&conn).unwrap();
783782
::sign_in_as(&mut req, &user);
784-
krate = ::new_crate("foo_following").create_or_update(&conn, None, user.id).unwrap();
785-
786-
// FIXME: Go back to hitting the actual endpoint once it's using Diesel
787-
conn
788-
.execute(&format!("INSERT INTO follows (user_id, crate_id) VALUES ({}, {})",
789-
user.id, krate.id))
790-
.unwrap();
783+
::new_crate("foo_following").create_or_update(&conn, None, user.id).unwrap();
791784
}
792785

793-
// let mut response = ok_resp!(middle.call(&mut req));
794-
// assert!(!::json::<F>(&mut response).following);
786+
let mut response = ok_resp!(middle.call(&mut req));
787+
assert!(!::json::<F>(&mut response).following);
795788

796-
// req.with_path("/api/v1/crates/foo_following/follow")
797-
// .with_method(Method::Put);
798-
// let mut response = ok_resp!(middle.call(&mut req));
799-
// assert!(::json::<O>(&mut response).ok);
800-
// let mut response = ok_resp!(middle.call(&mut req));
801-
// assert!(::json::<O>(&mut response).ok);
789+
req.with_path("/api/v1/crates/foo_following/follow")
790+
.with_method(Method::Put);
791+
let mut response = ok_resp!(middle.call(&mut req));
792+
assert!(::json::<O>(&mut response).ok);
793+
let mut response = ok_resp!(middle.call(&mut req));
794+
assert!(::json::<O>(&mut response).ok);
802795

803-
// req.with_path("/api/v1/crates/foo_following/following")
804-
// .with_method(Method::Get);
805-
// let mut response = ok_resp!(middle.call(&mut req));
806-
// assert!(::json::<F>(&mut response).following);
796+
req.with_path("/api/v1/crates/foo_following/following")
797+
.with_method(Method::Get);
798+
let mut response = ok_resp!(middle.call(&mut req));
799+
assert!(::json::<F>(&mut response).following);
807800

808801
req.with_path("/api/v1/crates")
809-
.with_query("following=1");
802+
.with_method(Method::Get)
803+
.with_query("following=1");
810804
let mut response = ok_resp!(middle.call(&mut req));
811805
let l = ::json::<CrateList>(&mut response);
812806
assert_eq!(l.crates.len(), 1);
813807

814-
// FIXME: Go back to hitting the actual endpoint once it's using Diesel
815-
req.db_conn().unwrap()
816-
.execute("TRUNCATE TABLE follows")
817-
.unwrap();
818-
// req.with_path("/api/v1/crates/foo_following/follow")
819-
// .with_method(Method::Delete);
820-
// let mut response = ok_resp!(middle.call(&mut req));
821-
// assert!(::json::<O>(&mut response).ok);
822-
// let mut response = ok_resp!(middle.call(&mut req));
823-
// assert!(::json::<O>(&mut response).ok);
824-
825-
// req.with_path("/api/v1/crates/foo_following/following")
826-
// .with_method(Method::Get);
827-
// let mut response = ok_resp!(middle.call(&mut req));
828-
// assert!(!::json::<F>(&mut response).following);
808+
req.with_path("/api/v1/crates/foo_following/follow")
809+
.with_method(Method::Delete);
810+
let mut response = ok_resp!(middle.call(&mut req));
811+
assert!(::json::<O>(&mut response).ok);
812+
let mut response = ok_resp!(middle.call(&mut req));
813+
assert!(::json::<O>(&mut response).ok);
814+
815+
req.with_path("/api/v1/crates/foo_following/following")
816+
.with_method(Method::Get);
817+
let mut response = ok_resp!(middle.call(&mut req));
818+
assert!(!::json::<F>(&mut response).following);
829819

830820
req.with_path("/api/v1/crates")
831821
.with_query("following=1")

src/tests/user.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use conduit::{Handler, Method};
2+
use diesel::prelude::*;
3+
use diesel::insert;
24

35
use cargo_registry::Model;
6+
use cargo_registry::db::RequestTransaction;
47
use cargo_registry::krate::EncodableCrate;
8+
use cargo_registry::schema::versions;
59
use cargo_registry::user::{User, NewUser, EncodableUser};
6-
use cargo_registry::db::RequestTransaction;
710
use cargo_registry::version::EncodableVersion;
811

912
#[derive(RustcDecodable)]
@@ -139,10 +142,27 @@ fn following() {
139142
#[derive(RustcDecodable)] struct Meta { more: bool }
140143

141144
let (_b, app, middle) = ::app();
142-
let mut req = ::req(app, Method::Get, "/");
143-
::mock_user(&mut req, ::user("foo"));
144-
::mock_crate(&mut req, ::krate("foo_fighters"));
145-
::mock_crate(&mut req, ::krate("bar_fighters"));
145+
let mut req = ::req(app.clone(), Method::Get, "/");
146+
{
147+
let conn = app.diesel_database.get().unwrap();
148+
let user = ::new_user("foo").create_or_update(&conn).unwrap();
149+
::sign_in_as(&mut req, &user);
150+
#[derive(Insertable)]
151+
#[table_name="versions"]
152+
struct NewVersion<'a> {
153+
crate_id: i32,
154+
num: &'a str,
155+
}
156+
let id1 = ::new_crate("foo_fighters").create_or_update(&conn, None, user.id)
157+
.unwrap().id;
158+
let id2 = ::new_crate("bar_fighters").create_or_update(&conn, None, user.id)
159+
.unwrap().id;
160+
let new_versions = vec![
161+
NewVersion { crate_id: id1, num: "1.0.0" },
162+
NewVersion { crate_id: id2, num: "1.0.0" },
163+
];
164+
insert(&new_versions).into(versions::table).execute(&*conn).unwrap();
165+
}
146166

147167
let mut response = ok_resp!(middle.call(req.with_path("/me/updates")
148168
.with_method(Method::Get)));

0 commit comments

Comments
 (0)