Skip to content

Decline owner invites #1085

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Sep 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8701a31
created api route for user to accept invitation
natboehm Sep 18, 2017
a655676
test for accepting owner invites route, currently failing
natboehm Sep 18, 2017
3d3bdec
test for accepting crate owner invitation, wrap delete and insert sta…
natboehm Sep 19, 2017
43909ba
accept button functioning on frontend
natboehm Sep 19, 2017
aaba3c9
add filter to delete statment such that delete filters by crate_id an…
natboehm Sep 20, 2017
d7c0031
page should reload on accepting an invite
natboehm Sep 20, 2017
1f74f42
cargo fmt
natboehm Sep 20, 2017
3d4f0fd
js linting error
natboehm Sep 20, 2017
eceb03f
change route for accept to use model.save on the frontend
natboehm Sep 22, 2017
5bdcabc
switch controller to component for pending invites, generate success …
natboehm Sep 22, 2017
6b3d0df
fix test for accepting owner invite
natboehm Sep 22, 2017
d50416b
npm linting errors
natboehm Sep 22, 2017
4b4aa3b
clippy
natboehm Sep 22, 2017
a8a6c96
delete whitespace
natboehm Sep 22, 2017
440bee4
Add width to make columns line up on invite page
natboehm Sep 25, 2017
b64c808
delete http-data file formerly needed for test to pass
natboehm Sep 25, 2017
9cf6974
add route and function for declining an owner invite
natboehm Sep 20, 2017
4485f35
add filter statement for user_id - don't want to delete all occurranc…
natboehm Sep 20, 2017
8b491ad
add test for declining invitations
natboehm Sep 20, 2017
0f1f2b1
http data needed for test
natboehm Sep 20, 2017
69a682d
comments i forgot to save
natboehm Sep 20, 2017
4b41121
frontend functionality for declining invites
natboehm Sep 20, 2017
52ee915
i don't know how this got back in here
natboehm Sep 22, 2017
2f965d2
add decline function for /me/crate_invitations/:crate_id route
natboehm Sep 22, 2017
0f771e4
updated test for declining invites
natboehm Sep 22, 2017
a252eb5
add frontend functionality for declining invitations
natboehm Sep 22, 2017
dcfe7c5
cargo fmt
natboehm Sep 22, 2017
5f15a6a
remove http-data file formerly needed for tests
natboehm Sep 25, 2017
3d7a851
Change button from deny to decline
natboehm Sep 25, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions app/components/pending-owner-invite-row.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Ember from 'ember';

export default Ember.Component.extend({
isAccepted: false,
isDeclined: false,
isError: false,
inviteError: 'default error message',

actions: {
acceptInvitation(invite) {
invite.set('accepted', true);
invite.save()
.then(() => {
this.set('isAccepted', true);
})
.catch((error) => {
this.set('isError', true);
if (error.payload) {
this.set('inviteError',
`Error in accepting invite: ${error.payload.errors[0].detail}`
);
} else {
this.set('inviteError', 'Error in accepting invite');
}
});
},
declineInvitation(invite) {
invite.set('accepted', false);
invite.save()
.then(() => {
this.set('isDeclined', true);
})
.catch((error) => {
this.set('isError', true);
if (error.payload) {
this.set('inviteError',
`Error in declining invite: ${error.payload.errors[0].detail}`
);
} else {
this.set('inviteError', 'Error in declining invite');
}
});
}
}
});
3 changes: 2 additions & 1 deletion app/models/crate-owner-invite.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export default DS.Model.extend({
invited_by_username: DS.attr('string'),
crate_name: DS.attr('string'),
crate_id: DS.attr('number'),
created_at: DS.attr('date')
created_at: DS.attr('date'),
accepted: DS.attr('boolean', { defaultValue: false })
});
3 changes: 3 additions & 0 deletions app/serializers/crate-owner-invite.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ export default DS.RESTSerializer.extend({
primaryKey: 'crate_id',
modelNameFromPayloadKey() {
return 'crate-owner-invite';
},
payloadKeyFromModelName() {
return 'crate_owner_invite';
}
});
8 changes: 8 additions & 0 deletions app/styles/me.scss
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,14 @@
@include justify-content(space-between);

.date { @include flex-grow(2); text-align: right; }
.label {
.small-text {
font-size: 90%;
}
}
.name {
width: 200px;
}
}
}

