Skip to content

Commit b098509

Browse files
committed
Implement listing of crate invitations
Connects to rust-lang#959.
1 parent b3808ad commit b098509

File tree

3 files changed

+163
-0
lines changed

3 files changed

+163
-0
lines changed

src/crate_owner_invitation.rs

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use conduit::{Request, Response};
2+
use diesel::prelude::*;
3+
use time::Timespec;
4+
5+
use db::RequestTransaction;
6+
use schema::{crate_owner_invitations, users, crates};
7+
use user::RequestUser;
8+
use util::errors::CargoResult;
9+
use util::RequestUtils;
10+
11+
/// The model representing a row in the `crate_owner_invitations` database table.
12+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Identifiable, Queryable)]
13+
#[primary_key(invited_user_id, crate_id)]
14+
pub struct CrateOwnerInvitation {
15+
pub invited_user_id: i32,
16+
pub invited_by_user_id: i32,
17+
pub crate_id: i32,
18+
pub created_at: Timespec,
19+
}
20+
21+
#[derive(Insertable, Clone, Copy, Debug)]
22+
#[table_name = "crate_owner_invitations"]
23+
pub struct NewCrateOwnerInvitation {
24+
pub invited_user_id: i32,
25+
pub invited_by_user_id: i32,
26+
pub crate_id: i32,
27+
}
28+
29+
impl CrateOwnerInvitation {
30+
pub fn invited_by_username(&self, conn: &PgConnection) -> String {
31+
users::table
32+
.find(self.invited_by_user_id)
33+
.select(users::gh_login)
34+
.first(&*conn)
35+
.unwrap_or_else(|_| String::from("(unknown username)"))
36+
}
37+
38+
pub fn crate_name(&self, conn: &PgConnection) -> String {
39+
crates::table
40+
.find(self.crate_id)
41+
.select(crates::name)
42+
.first(&*conn)
43+
.unwrap_or_else(|_| String::from("(unknown crate name)"))
44+
}
45+
46+
pub fn encodable(self, conn: &PgConnection) -> EncodableCrateOwnerInvitation {
47+
EncodableCrateOwnerInvitation {
48+
invited_by_username: self.invited_by_username(conn),
49+
crate_name: self.crate_name(conn),
50+
crate_id: self.crate_id,
51+
created_at: ::encode_time(self.created_at),
52+
}
53+
}
54+
}
55+
56+
/// The serialization format for the `CrateOwnerInvitation` model.
57+
#[derive(Deserialize, Serialize, Debug)]
58+
pub struct EncodableCrateOwnerInvitation {
59+
pub invited_by_username: String,
60+
pub crate_name: String,
61+
pub crate_id: i32,
62+
pub created_at: String,
63+
}
64+
65+
/// Handles the `GET /me/crate_owner_invitations` route.
66+
pub fn list(req: &mut Request) -> CargoResult<Response> {
67+
let conn = &*req.db_conn()?;
68+
let user_id = req.user()?.id;
69+
70+
let invitations = crate_owner_invitations::table
71+
.filter(crate_owner_invitations::invited_user_id.eq(user_id))
72+
.load::<CrateOwnerInvitation>(&*conn)?
73+
.into_iter()
74+
.map(|i| i.encodable(conn))
75+
.collect();
76+
77+
#[derive(Serialize)]
78+
struct R {
79+
invitations: Vec<EncodableCrateOwnerInvitation>,
80+
}
81+
Ok(req.json(&R { invitations }))
82+
}

src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ pub mod badge;
7676
pub mod categories;
7777
pub mod category;
7878
pub mod config;
79+
pub mod crate_owner_invitation;
7980
pub mod db;
8081
pub mod dependency;
8182
pub mod dist;
@@ -183,6 +184,10 @@ pub fn middleware(app: Arc<App>) -> MiddlewareBuilder {
183184
api_router.get("/me/tokens", C(token::list));
184185
api_router.post("/me/tokens", C(token::new));
185186
api_router.delete("/me/tokens/:id", C(token::revoke));
187+
api_router.get(
188+
"/me/crate_owner_invitations",
189+
C(crate_owner_invitation::list),
190+
);
186191
api_router.get("/summary", C(krate::summary));
187192
let api_router = Arc::new(R404(api_router));
188193

src/tests/owners.rs

+76
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ use {CrateList, GoodCrate};
22

33
use cargo_registry::owner::EncodableOwner;
44
use cargo_registry::user::EncodablePublicUser;
5+
use cargo_registry::crate_owner_invitation::{EncodableCrateOwnerInvitation,
6+
NewCrateOwnerInvitation};
7+
use cargo_registry::schema::crate_owner_invitations;
58

69
use conduit::{Handler, Method};
10+
use diesel;
11+
use diesel::prelude::*;
712

813
#[derive(Deserialize)]
914
struct TeamResponse {
@@ -291,3 +296,74 @@ fn check_ownership_one_crate() {
291296
assert_eq!(json.users[0].kind, "user");
292297
assert_eq!(json.users[0].name, user.name);
293298
}
299+
300+
#[test]
301+
fn invitations_are_empty_by_default() {
302+
#[derive(Deserialize)]
303+
struct R {
304+
invitations: Vec<EncodableCrateOwnerInvitation>,
305+
}
306+
307+
let (_b, app, middle) = ::app();
308+
let mut req = ::req(
309+
app.clone(),
310+
Method::Get,
311+
"/api/v1/me/crate_owner_invitations",
312+
);
313+
314+
let user = {
315+
let conn = app.diesel_database.get().unwrap();
316+
::new_user("user_no_invites")
317+
.create_or_update(&conn)
318+
.unwrap()
319+
};
320+
::sign_in_as(&mut req, &user);
321+
322+
let mut response = ok_resp!(middle.call(&mut req));
323+
let json: R = ::json(&mut response);
324+
325+
assert_eq!(json.invitations.len(), 0);
326+
}
327+
328+
#[test]
329+
fn invitations_list() {
330+
#[derive(Deserialize)]
331+
struct R {
332+
invitations: Vec<EncodableCrateOwnerInvitation>,
333+
}
334+
335+
let (_b, app, middle) = ::app();
336+
let mut req = ::req(
337+
app.clone(),
338+
Method::Get,
339+
"/api/v1/me/crate_owner_invitations",
340+
);
341+
let (krate, user) = {
342+
let conn = app.diesel_database.get().unwrap();
343+
let owner = ::new_user("inviting_user").create_or_update(&conn).unwrap();
344+
let user = ::new_user("invited_user").create_or_update(&conn).unwrap();
345+
let krate = ::CrateBuilder::new("invited_crate", owner.id).expect_build(&conn);
346+
347+
// This should be replaced by an actual call to the route that `owner --add` hits once
348+
// that route creates an invitation.
349+
let invitation = NewCrateOwnerInvitation {
350+
invited_by_user_id: owner.id,
351+
invited_user_id: user.id,
352+
crate_id: krate.id,
353+
};
354+
diesel::insert(&invitation)
355+
.into(crate_owner_invitations::table)
356+
.execute(&*conn)
357+
.unwrap();
358+
(krate, user)
359+
};
360+
::sign_in_as(&mut req, &user);
361+
362+
let mut response = ok_resp!(middle.call(&mut req));
363+
let json: R = ::json(&mut response);
364+
365+
assert_eq!(json.invitations.len(), 1);
366+
assert_eq!(json.invitations[0].invited_by_username, "inviting_user");
367+
assert_eq!(json.invitations[0].crate_name, "invited_crate");
368+
assert_eq!(json.invitations[0].crate_id, krate.id);
369+
}

0 commit comments

Comments
 (0)