Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract more controller modules #10231

Merged
merged 8 commits into from
Dec 17, 2024
1 change: 1 addition & 0 deletions src/controllers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod github;
pub mod keyword;
pub mod krate;
pub mod metrics;
pub mod session;
pub mod site_metadata;
pub mod summary;
pub mod team;
Expand Down
1 change: 1 addition & 0 deletions src/controllers/krate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod follow;
pub mod metadata;
pub mod owners;
pub mod publish;
pub mod rev_deps;
pub mod search;
pub mod versions;

Expand Down
90 changes: 4 additions & 86 deletions src/controllers/krate/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,15 @@
//! `Cargo.toml` file.

use crate::app::AppState;
use crate::controllers::helpers::pagination::PaginationOptions;
use crate::controllers::krate::CratePath;
use crate::controllers::version::CrateVersionPath;
use crate::models::{
Category, Crate, CrateCategory, CrateKeyword, CrateName, Keyword, RecentCrateDownloads, User,
Version, VersionOwnerAction,
Category, Crate, CrateCategory, CrateKeyword, Keyword, RecentCrateDownloads, User, Version,
VersionOwnerAction,
};
use crate::schema::*;
use crate::util::errors::{bad_request, crate_not_found, AppResult, BoxedAppError};
use crate::util::{redirect, RequestUtils};
use crate::views::{
EncodableCategory, EncodableCrate, EncodableDependency, EncodableKeyword, EncodableVersion,
};
use axum::response::{IntoResponse, Response};
use crate::util::RequestUtils;
use crate::views::{EncodableCategory, EncodableCrate, EncodableKeyword, EncodableVersion};
use axum_extra::json;
use axum_extra::response::ErasedJson;
use diesel::prelude::*;
Expand Down Expand Up @@ -244,80 +239,3 @@ impl FromStr for ShowIncludeMode {
Ok(mode)
}
}

/// Get the readme of a crate version.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/{version}/readme",
params(CrateVersionPath),
tag = "versions",
responses((status = 200, description = "Successful Response")),
)]
pub async fn get_version_readme(app: AppState, path: CrateVersionPath, req: Parts) -> Response {
let redirect_url = app.storage.readme_location(&path.name, &path.version);
if req.wants_json() {
json!({ "url": redirect_url }).into_response()
} else {
redirect(redirect_url)
}
}

/// List reverse dependencies of a crate.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/reverse_dependencies",
params(CratePath),
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn list_reverse_dependencies(
app: AppState,
path: CratePath,
req: Parts,
) -> AppResult<ErasedJson> {
let mut conn = app.db_read().await?;

let pagination_options = PaginationOptions::builder().gather(&req)?;

let krate = path.load_crate(&mut conn).await?;

let (rev_deps, total) = krate
.reverse_dependencies(&mut conn, pagination_options)
.await?;

let rev_deps: Vec<_> = rev_deps
.into_iter()
.map(|dep| EncodableDependency::from_reverse_dep(dep, &krate.name))
.collect();

let version_ids: Vec<i32> = rev_deps.iter().map(|dep| dep.version_id).collect();

let versions_and_publishers: Vec<(Version, CrateName, Option<User>)> = versions::table
.filter(versions::id.eq_any(version_ids))
.inner_join(crates::table)
.left_outer_join(users::table)
.select(<(Version, CrateName, Option<User>)>::as_select())
.load(&mut conn)
.await?;

let versions = versions_and_publishers
.iter()
.map(|(v, ..)| v)
.collect::<Vec<_>>();

let actions = VersionOwnerAction::for_versions(&mut conn, &versions).await?;

let versions = versions_and_publishers
.into_iter()
.zip(actions)
.map(|((version, krate_name, published_by), actions)| {
EncodableVersion::from(version, &krate_name.name, published_by, actions)
})
.collect::<Vec<_>>();

Ok(json!({
"dependencies": rev_deps,
"versions": versions,
"meta": { "total": total },
}))
}
72 changes: 72 additions & 0 deletions src/controllers/krate/rev_deps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use crate::app::AppState;
use crate::controllers::helpers::pagination::PaginationOptions;
use crate::controllers::krate::CratePath;
use crate::models::{CrateName, User, Version, VersionOwnerAction};
use crate::util::errors::AppResult;
use crate::views::{EncodableDependency, EncodableVersion};
use axum_extra::json;
use axum_extra::response::ErasedJson;
use crates_io_database::schema::{crates, users, versions};
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use http::request::Parts;

