Skip to content

Commit d9fd8b4

Browse files
Merge #1247
1247: Split keyword and category functionality into MVC units r=carols10cents This PR is based on #1155 which is not yet merged to master. This [comparison](jtgeibel/crates.io@add-mvc-modules...jtgeibel:mvc-keyword-and-category) shows just the changes built on top of that branch. This PR moves the request handlers for keyword and category functionality to the `controllers` module. I extracted the common `use` statements into a `controllers::prelude`. `Encodable*` structs and the rfc3339 tests are moved under `views` and the remaining model structs, impls, and tests are moved under `models`.
2 parents af90b2d + 658b62f commit d9fd8b4

File tree

9 files changed

+307
-297
lines changed

9 files changed

+307
-297
lines changed

src/controllers/category.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use super::prelude::*;
2+
3+
use models::Category;
4+
use schema::categories;
5+
use views::{EncodableCategory, EncodableCategoryWithSubcategories};
6+
7+
/// Handles the `GET /categories` route.
8+
pub fn index(req: &mut Request) -> CargoResult<Response> {
9+
let conn = req.db_conn()?;
10+
let (offset, limit) = req.pagination(10, 100)?;
11+
let query = req.query();
12+
let sort = query.get("sort").map_or("alpha", String::as_str);
13+
14+
let categories = Category::toplevel(&conn, sort, limit, offset)?;
15+
let categories = categories.into_iter().map(Category::encodable).collect();
16+
17+
// Query for the total count of categories
18+
let total = Category::count_toplevel(&conn)?;
19+
20+
#[derive(Serialize)]
21+
struct R {
22+
categories: Vec<EncodableCategory>,
23+
meta: Meta,
24+
}
25+
#[derive(Serialize)]
26+
struct Meta {
27+
total: i64,
28+
}
29+
30+
Ok(req.json(&R {
31+
categories: categories,
32+
meta: Meta { total: total },
33+
}))
34+
}
35+
36+
/// Handles the `GET /categories/:category_id` route.
37+
pub fn show(req: &mut Request) -> CargoResult<Response> {
38+
let slug = &req.params()["category_id"];
39+
let conn = req.db_conn()?;
40+
let cat = categories::table
41+
.filter(categories::slug.eq(::lower(slug)))
42+
.first::<Category>(&*conn)?;
43+
let subcats = cat.subcategories(&conn)?
44+
.into_iter()
45+
.map(Category::encodable)
46+
.collect();
47+
48+
let cat = cat.encodable();
49+
let cat_with_subcats = EncodableCategoryWithSubcategories {
50+
id: cat.id,
51+
category: cat.category,
52+
slug: cat.slug,
53+
description: cat.description,
54+
created_at: cat.created_at,
55+
crates_cnt: cat.crates_cnt,
56+
subcategories: subcats,
57+
};
58+
59+
#[derive(Serialize)]
60+
struct R {
61+
category: EncodableCategoryWithSubcategories,
62+
}
63+
Ok(req.json(&R {
64+
category: cat_with_subcats,
65+
}))
66+
}
67+
68+
/// Handles the `GET /category_slugs` route.
69+
pub fn slugs(req: &mut Request) -> CargoResult<Response> {
70+
let conn = req.db_conn()?;
71+
let slugs = categories::table
72+
.select((categories::slug, categories::slug))
73+
.order(categories::slug)
74+
.load(&*conn)?;
75+
76+
#[derive(Serialize, Queryable)]
77+
struct Slug {
78+
id: String,
79+
slug: String,
80+
}
81+
82+
#[derive(Serialize)]
83+
struct R {
84+
category_slugs: Vec<Slug>,
85+
}
86+
Ok(req.json(&R {
87+
category_slugs: slugs,
88+
}))
89+
}

src/controllers/keyword.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use super::prelude::*;
2+
3+
use controllers::helpers::Paginate;
4+
use models::Keyword;
5+
use views::EncodableKeyword;
6+
7+
/// Handles the `GET /keywords` route.
8+
pub fn index(req: &mut Request) -> CargoResult<Response> {
9+
use schema::keywords;
10+
11+
let conn = req.db_conn()?;
12+
let (offset, limit) = req.pagination(10, 100)?;
13+
let query = req.query();
14+
let sort = query.get("sort").map(|s| &s[..]).unwrap_or("alpha");
15+
16+
let mut query = keywords::table.into_boxed();
17+
18+
if sort == "crates" {
19+
query = query.order(keywords::crates_cnt.desc());
20+
} else {
21+
query = query.order(keywords::keyword.asc());
22+
}
23+
24+
let data = query
25+
.paginate(limit, offset)
26+
.load::<(Keyword, i64)>(&*conn)?;
27+
let total = data.get(0).map(|&(_, t)| t).unwrap_or(0);
28+
let kws = data.into_iter()
29+
.map(|(k, _)| k.encodable())
30+
.collect::<Vec<_>>();
31+
32+
#[derive(Serialize)]
33+
struct R {
34+
keywords: Vec<EncodableKeyword>,
35+
meta: Meta,
36+
}
37+
#[derive(Serialize)]
38+
struct Meta {
39+
total: i64,
40+
}
41+
42+
Ok(req.json(&R {
43+
keywords: kws,
44+
meta: Meta { total: total },
45+
}))
46+
}
47+
48+
/// Handles the `GET /keywords/:keyword_id` route.
49+
pub fn show(req: &mut Request) -> CargoResult<Response> {
50+
let name = &req.params()["keyword_id"];
51+
let conn = req.db_conn()?;
52+
53+
let kw = Keyword::find_by_keyword(&conn, name)?;
54+
55+
#[derive(Serialize)]
56+
struct R {
57+
keyword: EncodableKeyword,
58+
}
59+
Ok(req.json(&R {
60+
keyword: kw.encodable(),
61+
}))
62+
}