Expand Down
40 changes: 40 additions & 0 deletions app/templates/components/pending-owner-invite-row.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<div class='row'>
{{#if isAccepted }}
<p>Success! You've been added as an owner of crate
{{#link-to 'crate' invite.crate_name}}{{invite.crate_name}}{{/link-to}}.
</p>
{{else if isDeclined}}
<p>Declined. You have not been added as an owner of crate
{{#link-to 'crate' invite.crate_name}}{{invite.crate_name}}{{/link-to}}.
</p>
{{else}}
<div class='info'>
<div class='name'>
<h3>
{{#link-to 'crate' invite.crate_name}}
{{invite.crate_name}}
{{/link-to}}
</h3>
</div>
<div class='invite'>
<p>Invited by:
{{#link-to 'user' invite.invited_by_username}}
{{invite.invited_by_username}}
{{/link-to}}
</p>
</div>
<div class='sent'>
<span class='small'>{{moment-from-now invite.created_at}}</span>
</div>
<div class='actions'>
<button class='small yellow-button' {{action 'acceptInvitation' invite}}>Accept</button>
<button class='small yellow-button' {{action 'declineInvitation' invite}}>Decline</button>
</div>
{{#if isError}}
<div class='label'>
<p class='small-text'>{{inviteError}}</p>
</div>
{{/if}}
</div>
{{/if}}
</div>
28 changes: 3 additions & 25 deletions app/templates/me/pending-invites.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,9 @@
<div id='my-invites'>
<div class='white-rows'>
{{#each model as |invite|}}
<div class='row'>
<div class='info'>
<div class='name'>
<h3>
{{#link-to 'crate' invite.crate_name}}
{{invite.crate_name}}
{{/link-to}}
</h3>
</div>
<div class='invite'>
<p>Invited by:
{{#link-to 'user' invite.invited_by_username}}
{{invite.invited_by_username}}
{{/link-to}}
</p>
</div>
<div class='sent'>
<span class='small'>{{moment-from-now invite.created_at}}</span>
</div>
<div class='actions'>
<button class='small yellow-button'>Accept</button>
<button class='small yellow-button'>Deny</button>
</div>
</div>
</div>
{{pending-owner-invite-row invite=invite}}
{{else}}
<p>You don't seem to have any pending invitations.</p>
{{/each}}
</div>
</div>
96 changes: 94 additions & 2 deletions src/crate_owner_invitation.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use conduit::{Request, Response};
use diesel::prelude::*;
use time::Timespec;
use serde_json;

use db::RequestTransaction;
use schema::{crate_owner_invitations, users, crates};
use schema::{crate_owner_invitations, users, crates, crate_owners};
use user::RequestUser;
use util::errors::CargoResult;
use util::errors::{CargoResult, human};
use util::RequestUtils;
use owner::{CrateOwner, OwnerKind};

/// The model representing a row in the `crate_owner_invitations` database table.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Identifiable, Queryable)]
Expand Down Expand Up @@ -80,3 +82,93 @@ pub fn list(req: &mut Request) -> CargoResult<Response> {
}
Ok(req.json(&R { crate_owner_invitations }))
}

#[derive(Deserialize)]
struct OwnerInvitation {
crate_owner_invite: InvitationResponse,
}

#[derive(Deserialize, Serialize, Debug, Copy, Clone)]
pub struct InvitationResponse {
pub crate_id: i32,
pub accepted: bool,
}

/// Handles the `PUT /me/crate_owner_invitations/:crate_id` route.
pub fn handle_invite(req: &mut Request) -> CargoResult<Response> {

let conn = &*req.db_conn()?;


let mut body = String::new();
req.body().read_to_string(&mut body)?;

let crate_invite: OwnerInvitation = serde_json::from_str(&body).map_err(|_| {
human("invalid json request")
})?;

let crate_invite = crate_invite.crate_owner_invite;

if crate_invite.accepted {
accept_invite(req, conn, crate_invite)
} else {
decline_invite(req, conn, crate_invite)
}
}

fn accept_invite(
req: &mut Request,
conn: &PgConnection,
crate_invite: InvitationResponse,
) -> CargoResult<Response> {
let user_id = req.user()?.id;
use diesel::{insert, delete};
let pending_crate_owner = crate_owner_invitations::table
.filter(crate_owner_invitations::crate_id.eq(crate_invite.crate_id))
.filter(crate_owner_invitations::invited_user_id.eq(user_id))
.first::<CrateOwnerInvitation>(&*conn)?;

let owner = CrateOwner {
crate_id: crate_invite.crate_id,
owner_id: user_id,
created_by: pending_crate_owner.invited_by_user_id,
owner_kind: OwnerKind::User as i32,
};

conn.transaction(|| {
insert(&owner).into(crate_owners::table).execute(conn)?;
delete(
crate_owner_invitations::table
.filter(crate_owner_invitations::crate_id.eq(crate_invite.crate_id))
.filter(crate_owner_invitations::invited_user_id.eq(user_id)),
).execute(conn)?;

#[derive(Serialize)]
struct R {
crate_owner_invitation: InvitationResponse,
}
Ok(req.json(&R { crate_owner_invitation: crate_invite }))
})
}

fn decline_invite(
req: &mut Request,
conn: &PgConnection,
crate_invite: InvitationResponse,
) -> CargoResult<Response> {
use diesel::delete;
let user_id = req.user()?.id;

delete(
crate_owner_invitations::table
.filter(crate_owner_invitations::crate_id.eq(crate_invite.crate_id))
.filter(crate_owner_invitations::invited_user_id.eq(user_id)),
).execute(conn)?;

#[derive(Serialize)]
struct R {
crate_owner_invitation: InvitationResponse,
}

Ok(req.json(&R { crate_owner_invitation: crate_invite }))
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ pub fn middleware(app: Arc<App>) -> MiddlewareBuilder {
"/me/crate_owner_invitations",
C(crate_owner_invitation::list),
);
api_router.put(
"/me/crate_owner_invitations/:crate_id",
C(crate_owner_invitation::handle_invite),
);
api_router.get("/summary", C(krate::summary));
api_router.put("/confirm/:email_token", C(user::confirm_user_email));
api_router.put("/users/:user_id/resend", C(user::regenerate_token_and_send));
Expand Down
1 change: 1 addition & 0 deletions src/tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ extern crate dotenv;
extern crate git2;
extern crate semver;
extern crate serde;
#[macro_use]
extern crate serde_json;
extern crate time;
extern crate url;
Expand Down
Loading