Skip to content

Commit 9fd121d

Browse files
committed
Auto merge of #3403 - Turbo87:invitation-users, r=pietroalbini
GET /me/crate_owner_invitations: Add `users` list This will allow us to display avatars on the frontend for the `inviter` part of the invitations.
2 parents ed1457d + ed859fb commit 9fd121d

File tree

4 files changed

+98
-37
lines changed

4 files changed

+98
-37
lines changed

src/controllers/crate_owner_invitation.rs

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,82 @@
11
use super::frontend_prelude::*;
22

3-
use crate::models::{CrateOwner, CrateOwnerInvitation, OwnerKind};
4-
use crate::schema::{crate_owner_invitations, crate_owners};
5-
use crate::views::{EncodableCrateOwnerInvitation, InvitationResponse};
3+
use crate::models::{CrateOwner, CrateOwnerInvitation, OwnerKind, User};
4+
use crate::schema::{crate_owner_invitations, crate_owners, crates, users};
5+
use crate::views::{EncodableCrateOwnerInvitation, EncodablePublicUser, InvitationResponse};
6+
use diesel::dsl::any;
7+
use std::collections::HashMap;
68

79
/// Handles the `GET /me/crate_owner_invitations` route.
810
pub fn list(req: &mut dyn RequestExt) -> EndpointResult {
9-
let user_id = req.authenticate()?.user_id();
10-
let conn = &*req.db_read_only()?;
11+
// Ensure that the user is authenticated
12+
let user = req.authenticate()?.user();
1113

14+
// Load all pending invitations for the user
15+
let conn = &*req.db_read_only()?;
1216
let crate_owner_invitations: Vec<CrateOwnerInvitation> = crate_owner_invitations::table
13-
.filter(crate_owner_invitations::invited_user_id.eq(user_id))
17+
.filter(crate_owner_invitations::invited_user_id.eq(user.id))
1418
.load(&*conn)?;
19+
20+
// Make a list of all related users
21+
let user_ids: Vec<_> = crate_owner_invitations
22+
.iter()
23+
.map(|invitation| invitation.invited_by_user_id)
24+
.collect();
25+
26+
// Load all related users
27+
let users: Vec<User> = users::table
28+
.filter(users::id.eq(any(user_ids)))
29+
.load(conn)?;
30+
31+
let users: HashMap<i32, User> = users.into_iter().map(|user| (user.id, user)).collect();
32+
33+
// Make a list of all related crates
34+
let crate_ids: Vec<_> = crate_owner_invitations
35+
.iter()
36+
.map(|invitation| invitation.crate_id)
37+
.collect();
38+
39+
// Load all related crates
40+
let crates: Vec<_> = crates::table
41+
.select((crates::id, crates::name))
42+
.filter(crates::id.eq(any(crate_ids)))
43+
.load(conn)?;
44+
45+
let crates: HashMap<i32, String> = crates.into_iter().collect();
46+
47+
// Turn `CrateOwnerInvitation` list into `EncodableCrateOwnerInvitation` list
1548
let crate_owner_invitations = crate_owner_invitations
1649
.into_iter()
17-
.map(|i| EncodableCrateOwnerInvitation::from(i, conn))
50+
.map(|invitation| {
51+
let inviter_id = invitation.invited_by_user_id;
52+
let inviter_name = users
53+
.get(&inviter_id)
54+
.map(|user| user.gh_login.clone())
55+
.unwrap_or_default();
56+
57+
let crate_name = crates
58+
.get(&invitation.crate_id)
59+
.map(|name| name.clone())
60+
.unwrap_or_else(|| String::from("(unknown crate name)"));
61+
62+
EncodableCrateOwnerInvitation::from(invitation, inviter_name, crate_name)
63+
})
64+
.collect();
65+
66+
// Turn `User` list into `EncodablePublicUser` list
67+
let users = users
68+
.into_iter()
69+
.map(|(_, user)| EncodablePublicUser::from(user))
1870
.collect();
1971

2072
#[derive(Serialize)]
2173
struct R {
2274
crate_owner_invitations: Vec<EncodableCrateOwnerInvitation>,
75+
users: Vec<EncodablePublicUser>,
2376
}
2477
Ok(req.json(&R {
2578
crate_owner_invitations,
79+
users,
2680
}))
2781
}
2882

src/models/crate_owner_invitation.rs

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use chrono::NaiveDateTime;
2-
use diesel::prelude::*;
32

