Skip to content

Commit db3e38d

Browse files
Merge #1085
1085: Decline owner invites r=carols10cents This implements deployable chunk 3: declining invitations of issue #924. The api route for declining an invite is the same as for accepting an invite. To know that an invite is being declined instead of accepted, the `accepted` field passed to the backend is set to `false`. If declined, the invite should be replaced with a message to the user confirming that they declined the invitation for the crate.
2 parents 4a7e7a7 + 3d7a851 commit db3e38d

File tree

4 files changed

+148
-10
lines changed

4 files changed

+148
-10
lines changed

app/components/pending-owner-invite-row.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import Ember from 'ember';
22

33
export default Ember.Component.extend({
4-
isSuccess: false,
4+
isAccepted: false,
5+
isDeclined: false,
56
isError: false,
67
inviteError: 'default error message',
78

@@ -10,7 +11,7 @@ export default Ember.Component.extend({
1011
invite.set('accepted', true);
1112
invite.save()
1213
.then(() => {
13-
this.set('isSuccess', true);
14+
this.set('isAccepted', true);
1415
})
1516
.catch((error) => {
1617
this.set('isError', true);
@@ -22,6 +23,23 @@ export default Ember.Component.extend({
2223
this.set('inviteError', 'Error in accepting invite');
2324
}
2425
});
26+
},
27+
declineInvitation(invite) {
28+
invite.set('accepted', false);
29+
invite.save()
30+
.then(() => {
31+
this.set('isDeclined', true);
32+
})
33+
.catch((error) => {
34+
this.set('isError', true);
35+
if (error.payload) {
36+
this.set('inviteError',
37+
`Error in declining invite: ${error.payload.errors[0].detail}`
38+
);
39+
} else {
40+
this.set('inviteError', 'Error in declining invite');
41+
}
42+
});
2543
}
2644
}
2745
});

app/templates/components/pending-owner-invite-row.hbs

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
<div class='row'>
2-
{{#if isSuccess }}
2+
{{#if isAccepted }}
33
<p>Success! You've been added as an owner of crate
44
{{#link-to 'crate' invite.crate_name}}{{invite.crate_name}}{{/link-to}}.
5-
</p>
5+
</p>
6+
{{else if isDeclined}}
7+
<p>Declined. You have not been added as an owner of crate
8+
{{#link-to 'crate' invite.crate_name}}{{invite.crate_name}}{{/link-to}}.
9+
</p>
610
{{else}}
711
<div class='info'>
812
<div class='name'>
@@ -24,7 +28,7 @@
2428
</div>
2529
<div class='actions'>
2630
<button class='small yellow-button' {{action 'acceptInvitation' invite}}>Accept</button>
27-
<button class='small yellow-button'>Deny</button>
31+
<button class='small yellow-button' {{action 'declineInvitation' invite}}>Decline</button>
2832
</div>
2933
{{#if isError}}
3034
<div class='label'>

src/crate_owner_invitation.rs

+23-5
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,7 @@ pub fn handle_invite(req: &mut Request) -> CargoResult<Response> {
112112
if crate_invite.accepted {
113113
accept_invite(req, conn, crate_invite)
114114
} else {
115-
#[derive(Serialize)]
116-
struct R {
117-
crate_owner_invitation: InvitationResponse,
118-
}
119-
Ok(req.json(&R { crate_owner_invitation: crate_invite }))
115+
decline_invite(req, conn, crate_invite)
120116
}
121117
}
122118

@@ -154,3 +150,25 @@ fn accept_invite(
154150
Ok(req.json(&R { crate_owner_invitation: crate_invite }))
155151
})
156152
}
153+
154+
fn decline_invite(
155+
req: &mut Request,
156+
conn: &PgConnection,
157+
crate_invite: InvitationResponse,
158+
) -> CargoResult<Response> {
159+
use diesel::delete;
160+
let user_id = req.user()?.id;
161+
162+
delete(
163+
crate_owner_invitations::table
164+
.filter(crate_owner_invitations::crate_id.eq(crate_invite.crate_id))
165+
.filter(crate_owner_invitations::invited_user_id.eq(user_id)),
166+
).execute(conn)?;
167+
168+
#[derive(Serialize)]
169+
struct R {
170+
crate_owner_invitation: InvitationResponse,
171+
}
172+
173+
Ok(req.json(&R { crate_owner_invitation: crate_invite }))
174+
}

src/tests/owners.rs

+98
Original file line numberDiff line numberDiff line change
@@ -467,3 +467,101 @@ fn test_accept_invitation() {
467467
let json: Q = ::json(&mut response);
468468
assert_eq!(json.users.len(), 2);
469469
}
470+
471+
472+
/* Given a user inviting a different user to be a crate
473+
owner, check that the user invited can decline their
474+
invitation and the invitation will be deleted from
475+
the invitations table.
476+
*/
477+
#[test]
478+
fn test_decline_invitation() {
479+
#[derive(Deserialize)]
480+
struct R {
481+
crate_owner_invitations: Vec<EncodableCrateOwnerInvitation>,
482+
}
483+
484+
#[derive(Deserialize)]
485+
struct Q {
486+
users: Vec<EncodablePublicUser>,
487+
}
488+
489+
#[derive(Deserialize)]
490+
struct T {
491+
crate_owner_invitation: InvitationResponse,
492+
}
493+
494+
let (_b, app, middle) = ::app();
495+
let mut req = ::req(
496+
app.clone(),
497+
Method::Get,
498+
"/api/v1/me/crate_owner_invitations",
499+
);
500+
let (krate, user) = {
501+
let conn = app.diesel_database.get().unwrap();
502+
let owner = ::new_user("inviting_user").create_or_update(&conn).unwrap();
503+
let user = ::new_user("invited_user").create_or_update(&conn).unwrap();
504+
let krate = ::CrateBuilder::new("invited_crate", owner.id).expect_build(&conn);
505+
506+
// This should be replaced by an actual call to the route that `owner --add` hits once
507+
// that route creates an invitation.
508+
let invitation = NewCrateOwnerInvitation {
509+
invited_by_user_id: owner.id,
510+
invited_user_id: user.id,
511+
crate_id: krate.id,
512+
};
513+
diesel::insert(&invitation)
514+
.into(crate_owner_invitations::table)
515+
.execute(&*conn)
516+
.unwrap();
517+
(krate, user)
518+
};
519+
::sign_in_as(&mut req, &user);
520+
521+
let body = json!({
522+
"crate_owner_invite": {
523+
"invited_by_username": "inviting_user",
524+
"crate_name": "invited_crate",
525+
"crate_id": krate.id,
526+
"created_at": "",
527+
"accepted": false
528+
}
529+
});
530+
531+
// first check that response from deleting
532+
// crate_owner_invitation is okay
533+
let mut response = ok_resp!(
534+
middle.call(
535+
req.with_path(&format!("api/v1/me/crate_owner_invitations/{}", krate.id))
536+
.with_method(Method::Put)
537+
.with_body(body.to_string().as_bytes()),
538+
)
539+
);
540+
541+
let json: T = ::json(&mut response);
542+
assert_eq!(json.crate_owner_invitation.accepted, false);
543+
assert_eq!(json.crate_owner_invitation.crate_id, krate.id);
544+
545+
546+
// then check to make sure that decline_invite did what it
547+
// was supposed to
548+
// crate_owner_invitation was deleted
549+
let mut response = ok_resp!(
550+
middle.call(
551+
req.with_path("api/v1/me/crate_owner_invitations")
552+
.with_method(Method::Get)
553+
)
554+
);
555+
let json: R = ::json(&mut response);
556+
assert_eq!(json.crate_owner_invitations.len(), 0);
557+
558+
// new crate owner was not inserted
559+
let mut response = ok_resp!(
560+
middle.call(
561+
req.with_path("/api/v1/crates/invited_crate/owners")
562+
.with_method(Method::Get)
563+
)
564+
);
565+
let json: Q = ::json(&mut response);
566+
assert_eq!(json.users.len(), 1);
567+
}

0 commit comments

Comments
 (0)