Skip to content

Commit 9960545

Browse files
1287: Finish MVC Split and Organize Middleware r=carols10cents This PR largely finishes the reorganization of code into `models`, `views`, and `controllers` modules. The following table summarizes the breakdown: | File in src/ | Model | View | Controller | | --- | --- | --- | --- | | dependency.rs | X | X | | | download.rs | X | X | | | site_metadata.rs | | | X | | token.rs | X | X | X | | version/* | X | X | X| | user/* | X | X | X | While splitting `user/*` I created a new module for the CurrentUser middleware. The rest of the commit series organizes and simplifies the remaining middleware logic and pulls the route building logic into `router.rs`. The individual commits are straightforward, but the PR has grown rather large. I can split it in half if that helps with review, but I hope its okay as-is.
2 parents 3f81809 + 5847cfe commit 9960545

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1541
-1557
lines changed

src/app.rs

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
//! Application-wide components in a struct accessible from each request
22
33
use std::env;
4-
use std::error::Error;
54
use std::path::PathBuf;
65
use std::sync::{Arc, Mutex};
76

8-
use conduit::{Request, Response};
9-
use conduit_middleware::Middleware;
107
use diesel::r2d2;
118
use git2;
129
use oauth2;
@@ -39,13 +36,6 @@ pub struct App {
3936
pub config: Config,
4037
}
4138

42-
/// The `AppMiddleware` injects an `App` instance into the `Request` extensions
43-
// Can't derive Debug because `App` can't.
44-
#[allow(missing_debug_implementations)]
45-
pub struct AppMiddleware {
46-
app: Arc<App>,
47-
}
48-
4939
impl App {
5040
/// Creates a new `App` with a given `Config`
5141
///
@@ -113,36 +103,3 @@ impl App {
113103
handle
114104
}
115105
}
116-
117-
impl AppMiddleware {
118-
pub fn new(app: Arc<App>) -> AppMiddleware {
119-
AppMiddleware { app: app }
120-
}
121-
}
122-
123-
impl Middleware for AppMiddleware {
124-
fn before(&self, req: &mut Request) -> Result<(), Box<Error + Send>> {
125-
req.mut_extensions().insert(Arc::clone(&self.app));
126-
Ok(())
127-
}
128-
129-
fn after(
130-
&self,
131-
req: &mut Request,
132-
res: Result<Response, Box<Error + Send>>,
133-
) -> Result<Response, Box<Error + Send>> {
134-
req.mut_extensions().pop::<Arc<App>>().unwrap();
135-
res
136-
}
137-
}
138-
139-
/// Adds an `app()` method to the `Request` type returning the global `App` instance
140-
pub trait RequestApp {
141-
fn app(&self) -> &Arc<App>;
142-
}
143-
144-
impl<T: Request + ?Sized> RequestApp for T {
145-
fn app(&self) -> &Arc<App> {
146-
self.extensions().find::<Arc<App>>().expect("Missing app")
147-
}
148-
}

src/bin/delete-crate.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use std::io::prelude::*;
1818
use cargo_registry::models::Crate;
1919
use cargo_registry::schema::crates;
2020

21-
#[allow(dead_code)]
2221
fn main() {
2322
let conn = cargo_registry::db::connect_now().unwrap();
2423
conn.transaction::<_, diesel::result::Error, _>(|| {

src/bin/delete-version.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use std::io::prelude::*;
1818
use cargo_registry::models::{Crate, Version};
1919
use cargo_registry::schema::versions;
2020

21-
#[allow(dead_code)]
2221
fn main() {
2322
let conn = cargo_registry::db::connect_now().unwrap();
2423
conn.transaction::<_, diesel::result::Error, _>(|| {

src/bin/server.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use std::fs::{self, File};
1212
use std::sync::Arc;
1313
use std::sync::mpsc::channel;
1414

15-
#[allow(dead_code)]
1615
fn main() {
1716
// Initialize logging
1817
env_logger::init();
@@ -46,7 +45,7 @@ fn main() {
4645
cfg.set_str("user.email", "[email protected]").unwrap();
4746

4847
let app = cargo_registry::App::new(&config);
49-
let app = cargo_registry::middleware(Arc::new(app));
48+
let app = cargo_registry::build_handler(Arc::new(app));
5049

5150
// On every server restart, ensure the categories available in the database match
5251
// the information in *src/categories.toml*.

src/bin/update-downloads.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use cargo_registry::schema::*;
1313

1414
static LIMIT: i64 = 1000;
1515

16-
#[allow(dead_code)] // dead in tests
1716
fn main() {
1817
let daemon = env::args().nth(1).as_ref().map(|s| &s[..]) == Some("daemon");
1918
let sleep = env::args().nth(2).map(|s| s.parse().unwrap());

src/controllers/krate/metadata.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
//! index or cached metadata which was extracted (client side) from the
55
//! `Cargo.toml` file.
66
7-
use app::RequestApp;
8-
97
use controllers::prelude::*;
108
use views::{EncodableCategory, EncodableCrate, EncodableDependency, EncodableKeyword,
119
EncodableVersion};

src/controllers/krate/publish.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::sync::Arc;
77
use hex::ToHex;
88
use serde_json;
99

10-
use dependency;
1110
use git;
1211
use render;
1312
use util::{read_fill, read_le_u32};
@@ -16,6 +15,7 @@ use util::{internal, ChainError};
1615
use controllers::prelude::*;
1716
use views::{EncodableCrate, EncodableCrateUpload};
1817
use models::{Badge, Category, Keyword, NewCrate, NewVersion, Rights, User};
18+
use models::dependency;
1919

2020
/// Handles the `PUT /crates/new` route.
2121
/// Used by `cargo publish` to publish a new crate or to publish a new version of an

src/controllers/mod.rs

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,80 @@
1-
// TODO: Finish moving api endpoints to submodules here
2-
31
mod prelude {
42
pub use diesel::prelude::*;
53
pub use super::helpers::ok_true;
64

7-
pub use app::RequestApp;
85
pub use conduit::{Request, Response};
96
pub use conduit_router::RequestParams;
7+
108
pub use db::RequestTransaction;
11-
pub use user::RequestUser;
12-
pub use util::{human, CargoResult, RequestUtils};
9+
pub use util::{human, CargoResult};
10+
11+
pub use middleware::app::RequestApp;
12+
pub use middleware::current_user::RequestUser;
13+
14+
use std::io;
15+
use url;
16+
use std::collections::HashMap;
17+
use serde::Serialize;
18+
19+
pub trait RequestUtils {
20+
fn redirect(&self, url: String) -> Response;
21+
22+
fn json<T: Serialize>(&self, t: &T) -> Response;
23+
fn query(&self) -> HashMap<String, String>;
24+
fn wants_json(&self) -> bool;
25+
fn pagination(&self, default: usize, max: usize) -> CargoResult<(i64, i64)>;
26+
}
27+
28+
impl<'a> RequestUtils for Request + 'a {
29+
fn json<T: Serialize>(&self, t: &T) -> Response {
30+
::util::json_response(t)
31+
}
32+
33+
fn query(&self) -> HashMap<String, String> {
34+
url::form_urlencoded::parse(self.query_string().unwrap_or("").as_bytes())
35+
.map(|(a, b)| (a.into_owned(), b.into_owned()))
36+
.collect()
37+
}
38+
39+
fn redirect(&self, url: String) -> Response {
40+
let mut headers = HashMap::new();
41+
headers.insert("Location".to_string(), vec![url.to_string()]);
42+
Response {
43+
status: (302, "Found"),
44+
headers: headers,
45+
body: Box::new(io::empty()),
46+
}
47+
}
48+
49+
fn wants_json(&self) -> bool {
50+
self.headers()
51+
.find("Accept")
52+
.map(|accept| accept.iter().any(|s| s.contains("json")))
53+
.unwrap_or(false)
54+
}
55+
56+
fn pagination(&self, default: usize, max: usize) -> CargoResult<(i64, i64)> {
57+
let query = self.query();
58+
let page = query
59+
.get("page")
60+
.and_then(|s| s.parse::<usize>().ok())
61+
.unwrap_or(1);
62+
let limit = query
63+
.get("per_page")
64+
.and_then(|s| s.parse::<usize>().ok())
65+
.unwrap_or(default);
66+
if limit > max {
67+
return Err(human(&format_args!(
68+
"cannot request more than {} items",
69+
max
70+
)));
71+
}
72+
if page == 0 {
73+
return Err(human("page indexing starts from 1, page 0 is invalid"));
74+
}
75+
Ok((((page - 1) * limit) as i64, limit as i64))
76+
}
77+
}
1378
}
1479

1580
pub mod helpers;
@@ -18,3 +83,8 @@ pub mod category;
1883
pub mod crate_owner_invitation;
1984
pub mod keyword;
2085
pub mod krate;
86+
pub mod site_metadata;
87+
pub mod team;
88+
pub mod token;
89+
pub mod version;
90+
pub mod user;

src/site_metadata.rs renamed to src/controllers/site_metadata.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
use conduit::{Request, Response};
2-
use util::RequestUtils;
3-
use util::errors::CargoResult;
1+
use super::prelude::*;
42

53
/// Returns the JSON representation of the current deployed commit sha.
64
///

src/controllers/team.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use controllers::prelude::*;
2+
3+
use models::Team;
4+
use schema::teams;
5+
use views::EncodableTeam;
6+
7+
/// Handles the `GET /teams/:team_id` route.
8+
pub fn show_team(req: &mut Request) -> CargoResult<Response> {
9+
use self::teams::dsl::{login, teams};
10+
11+
let name = &req.params()["team_id"];
12+
let conn = req.db_conn()?;
13+
let team = teams.filter(login.eq(name)).first::<Team>(&*conn)?;
14+
15+
#[derive(Serialize)]
16+
struct R {
17+
team: EncodableTeam,
18+
}
19+
Ok(req.json(&R {
20+
team: team.encodable(),
21+
}))
22+
}

src/controllers/token.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use super::prelude::*;
2+
3+
use diesel;
4+
use serde_json as json;
5+
use middleware::current_user::AuthenticationSource;
6+
use util::{bad_request, read_fill, ChainError};
7+
8+
use models::ApiToken;
9+
use schema::api_tokens;
10+
use views::EncodableApiTokenWithToken;
11+
12+
/// Handles the `GET /me/tokens` route.
13+
pub fn list(req: &mut Request) -> CargoResult<Response> {
14+
let tokens = ApiToken::belonging_to(req.user()?)
15+
.order(api_tokens::created_at.desc())
16+
.load(&*req.db_conn()?)?;
17+
#[derive(Serialize)]
18+
struct R {
19+
api_tokens: Vec<ApiToken>,
20+
}
21+
Ok(req.json(&R { api_tokens: tokens }))
22+
}
23+
24+
/// Handles the `PUT /me/tokens` route.
25+
pub fn new(req: &mut Request) -> CargoResult<Response> {
26+
/// The incoming serialization format for the `ApiToken` model.
27+
#[derive(Deserialize, Serialize)]
28+
struct NewApiToken {
29+
name: String,
30+
}
31+
32+
/// The incoming serialization format for the `ApiToken` model.
33+
#[derive(Deserialize, Serialize)]
34+
struct NewApiTokenRequest {
35+
api_token: NewApiToken,
36+
}
37+
38+
if req.authentication_source()? != AuthenticationSource::SessionCookie {
39+
return Err(bad_request(
40+
"cannot use an API token to create a new API token",
41+
));
42+
}
43+
44+
let max_size = 2000;
45+
let length = req.content_length()
46+
.chain_error(|| bad_request("missing header: Content-Length"))?;
47+
48+
if length > max_size {
49+
return Err(bad_request(&format!("max content length is: {}", max_size)));
50+
}
51+
52+
let mut json = vec![0; length as usize];
53+
read_fill(req.body(), &mut json)?;
54+
55+
let json = String::from_utf8(json).map_err(|_| bad_request(&"json body was not valid utf-8"))?;
56+
57+
let new: NewApiTokenRequest = json::from_str(&json)
58+
.map_err(|e| bad_request(&format!("invalid new token request: {:?}", e)))?;
59+
60+
let name = &new.api_token.name;
61+
if name.len() < 1 {
62+
return Err(bad_request("name must have a value"));
63+
}
64+
65+
let user = req.user()?;
66+
67+
let max_token_per_user = 500;
68+
let count = ApiToken::belonging_to(user)
69+
.count()
70+
.get_result::<i64>(&*req.db_conn()?)?;
71+
if count >= max_token_per_user {
72+
return Err(bad_request(&format!(
73+
"maximum tokens per user is: {}",
74+
max_token_per_user
75+
)));
76+
}
77+
78+
let api_token = ApiToken::insert(&*req.db_conn()?, user.id, name)?;
79+
80+
#[derive(Serialize)]
81+
struct R {
82+
api_token: EncodableApiTokenWithToken,
83+
}
84+
Ok(req.json(&R {
85+
api_token: api_token.encodable_with_token(),
86+
}))
87+
}
88+
89+
/// Handles the `DELETE /me/tokens/:id` route.
90+
pub fn revoke(req: &mut Request) -> CargoResult<Response> {
91+
let id = req.params()["id"]
92+
.parse::<i32>()
93+
.map_err(|e| bad_request(&format!("invalid token id: {:?}", e)))?;
94+
95+
diesel::delete(ApiToken::belonging_to(req.user()?).find(id)).execute(&*req.db_conn()?)?;
96+
97+
#[derive(Serialize)]
98+
struct R {}
99+
Ok(req.json(&R {}))
100+
}

0 commit comments

Comments
 (0)