Skip to content

Commit be1429c

Browse files
Merge #1040
1040: Backend of deployable chunk 1 of owner invitations r=carols10cents This adds a table for storing pending crate owner invitations and a backend route for listing the current user's invitations. I'll probably merge this later today if no one has time to review so that @natboehm can build off of this tomorrow :)
2 parents c9c1477 + b098509 commit be1429c

File tree

9 files changed

+523
-311
lines changed

9 files changed

+523
-311
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE crate_owner_invitations;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CREATE TABLE crate_owner_invitations (
2+
invited_user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
3+
invited_by_user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
4+
crate_id INTEGER NOT NULL REFERENCES crates (id) ON DELETE CASCADE,
5+
created_at TIMESTAMP NOT NULL DEFAULT now(),
6+
PRIMARY KEY (invited_user_id, crate_id)
7+
);

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/schema.rs

+33
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,38 @@ table! {
138138
}
139139
}
140140

141+
table! {
142+
/// Representation of the `crate_owner_invitations` table.
143+
///
144+
/// (Automatically generated by Diesel.)
145+
crate_owner_invitations (invited_user_id, crate_id) {
146+
/// The `invited_user_id` column of the `crate_owner_invitations` table.
147+
///
148+
/// Its SQL type is `Int4`.
149+
///
150+
/// (Automatically generated by Diesel.)
151+
invited_user_id -> Int4,
152+
/// The `invited_by_user_id` column of the `crate_owner_invitations` table.
153+
///
154+
/// Its SQL type is `Int4`.
155+
///
156+
/// (Automatically generated by Diesel.)
157+
invited_by_user_id -> Int4,
158+
/// The `crate_id` column of the `crate_owner_invitations` table.
159+
///
160+
/// Its SQL type is `Int4`.
161+
///
162+
/// (Automatically generated by Diesel.)
163+
crate_id -> Int4,
164+
/// The `created_at` column of the `crate_owner_invitations` table.
165+
///
166+
/// Its SQL type is `Timestamp`.
167+
///
168+
/// (Automatically generated by Diesel.)
169+
created_at -> Timestamp,
170+
}
171+
}
172+
141173
table! {
142174
/// Representation of the `crate_owners` table.
143175
///
@@ -720,3 +752,4 @@ joinable!(version_downloads -> versions (version_id));
720752
joinable!(crate_owners -> teams (owner_id));
721753
joinable!(crate_owners -> users (owner_id));
722754
joinable!(readme_rendering -> versions (version_id));
755+
joinable!(crate_owner_invitations -> crates (crate_id));

src/tests/all.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ use cargo_registry::app::App;
3131
use cargo_registry::category::NewCategory;
3232
use cargo_registry::dependency::NewDependency;
3333
use cargo_registry::keyword::Keyword;
34-
use cargo_registry::krate::{NewCrate, CrateDownload};
35-
use cargo_registry::schema::dependencies;
34+
use cargo_registry::krate::{NewCrate, CrateDownload, EncodableCrate};
35+
use cargo_registry::schema::*;
3636
use cargo_registry::upload as u;
3737
use cargo_registry::user::NewUser;
3838
use cargo_registry::owner::{CrateOwner, NewTeam, Team};
@@ -43,7 +43,6 @@ use conduit::{Request, Method};
4343
use conduit_test::MockRequest;
4444
use diesel::prelude::*;
4545
use diesel::pg::upsert::*;
46-
use cargo_registry::schema::*;
4746

4847
macro_rules! t {
4948
($e:expr) => (
@@ -89,13 +88,35 @@ mod category;
8988
mod git;
9089
mod keyword;
9190
mod krate;
91+
mod owners;
9292
mod record;
9393
mod schema_details;
9494
mod team;
9595
mod token;
9696
mod user;
9797
mod version;
9898

99+
#[derive(Deserialize)]
100+
struct GoodCrate {
101+
#[serde(rename = "crate")]
102+
krate: EncodableCrate,
103+
warnings: Warnings,
104+
}
105+
#[derive(Deserialize)]
106+
struct CrateList {
107+
crates: Vec<EncodableCrate>,
108+
meta: CrateMeta,
109+
}
110+
#[derive(Deserialize)]
111+
struct Warnings {
112+
invalid_categories: Vec<String>,
113+
invalid_badges: Vec<String>,
114+
}
115+
#[derive(Deserialize)]
116+
struct CrateMeta {
117+
total: i32,
118+
}
119+
99120
fn app() -> (record::Bomb, Arc<App>, conduit_middleware::MiddlewareBuilder) {
100121
dotenv::dotenv().ok();
101122
git::init();

0 commit comments

Comments
 (0)