Skip to content

Commit 3e3bd3d

Browse files
committed
Auto merge of #1621 - integer32llc:record-verified-email, r=jtgeibel
Record verified email, if present, of the publisher in the version record Connects to #1620. We can start recording emails if we have them, even though we're not requiring verified emails yet. This builds on top of #1561.
2 parents 1aa4b1d + 8935dcd commit 3e3bd3d

File tree

18 files changed

+346
-57
lines changed

18 files changed

+346
-57
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TABLE versions DROP CONSTRAINT fk_versions_published_by;
2+
3+
ALTER TABLE versions DROP COLUMN published_by;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ALTER TABLE versions
2+
ADD COLUMN published_by integer;
3+
4+
ALTER TABLE versions
5+
ADD CONSTRAINT "fk_versions_published_by"
6+
FOREIGN KEY (published_by)
7+
REFERENCES users(id);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE versions_published_by;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CREATE TABLE versions_published_by (
2+
version_id INTEGER NOT NULL PRIMARY KEY REFERENCES versions(id) ON DELETE CASCADE,
3+
email VARCHAR NOT NULL
4+
);

src/bin/update-downloads.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,12 @@ mod test {
146146
None,
147147
None,
148148
0,
149+
user_id,
149150
)
150151
.unwrap();
151-
let version = version.save(conn, &[]).unwrap();
152+
let version = version
153+
.save(conn, &[], Some("someone@example.com".into()))
154+
.unwrap();
152155
(krate, version)
153156
}
154157