4-
use crate::schema::{crate_owner_invitations, crates, users};
3+
use crate::schema::crate_owner_invitations;
54

65
/// The model representing a row in the `crate_owner_invitations` database table.
76
#[derive(Clone, Debug, PartialEq, Eq, Identifiable, Queryable)]
@@ -22,21 +21,3 @@ pub struct NewCrateOwnerInvitation {
2221
pub invited_by_user_id: i32,
2322
pub crate_id: i32,
2423
}
25-
26-
impl CrateOwnerInvitation {
27-
pub fn invited_by_username(&self, conn: &PgConnection) -> String {
28-
users::table
29-
.find(self.invited_by_user_id)
30-
.select(users::gh_login)
31-
.first(&*conn)
32-
.unwrap_or_else(|_| String::from("(unknown username)"))
33-
}
34-
35-
pub fn crate_name(&self, conn: &PgConnection) -> String {
36-
crates::table
37-
.find(self.crate_id)
38-
.select(crates::name)
39-
.first(&*conn)
40-
.unwrap_or_else(|_| String::from("(unknown crate name)"))
41-
}
42-
}

src/tests/owners.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -330,14 +330,31 @@ fn invitations_list() {
330330
let user = app.db_new_user("invited_user");
331331
token.add_user_owner("invited_crate", "invited_user");
332332

333-
let json = user.list_invitations();
334-
assert_eq!(json.crate_owner_invitations.len(), 1);
333+
let response = user.get::<()>("/api/v1/me/crate_owner_invitations");
334+
assert_eq!(response.status(), StatusCode::OK);
335+
336+
let json = response.json();
335337
assert_eq!(
336-
json.crate_owner_invitations[0].invited_by_username,
337-
owner.gh_login
338+
json,
339+
json!({
340+
"crate_owner_invitations": [{
341+
"crate_id": krate.id,
342+
"crate_name": "invited_crate",
343+
// this value changes with each test run so we can't use a fixed value here
344+
"created_at": &json["crate_owner_invitations"][0]["created_at"],
345+
"invited_by_username": owner.gh_login,
346+
"invitee_id": user.as_model().id,
347+
"inviter_id": owner.id,
348+
}],
349+
"users": [{
350+
"avatar": null,
351+
"id": owner.id,
352+
"login": "foo",
353+
"name": null,
354+
"url": "https://github.com/foo",
355+
}],
356+
})
338357
);
339-
assert_eq!(json.crate_owner_invitations[0].crate_name, "invited_crate");
340-
assert_eq!(json.crate_owner_invitations[0].crate_id, krate.id);
341358
}
342359

343360
/* Given a user inviting a different user to be a crate

src/views.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use chrono::NaiveDateTime;
2-
use diesel::PgConnection;
32
use std::collections::HashMap;
43
use url::Url;
54

@@ -76,6 +75,8 @@ pub struct EncodableCategoryWithSubcategories {
7675
/// The serialization format for the `CrateOwnerInvitation` model.
7776
#[derive(Deserialize, Serialize, Debug)]
7877
pub struct EncodableCrateOwnerInvitation {
78+
pub invitee_id: i32,
79+
pub inviter_id: i32,
7980
pub invited_by_username: String,
8081
pub crate_name: String,
8182
pub crate_id: i32,
@@ -84,10 +85,16 @@ pub struct EncodableCrateOwnerInvitation {
8485
}
8586

8687
impl EncodableCrateOwnerInvitation {
87-
pub fn from(invitation: CrateOwnerInvitation, conn: &PgConnection) -> Self {
88+
pub fn from(
89+
invitation: CrateOwnerInvitation,
90+
inviter_name: String,
91+
crate_name: String,
92+
) -> Self {
8893
Self {
89-
invited_by_username: invitation.invited_by_username(conn),
90-
crate_name: invitation.crate_name(conn),
94+
invitee_id: invitation.invited_user_id,
95+
inviter_id: invitation.invited_by_user_id,
96+
invited_by_username: inviter_name,
97+
crate_name,
9198
crate_id: invitation.crate_id,
9299
created_at: invitation.created_at,
93100
}
@@ -791,6 +798,8 @@ mod tests {
791798
#[test]
792799
fn crate_owner_invitation_serializes_to_rfc3339() {
793800
let inv = EncodableCrateOwnerInvitation {
801+
invitee_id: 1,
802+
inviter_id: 2,
794803
invited_by_username: "".to_string(),
795804
crate_name: "".to_string(),
796805
crate_id: 123,

0 commit comments

Comments
 (0)