Skip to content

Commit 4628bcf

Browse files
Merge #1102
1102: Split krate and version functionality into submodules r=carols10cents This series of commits splits the api endpoints in the `krate` and `version` modules into submodules as an initial step of the refactoring discussed in #912. ## Module structure ### `krate::search::search` Shared by cargo and the frontend, used for searching crates. ### `krate::publish::publish` Used by `cargo publish`. This endpoint updates the index, uploads to S3, and caches crate metadata in the database. ### `{krate,version}/metadata.rs` Endpoints in these files provide read-only access to data that could largely be recreated by replaying all crate uploads. The only exception I've seen to this so far is that some responses include download counts. ### `{krate,version}/downloads.rs` Provide crate and version level download stats (updated in `version::cargo::download`). ### `krate/owners.rs` All endpoints for the used by cargo and the frontend for maintaining the list of crate owners. ### `krate/follow.rs` Read/write access to a user populated list of followed crates. ### `version::deprecated` The `version::deprecated::{index,show}` routes appear to be unused. We should confirm this and discuss a plan for potential removal. ### `version::yank` Yank and unyank functionality. ## Code that remains in `mod.rs` The code that remains in the `mod.rs` files consists primarily of structs for querying, inserting, and serializing. I'm thinking that these structs could be moved to modules under a `src/models` directory along with: `src/category.rs`, `src/dependency.rs`, `src/download.rs`, `src/owner.rs`. (The `keyword`, `token` and `user` modules also have model logic which can be extracted.) ## Remaining work In order to simplify review of this PR, I've only moved code around and haven't done any refactoring of logic. There are probably bits of code that we can move from the endpoints to the model logic, especially if it returns a QueryResult. Feel free to let me know if you see any low-hanging fruit there, otherwise we can address that in a future PR. (Some of the logic still in `mod.rs` returns CargoResult which covers all error types. We will probably need to put some thought into if the model represents just the database or also includes the index and S3 state. I think further exploration of this is best tracked under #912.) /cc @vignesh-sankaran @kureuil
2 parents ef1ac58 + 18cd661 commit 4628bcf

File tree

13 files changed

+1354
-1168
lines changed

13 files changed

+1354
-1168
lines changed

src/krate/downloads.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//! Endpoint for exposing crate download counts
2+
//!
3+
//! The enpoints for download a crate and exposing version specific
4+
//! download counts are located in `krate::downloads`.
5+
6+
use std::cmp;
7+
8+
use conduit::{Request, Response};
9+
use conduit_router::RequestParams;
10+
use diesel::prelude::*;
11+
12+
use db::RequestTransaction;
13+
use download::{EncodableVersionDownload, VersionDownload};
14+
use schema::*;
15+
use util::{CargoResult, RequestUtils};
16+
use Version;
17+
18+
use super::{to_char, Crate};
19+
20+
/// Handles the `GET /crates/:crate_id/downloads` route.
21+
pub fn downloads(req: &mut Request) -> CargoResult<Response> {
22+
use diesel::expression::dsl::*;
23+
use diesel::types::BigInt;
24+
25+
let crate_name = &req.params()["crate_id"];
26+
let conn = req.db_conn()?;
27+
let krate = Crate::by_name(crate_name).first::<Crate>(&*conn)?;
28+
29+
let mut versions = Version::belonging_to(&krate).load::<Version>(&*conn)?;
30+
versions.sort_by(|a, b| b.num.cmp(&a.num));
31+
let (latest_five, rest) = versions.split_at(cmp::min(5, versions.len()));
32+
33+
let downloads = VersionDownload::belonging_to(latest_five)
34+
.filter(version_downloads::date.gt(date(now - 90.days())))
35+
.order(version_downloads::date.asc())
36+
.load(&*conn)?
37+
.into_iter()
38+
.map(VersionDownload::encodable)
39+
.collect::<Vec<_>>();
40+
41+
let sum_downloads = sql::<BigInt>("SUM(version_downloads.downloads)");
42+
let extra = VersionDownload::belonging_to(rest)
43+
.select((
44+
to_char(version_downloads::date, "YYYY-MM-DD"),
45+
sum_downloads,
46+
))
47+
.filter(version_downloads::date.gt(date(now - 90.days())))
48+
.group_by(version_downloads::date)
49+
.order(version_downloads::date.asc())
50+
.load::<ExtraDownload>(&*conn)?;
51+
52+
#[derive(Serialize, Queryable)]
53+
struct ExtraDownload {
54+
date: String,
55+
downloads: i64,
56+
}
57+
#[derive(Serialize)]
58+
struct R {
59+
version_downloads: Vec<EncodableVersionDownload>,
60+
meta: Meta,
61+
}
62+
#[derive(Serialize)]
63+
struct Meta {
64+
extra_downloads: Vec<ExtraDownload>,
65+
}
66+
let meta = Meta {
67+
extra_downloads: extra,
68+
};
69+
Ok(req.json(&R {
70+
version_downloads: downloads,
71+
meta: meta,
72+
}))
73+
}

src/krate/follow.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//! Endpoints for managing a per user list of followed crates
2+
3+
use conduit::{Request, Response};
4+
use conduit_router::RequestParams;
5+
use diesel::associations::Identifiable;
6+
use diesel::pg::upsert::*;
7+
use diesel::prelude::*;
8+
use diesel;
9+
10+
use db::RequestTransaction;
11+
use schema::*;
12+
use user::RequestUser;
13+
use util::{CargoResult, RequestUtils};
14+
15+
use super::{Crate, Follow};
16+
17+
fn follow_target(req: &mut Request) -> CargoResult<Follow> {
18+
let user = req.user()?;
19+
let conn = req.db_conn()?;
20+
let crate_name = &req.params()["crate_id"];
21+
let crate_id = Crate::by_name(crate_name).select(crates::id).first(&*conn)?;
22+
Ok(Follow {
23+
user_id: user.id,
24+
crate_id: crate_id,
25+
})
26+
}
27+
28+
/// Handles the `PUT /crates/:crate_id/follow` route.
29+
pub fn follow(req: &mut Request) -> CargoResult<Response> {
30+
let follow = follow_target(req)?;
31+
let conn = req.db_conn()?;
32+
diesel::insert(&follow.on_conflict_do_nothing())
33+
.into(follows::table)
34+
.execute(&*conn)?;
35+
#[derive(Serialize)]
36+
struct R {
37+
ok: bool,
38+
}
39+
Ok(req.json(&R { ok: true }))
40+
}
41+
42+
/// Handles the `DELETE /crates/:crate_id/follow` route.
43+
pub fn unfollow(req: &mut Request) -> CargoResult<Response> {
44+
let follow = follow_target(req)?;
45+
let conn = req.db_conn()?;
46+
diesel::delete(&follow).execute(&*conn)?;
47+
#[derive(Serialize)]
48+
struct R {
49+
ok: bool,
50+
}
51+
Ok(req.json(&R { ok: true }))
52+
}
53+
54+
/// Handles the `GET /crates/:crate_id/following` route.
55+
pub fn following(req: &mut Request) -> CargoResult<Response> {
56+
use diesel::expression::dsl::exists;
57+
58+
let follow = follow_target(req)?;
59+
let conn = req.db_conn()?;
60+
let following = diesel::select(exists(follows::table.find(follow.id()))).get_result(&*conn)?;
61+
#[derive(Serialize)]
62+
struct R {
63+
following: bool,
64+
}
65+
Ok(req.json(&R {
66+
following: following,
67+
}))
68+
}

0 commit comments

Comments
 (0)