Skip to content

Commit

Permalink
Merge pull request #10231 from Turbo87/controller-modules
Browse files Browse the repository at this point in the history
Extract more controller modules
  • Loading branch information
Turbo87 authored Dec 17, 2024
2 parents f94e5e0 + dc70376 commit 41dc490
Show file tree
Hide file tree
Showing 18 changed files with 492 additions and 445 deletions.
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

0 comments on commit 41dc490

Please sign in to comment.