src/controllers/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1-
// TODO: All endpoints would be moved to submodules here
1+
// TODO: Finish moving api endpoints to submodules here
2+
3+
mod prelude {
4+
pub use diesel::prelude::*;
5+
6+
pub use conduit::{Request, Response};
7+
pub use conduit_router::RequestParams;
8+
pub use db::RequestTransaction;
9+
pub use util::{CargoResult, RequestUtils};
10+
}
211

312
pub mod helpers;
13+
14+
pub mod category;
15+
pub mod keyword;

src/lib.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ use util::{R404, C, R};
6565
pub mod app;
6666
pub mod badge;
6767
pub mod boot;
68-
pub mod category;
6968
pub mod config;
7069
pub mod crate_owner_invitation;
7170
pub mod db;
@@ -75,7 +74,6 @@ pub mod download;
7574
pub mod git;
7675
pub mod github;
7776
pub mod http;
78-
pub mod keyword;
7977
pub mod krate;
8078
pub mod owner;
8179
pub mod render;
@@ -186,11 +184,11 @@ pub fn middleware(app: Arc<App>) -> MiddlewareBuilder {
186184
"/crates/:crate_id/reverse_dependencies",
187185
C(krate::metadata::reverse_dependencies),
188186
);
189-
api_router.get("/keywords", C(keyword::index));
190-
api_router.get("/keywords/:keyword_id", C(keyword::show));
191-
api_router.get("/categories", C(category::index));
192-
api_router.get("/categories/:category_id", C(category::show));
193-
api_router.get("/category_slugs", C(category::slugs));
187+
api_router.get("/keywords", C(controllers::keyword::index));
188+
api_router.get("/keywords/:keyword_id", C(controllers::keyword::show));
189+
api_router.get("/categories", C(controllers::category::index));
190+
api_router.get("/categories/:category_id", C(controllers::category::show));
191+
api_router.get("/category_slugs", C(controllers::category::slugs));
194192
api_router.get("/users/:user_id", C(user::show));
195193
api_router.put("/users/:user_id", C(user::update_user));
196194
api_router.get("/users/:user_id/stats", C(user::stats));

src/category.rs renamed to src/models/category.rs

Lines changed: 1 addition & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
use chrono::NaiveDateTime;
2-
use conduit::{Request, Response};
3-
use conduit_router::RequestParams;
42
use diesel::*;
53

6-
use db::RequestTransaction;
7-
use util::{CargoResult, RequestUtils};
8-
94
use models::Crate;
105
use schema::*;
6+
use views::EncodableCategory;
117

128
#[derive(Clone, Identifiable, Queryable, QueryableByName, Debug)]
139
#[table_name = "categories"]
@@ -30,27 +26,6 @@ pub struct CrateCategory {
3026
category_id: i32,
3127
}
3228