/// List reverse dependencies of a crate.
#[utoipa::path(
get,
path = "/api/v1/crates/{name}/reverse_dependencies",
params(CratePath),
tag = "crates",
responses((status = 200, description = "Successful Response")),
)]
pub async fn list_reverse_dependencies(
app: AppState,
path: CratePath,
req: Parts,
) -> AppResult<ErasedJson> {
let mut conn = app.db_read().await?;

let pagination_options = PaginationOptions::builder().gather(&req)?;

let krate = path.load_crate(&mut conn).await?;

let (rev_deps, total) = krate
.reverse_dependencies(&mut conn, pagination_options)
.await?;

let rev_deps: Vec<_> = rev_deps
.into_iter()
.map(|dep| EncodableDependency::from_reverse_dep(dep, &krate.name))
.collect();

let version_ids: Vec<i32> = rev_deps.iter().map(|dep| dep.version_id).collect();

let versions_and_publishers: Vec<(Version, CrateName, Option<User>)> = versions::table
.filter(versions::id.eq_any(version_ids))
.inner_join(crates::table)
.left_outer_join(users::table)
.select(<(Version, CrateName, Option<User>)>::as_select())
.load(&mut conn)
.await?;

let versions = versions_and_publishers
.iter()
.map(|(v, ..)| v)
.collect::<Vec<_>>();

let actions = VersionOwnerAction::for_versions(&mut conn, &versions).await?;

let versions = versions_and_publishers
.into_iter()
.zip(actions)
.map(|((version, krate_name, published_by), actions)| {
EncodableVersion::from(version, &krate_name.name, published_by, actions)
})
.collect::<Vec<_>>();

Ok(json!({
"dependencies": rev_deps,
"versions": versions,
"meta": { "total": total },
}))
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ pub async fn authorize_session(
// Log in by setting a cookie and the middleware authentication
session.insert("user_id".to_string(), user.id.to_string());

super::me::get_authenticated_user(app, req).await
super::user::me::get_authenticated_user(app, req).await
}

async fn save_user_to_database(
Expand Down
6 changes: 3 additions & 3 deletions src/controllers/user.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
pub mod email_notifications;
pub mod email_verification;
pub mod me;
pub mod other;
pub mod resend;
pub mod session;
pub mod update;

pub use resend::resend_email_verification;
pub use email_verification::resend_email_verification;
pub use update::update_user;
89 changes: 89 additions & 0 deletions src/controllers/user/email_notifications.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::app::AppState;
use crate::auth::AuthCheck;
use crate::controllers::helpers::ok_true;
use crate::models::{CrateOwner, OwnerKind};
use crate::schema::crate_owners;
use crate::util::errors::AppResult;
use axum::response::Response;
use axum::Json;
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use http::request::Parts;
use std::collections::HashMap;

#[derive(Deserialize)]
pub struct CrateEmailNotifications {
id: i32,
email_notifications: bool,
}

/// Update email notification settings for the authenticated user.
///
/// This endpoint was implemented for an experimental feature that was never
/// fully implemented. It is now deprecated and will be removed in the future.
#[utoipa::path(
put,
path = "/api/v1/me/email_notifications",
tag = "users",
responses((status = 200, description = "Successful Response")),
)]
#[deprecated]
pub async fn update_email_notifications(
app: AppState,
parts: Parts,
Json(updates): Json<Vec<CrateEmailNotifications>>,
) -> AppResult<Response> {
use diesel::pg::upsert::excluded;

let updates: HashMap<i32, bool> = updates
.iter()
.map(|c| (c.id, c.email_notifications))
.collect();

let mut conn = app.db_write().await?;
let user_id = AuthCheck::default()
.check(&parts, &mut conn)
.await?
.user_id();

// Build inserts from existing crates belonging to the current user
let to_insert = CrateOwner::by_owner_kind(OwnerKind::User)
.filter(crate_owners::owner_id.eq(user_id))
.select((
crate_owners::crate_id,
crate_owners::owner_id,
crate_owners::owner_kind,
crate_owners::email_notifications,
))
.load(&mut conn)
.await?
.into_iter()
// Remove records whose `email_notifications` will not change from their current value
.map(
|(c_id, o_id, o_kind, e_notifications): (i32, i32, i32, bool)| {
let current_e_notifications = *updates.get(&c_id).unwrap_or(&e_notifications);
(
crate_owners::crate_id.eq(c_id),
crate_owners::owner_id.eq(o_id),
crate_owners::owner_kind.eq(o_kind),
crate_owners::email_notifications.eq(current_e_notifications),
)
},
)
.collect::<Vec<_>>();

// Upsert crate owners; this should only actually execute updates
diesel::insert_into(crate_owners::table)
.values(&to_insert)
.on_conflict((
crate_owners::crate_id,
crate_owners::owner_id,
crate_owners::owner_kind,
))
.do_update()
.set(crate_owners::email_notifications.eq(excluded(crate_owners::email_notifications)))
.execute(&mut conn)
.await?;

ok_true()
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,31 @@ use diesel_async::scoped_futures::ScopedFutureExt;
use diesel_async::{AsyncConnection, RunQueryDsl};
use http::request::Parts;

/// Marks the email belonging to the given token as verified.
#[utoipa::path(
put,
path = "/api/v1/confirm/{email_token}",
params(
("email_token" = String, Path, description = "Secret verification token sent to the user's email address"),
),
tag = "users",
responses((status = 200, description = "Successful Response")),
)]
pub async fn confirm_user_email(state: AppState, Path(token): Path<String>) -> AppResult<Response> {
let mut conn = state.db_write().await?;

let updated_rows = diesel::update(emails::table.filter(emails::token.eq(&token)))
.set(emails::verified.eq(true))
.execute(&mut conn)
.await?;

if updated_rows == 0 {
return Err(bad_request("Email belonging to token not found."));
}

ok_true()
}

/// Regenerate and send an email verification token.
#[utoipa::path(
put,
Expand Down
Loading