src/controllers/krate/metadata.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
77
use crate::controllers::prelude::*;
88
use crate::models::{
9-
Category, Crate, CrateCategory, CrateDownload, CrateKeyword, CrateVersions, Keyword, Version,
9+
Category, Crate, CrateCategory, CrateDownload, CrateKeyword, CrateVersions, Keyword, User,
10+
Version,
1011
};
1112
use crate::schema::*;
1213
use crate::views::{
@@ -106,9 +107,13 @@ pub fn show(req: &mut dyn Request) -> CargoResult<Response> {
106107
let conn = req.db_conn()?;
107108
let krate = Crate::by_name(name).first::<Crate>(&*conn)?;
108109

109-
let mut versions = krate.all_versions().load::<Version>(&*conn)?;
110-
versions.sort_by(|a, b| b.num.cmp(&a.num));
111-
let ids = versions.iter().map(|v| v.id).collect();
110+
let mut versions_and_publishers: Vec<(Version, Option<User>)> = krate
111+
.all_versions()
112+
.left_outer_join(users::table)
113+
.select((versions::all_columns, users::all_columns.nullable()))
114+
.load(&*conn)?;
115+
versions_and_publishers.sort_by(|a, b| b.0.num.cmp(&a.0.num));
116+
let ids = versions_and_publishers.iter().map(|v| v.0.id).collect();
112117

113118
let kws = CrateKeyword::belonging_to(&krate)
114119
.inner_join(keywords::table)
@@ -146,9 +151,9 @@ pub fn show(req: &mut dyn Request) -> CargoResult<Response> {
146151
false,
147152
recent_downloads,
148153
),
149-
versions: versions
154+
versions: versions_and_publishers
150155
.into_iter()
151-
.map(|v| v.encodable(&krate.name))
156+
.map(|(v, pb)| v.encodable(&krate.name, pb))
152157
.collect(),
153158
keywords: kws.into_iter().map(|k| k.encodable()).collect(),
154159
categories: cats.into_iter().map(|k| k.encodable()).collect(),
@@ -185,11 +190,15 @@ pub fn versions(req: &mut dyn Request) -> CargoResult<Response> {
185190
let crate_name = &req.params()["crate_id"];
186191
let conn = req.db_conn()?;
187192
let krate = Crate::by_name(crate_name).first::<Crate>(&*conn)?;
188-
let mut versions = krate.all_versions().load::<Version>(&*conn)?;
189-
versions.sort_by(|a, b| b.num.cmp(&a.num));
190-
let versions = versions
193+
let mut versions_and_publishers: Vec<(Version, Option<User>)> = krate
194+
.all_versions()
195+
.left_outer_join(users::table)
196+
.select((versions::all_columns, users::all_columns.nullable()))
197+
.load(&*conn)?;
198+
versions_and_publishers.sort_by(|a, b| b.0.num.cmp(&a.0.num));
199+
let versions = versions_and_publishers
191200
.into_iter()
192-
.map(|v| v.encodable(crate_name))
201+
.map(|(v, pb)| v.encodable(crate_name, pb))
193202
.collect();
194203

195204
#[derive(Serialize)]
@@ -218,10 +227,15 @@ pub fn reverse_dependencies(req: &mut dyn Request) -> CargoResult<Response> {
218227
let versions = versions::table
219228
.filter(versions::id.eq(any(version_ids)))
220229
.inner_join(crates::table)
221-
.select((versions::all_columns, crates::name))
222-
.load::<(Version, String)>(&*conn)?
230+
.left_outer_join(users::table)
231+
.select((
232+
versions::all_columns,
233+
crates::name,
234+
users::all_columns.nullable(),
235+
))
236+
.load::<(Version, String, Option<User>)>(&*conn)?
223237
.into_iter()
224-
.map(|(version, krate_name)| version.encodable(&krate_name))
238+
.map(|(version, krate_name, published_by)| version.encodable(&krate_name, published_by))
225239
.collect();
226240

227241
#[derive(Serialize)]

src/controllers/krate/publish.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ pub fn publish(req: &mut dyn Request) -> CargoResult<Response> {
6666
let conn = app.diesel_database.get()?;
6767

6868
let mut other_warnings = vec![];
69-
if !user.has_verified_email(&conn)? {
69+
let verified_email_address = user.verified_email(&conn)?;
70+
if verified_email_address.is_none() {
7071
other_warnings.push(String::from(
7172
"You do not currently have a verified email address associated with your crates.io \
7273
account. Starting 2019-02-28, a verified email will be required to publish crates. \
@@ -146,8 +147,9 @@ pub fn publish(req: &mut dyn Request) -> CargoResult<Response> {
146147
// Downcast is okay because the file length must be less than the max upload size
147148
// to get here, and max upload sizes are way less than i32 max
148149
file_length as i32,
150+
user.id,
149151
)?
150-
.save(&conn, &new_crate.authors)?;
152+
.save(&conn, &new_crate.authors, verified_email_address)?;
151153

152154
// Link this new version to all dependencies
153155
let git_deps = dependency::add_dependencies(&conn, &new_crate.deps, version.id)?;

src/controllers/user/me.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,16 @@ pub fn updates(req: &mut dyn Request) -> CargoResult<Response> {
5656
let followed_crates = Follow::belonging_to(user).select(follows::crate_id);
5757
let data = versions::table
5858
.inner_join(crates::table)
59+
.left_outer_join(users::table)
5960
.filter(crates::id.eq(any(followed_crates)))
6061
.order(versions::created_at.desc())
61-
.select((versions::all_columns, crates::name))
62+
.select((
63+
versions::all_columns,
64+
crates::name,
65+
users::all_columns.nullable(),
66+
))
6267
.paginate(limit, offset)
63-
.load::<((Version, String), i64)>(&*conn)?;
68+
.load::<((Version, String, Option<User>), i64)>(&*conn)?;
6469

6570
let more = data
6671
.get(0)
@@ -69,7 +74,9 @@ pub fn updates(req: &mut dyn Request) -> CargoResult<Response> {
6974

7075
let versions = data
7176
.into_iter()
72-
.map(|((version, crate_name), _)| version.encodable(&crate_name))
77+
.map(|((version, crate_name, published_by), _)| {
78+
version.encodable(&crate_name, published_by)
79+
})
7380
.collect();
7481

7582
#[derive(Serialize)]

src/controllers/version/deprecated.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
88
use crate::controllers::prelude::*;
99

10-
use crate::models::{Crate, Version};
10+
use crate::models::{Crate, User, Version};
1111
use crate::schema::*;
1212
use crate::views::EncodableVersion;
1313

@@ -24,11 +24,16 @@ pub fn index(req: &mut dyn Request) -> CargoResult<Response> {
2424

2525
let versions = versions::table
2626
.inner_join(crates::table)
27-
.select((versions::all_columns, crates::name))
27+
.left_outer_join(users::table)
28+
.select((
29+
versions::all_columns,
30+
crates::name,
31+
users::all_columns.nullable(),
32+
))
2833
.filter(versions::id.eq(any(ids)))
29-
.load::<(Version, String)>(&*conn)?
34+
.load::<(Version, String, Option<User>)>(&*conn)?
3035
.into_iter()
31-
.map(|(version, crate_name)| version.encodable(&crate_name))
36+
.map(|(version, crate_name, published_by)| version.encodable(&crate_name, published_by))
3237
.collect();
3338

3439
#[derive(Serialize)]
@@ -45,17 +50,22 @@ pub fn show_by_id(req: &mut dyn Request) -> CargoResult<Response> {
4550
let id = &req.params()["version_id"];
4651
let id = id.parse().unwrap_or(0);
4752
let conn = req.db_conn()?;
48-
let (version, krate): (Version, Crate) = versions::table
53+
let (version, krate, published_by): (Version, Crate, Option<User>) = versions::table
4954
.find(id)
5055
.inner_join(crates::table)
51-
.select((versions::all_columns, crate::models::krate::ALL_COLUMNS))
56+
.left_outer_join(users::table)
57+
.select((
58+
versions::all_columns,
59+
crate::models::krate::ALL_COLUMNS,
60+
users::all_columns.nullable(),
61+
))
5262
.first(&*conn)?;
5363

5464
#[derive(Serialize)]
5565
struct R {
5666
version: EncodableVersion,
5767
}
5868
Ok(req.json(&R {
59-
version: version.encodable(&krate.name),
69+
version: version.encodable(&krate.name, published_by),
6070
}))
6171
}

src/controllers/version/metadata.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,14 @@ pub fn authors(req: &mut dyn Request) -> CargoResult<Response> {
6868
/// API route to have.
6969
pub fn show(req: &mut dyn Request) -> CargoResult<Response> {
7070
let (version, krate) = version_and_crate(req)?;
71+
let conn = req.db_conn()?;
72+
let published_by = version.published_by(&conn);
7173

7274
#[derive(Serialize)]
7375
struct R {
7476
version: EncodableVersion,
7577
}
7678
Ok(req.json(&R {
77-
version: version.encodable(&krate.name),
79+
version: version.encodable(&krate.name, published_by),
7880
}))
7981
}

src/models/user.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::borrow::Cow;
55
use crate::app::App;
66
use crate::util::CargoResult;
77

8-
use crate::models::{Crate, CrateOwner, NewEmail, Owner, OwnerKind, Rights};
8+
use crate::models::{Crate, CrateOwner, Email, NewEmail, Owner, OwnerKind, Rights};
99
use crate::schema::{crate_owners, emails, users};
1010
use crate::views::{EncodablePrivateUser, EncodablePublicUser};
1111

@@ -160,15 +160,12 @@ impl User {
160160
Ok(best)
161161
}
162162

163-
pub fn has_verified_email(&self, conn: &PgConnection) -> CargoResult<bool> {
164-
use diesel::dsl::exists;
165-
let email_exists = diesel::select(exists(
166-
emails::table
167-
.filter(emails::user_id.eq(self.id))
168-
.filter(emails::verified.eq(true)),
169-
))
170-
.get_result(&*conn)?;
171-
Ok(email_exists)
163+
pub fn verified_email(&self, conn: &PgConnection) -> CargoResult<Option<String>> {
164+
Ok(Email::belonging_to(self)
165+
.select(emails::email)
166+
.filter(emails::verified.eq(true))
167+
.first::<String>(&*conn)
168+
.optional()?)
172169
}
173170

174171
/// Converts this `User` model into an `EncodablePrivateUser` for JSON serialization.

src/models/version.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use diesel::prelude::*;
55

66
use crate::util::{human, CargoResult};
77

8-
use crate::models::{Crate, Dependency};
8+
use crate::models::{Crate, Dependency, User};
99
use crate::schema::*;
1010
use crate::views::{EncodableVersion, EncodableVersionLinks};
1111

@@ -23,6 +23,7 @@ pub struct Version {
2323
pub yanked: bool,
2424
pub license: Option<String>,
2525
pub crate_size: Option<i32>,
26+
pub published_by: Option<i32>,
2627
}
2728

2829
#[derive(Insertable, Debug)]
@@ -33,10 +34,11 @@ pub struct NewVersion {
3334
features: serde_json::Value,
3435
license: Option<String>,
3536
crate_size: Option<i32>,
37+
published_by: i32,
3638
}
3739

3840
impl Version {
39-
pub fn encodable(self, crate_name: &str) -> EncodableVersion {
41+
pub fn encodable(self, crate_name: &str, published_by: Option<User>) -> EncodableVersion {
4042
let Version {
4143
id,
4244
num,
@@ -68,6 +70,7 @@ impl Version {
6870
authors: format!("/api/v1/crates/{}/{}/authors", crate_name, num),
6971
},
7072
crate_size,
73+
published_by: published_by.map(|pb| pb.encodable_public()),
7174
}
7275
}
7376

@@ -107,6 +110,15 @@ impl Version {
107110
.set(rendered_at.eq(now))
108111
.execute(conn)
109112
}
113+
114+
/// Gets the User who ran `cargo publish` for this version, if recorded.
115+
/// Not for use when you have a group of versions you need the publishers for.
116+
pub fn published_by(&self, conn: &PgConnection) -> Option<User> {
117+
match self.published_by {
118+
Some(pb) => users::table.find(pb).first(conn).ok(),
119+
None => None,
120+
}
121+
}
110122
}
111123

112124
impl NewVersion {
@@ -118,6 +130,7 @@ impl NewVersion {
118130
license: Option<String>,
119131
license_file: Option<&str>,
120132
crate_size: i32,
133+
published_by: i32,
121134
) -> CargoResult<Self> {
122135
let features = serde_json::to_value(features)?;
123136

@@ -127,14 +140,21 @@ impl NewVersion {
127140
features,
128141
license,
129142
crate_size: Some(crate_size),
143+
published_by,
130144
};
131145

132146
new_version.validate_license(license_file)?;
133147

134148
Ok(new_version)
135149
}
136150

137-
pub fn save(&self, conn: &PgConnection, authors: &[String]) -> CargoResult<Version> {
151+
// TODO: change `published_by_email` to be `String` after 2019-02-28
152+
pub fn save(
153+
&self,
154+
conn: &PgConnection,
155+
authors: &[String],
156+
published_by_email: Option<String>,
157+
) -> CargoResult<Version> {
138158
use crate::schema::version_authors::{name, version_id};
139159
use crate::schema::versions::dsl::*;
140160
use diesel::dsl::exists;
@@ -156,6 +176,16 @@ impl NewVersion {
156176
.values(self)
157177
.get_result::<Version>(conn)?;
158178

179+
// TODO: Remove the `if` around this insert after 2019-02-28
180+
if let Some(published_by_email) = published_by_email {
181+
insert_into(versions_published_by::table)
182+
.values((
183+
versions_published_by::version_id.eq(version.id),
184+
versions_published_by::email.eq(published_by_email),
185+
))
186+
.execute(conn)?;
187+
}
188+
159189
let new_authors = authors
160190
.iter()
161191
.map(|s| (version_id.eq(version.id), name.eq(s)))

0 commit comments

Comments
 (0)