33-
#[derive(Serialize, Deserialize, Debug)]
34-
pub struct EncodableCategory {
35-
pub id: String,
36-
pub category: String,
37-
pub slug: String,
38-
pub description: String,
39-
#[serde(with = "::util::rfc3339")] pub created_at: NaiveDateTime,
40-
pub crates_cnt: i32,
41-
}
42-
43-
#[derive(Serialize, Deserialize, Debug)]
44-
pub struct EncodableCategoryWithSubcategories {
45-
pub id: String,
46-
pub category: String,
47-
pub slug: String,
48-
pub description: String,
49-
#[serde(with = "::util::rfc3339")] pub created_at: NaiveDateTime,
50-
pub crates_cnt: i32,
51-
pub subcategories: Vec<EncodableCategory>,
52-
}
53-
5429
impl Category {
5530
pub fn encodable(self) -> EncodableCategory {
5631
let Category {
@@ -180,97 +155,11 @@ impl<'a> NewCategory<'a> {
180155
}
181156
}
182157

183-
/// Handles the `GET /categories` route.
184-
pub fn index(req: &mut Request) -> CargoResult<Response> {
185-
let conn = req.db_conn()?;
186-
let (offset, limit) = req.pagination(10, 100)?;
187-
let query = req.query();
188-
let sort = query.get("sort").map_or("alpha", String::as_str);
189-
190-
let categories = Category::toplevel(&conn, sort, limit, offset)?;
191-
let categories = categories.into_iter().map(Category::encodable).collect();
192-
193-
// Query for the total count of categories
194-
let total = Category::count_toplevel(&conn)?;
195-
196-
#[derive(Serialize)]
197-
struct R {
198-
categories: Vec<EncodableCategory>,
199-
meta: Meta,
200-
}
201-
#[derive(Serialize)]
202-
struct Meta {
203-
total: i64,
204-
}
205-
206-
Ok(req.json(&R {
207-
categories: categories,
208-
meta: Meta { total: total },
209-
}))
210-
}
211-
212-
/// Handles the `GET /categories/:category_id` route.
213-
pub fn show(req: &mut Request) -> CargoResult<Response> {
214-
let slug = &req.params()["category_id"];
215-
let conn = req.db_conn()?;
216-
let cat = categories::table
217-
.filter(categories::slug.eq(::lower(slug)))
218-
.first::<Category>(&*conn)?;
219-
let subcats = cat.subcategories(&conn)?
220-
.into_iter()
221-
.map(Category::encodable)
222-
.collect();
223-
224-
let cat = cat.encodable();
225-
let cat_with_subcats = EncodableCategoryWithSubcategories {
226-
id: cat.id,
227-
category: cat.category,
228-
slug: cat.slug,
229-
description: cat.description,
230-
created_at: cat.created_at,
231-
crates_cnt: cat.crates_cnt,
232-
subcategories: subcats,
233-
};
234-
235-
#[derive(Serialize)]
236-
struct R {
237-
category: EncodableCategoryWithSubcategories,
238-
}
239-
Ok(req.json(&R {
240-
category: cat_with_subcats,
241-
}))
242-
}
243-
244-
/// Handles the `GET /category_slugs` route.
245-
pub fn slugs(req: &mut Request) -> CargoResult<Response> {
246-
let conn = req.db_conn()?;
247-
let slugs = categories::table
248-
.select((categories::slug, categories::slug))
249-
.order(categories::slug)
250-
.load(&*conn)?;
251-
252-
#[derive(Serialize, Queryable)]
253-
struct Slug {
254-
id: String,
255-
slug: String,
256-
}
257-
258-
#[derive(Serialize)]
259-
struct R {
260-
category_slugs: Vec<Slug>,
261-
}
262-
Ok(req.json(&R {
263-
category_slugs: slugs,
264-
}))
265-
}
266-
267158
#[cfg(test)]
268159
mod tests {
269160
use super::*;
270-
use chrono::NaiveDate;
271161
use diesel::connection::SimpleConnection;
272162
use dotenv::dotenv;
273-
use serde_json;
274163
use std::env;
275164

276165
fn pg_connection() -> PgConnection {
@@ -401,42 +290,4 @@ mod tests {
401290
let expected = vec![("Cat 3".to_string(), 6), ("Cat 1".to_string(), 3)];
402291
assert_eq!(expected, categories);
403292
}
404-
405-
#[test]
406-
fn category_dates_serializes_to_rfc3339() {
407-
let cat = EncodableCategory {
408-
id: "".to_string(),
409-
category: "".to_string(),
410-
slug: "".to_string(),
411-
description: "".to_string(),
412-
crates_cnt: 1,
413-
created_at: NaiveDate::from_ymd(2017, 1, 6).and_hms(14, 23, 11),
414-
};
415-
let json = serde_json::to_string(&cat).unwrap();
416-
assert!(
417-
json.as_str()
418-
.find(r#""created_at":"2017-01-06T14:23:11+00:00""#)
419-
.is_some()
420-
);
421-
}
422-
423-
#[test]
424-
fn category_with_sub_dates_serializes_to_rfc3339() {
425-
let cat = EncodableCategoryWithSubcategories {
426-
id: "".to_string(),
427-
category: "".to_string(),
428-
slug: "".to_string(),
429-
description: "".to_string(),
430-
crates_cnt: 1,
431-
created_at: NaiveDate::from_ymd(2017, 1, 6).and_hms(14, 23, 11),
432-
subcategories: vec![],
433-
};
434-
let json = serde_json::to_string(&cat).unwrap();
435-
assert!(
436-
json.as_str()
437-
.find(r#""created_at":"2017-01-06T14:23:11+00:00""#)
438-
.is_some()
439-
);
440-
}
441-
442293
}

0 commit comments

Comments
 (0)