diff --git a/src/controllers/user/me.rs b/src/controllers/user/me.rs index 5dd184a9220..5a80fdb30e0 100644 --- a/src/controllers/user/me.rs +++ b/src/controllers/user/me.rs @@ -7,14 +7,15 @@ use crate::email; use crate::controllers::helpers::pagination::Paginated; use crate::models::{ - CrateOwner, Email, Follow, NewEmail, OwnerKind, User, Version, VersionOwnerAction, + ApiToken, CrateOwner, Email, Follow, NewEmail, OwnerKind, User, Version, VersionOwnerAction, }; -use crate::schema::{crate_owners, crates, emails, follows, users, versions}; -use crate::views::{EncodableMe, EncodableVersion, OwnedCrate}; +use crate::schema::{api_tokens, crate_owners, crates, emails, follows, users, versions}; +use crate::views::{EncodableMe, EncodableMeMeta, EncodableVersion, OwnedCrate}; /// Handles the `GET /me` route. pub fn me(req: &mut dyn RequestExt) -> EndpointResult { - let user_id = req.authenticate()?.user_id(); + let user = req.authenticate()?; + let user_id = user.user_id(); let conn = req.db_conn()?; let (user, verified, email, verification_sent): (User, Option, Option, bool) = @@ -29,6 +30,11 @@ pub fn me(req: &mut dyn RequestExt) -> EndpointResult { )) .first(&*conn)?; + let token_count = ApiToken::belonging_to(&user) + .filter(api_tokens::revoked.eq(false)) + .count() + .get_result(&*conn)?; + let owned_crates = CrateOwner::by_owner_kind(OwnerKind::User) .inner_join(crates::table) .filter(crate_owners::owner_id.eq(user_id)) @@ -48,6 +54,7 @@ pub fn me(req: &mut dyn RequestExt) -> EndpointResult { Ok(req.json(&EncodableMe { user: user.encodable_private(email, verified, verification_sent), owned_crates, + meta: EncodableMeMeta { token_count }, })) } diff --git a/src/tests/user.rs b/src/tests/user.rs index db280868dc4..1cbf195227c 100644 --- a/src/tests/user.rs +++ b/src/tests/user.rs @@ -5,9 +5,11 @@ use crate::{ OkBool, TestApp, }; use cargo_registry::{ - models::{Email, NewUser, User}, + models::{ApiToken, Email, NewUser, User}, schema::crate_owners, - views::{EncodablePrivateUser, EncodablePublicUser, EncodableVersion, OwnedCrate}, + views::{ + EncodableMeMeta, EncodablePrivateUser, EncodablePublicUser, EncodableVersion, OwnedCrate, + }, }; use diesel::prelude::*; @@ -27,6 +29,7 @@ pub struct UserShowPublicResponse { pub struct UserShowPrivateResponse { pub user: EncodablePrivateUser, pub owned_crates: Vec, + pub meta: EncodableMeMeta, } #[derive(Deserialize)] @@ -748,3 +751,22 @@ fn test_update_email_notifications_not_owned() { // There should be no change to the `email_notifications` value for a crate not belonging to me assert!(email_notifications); } + +#[test] +fn shows_that_user_has_tokens() { + let (app, _, user) = TestApp::init().with_user(); + + let json = user.show_me(); + assert_eq!(json.meta.token_count, 0); + + let user_id = user.as_model().id; + app.db(|conn| { + vec![ + assert_ok!(ApiToken::insert(conn, user_id, "bar")), + assert_ok!(ApiToken::insert(conn, user_id, "baz")), + ] + }); + + let json = user.show_me(); + assert_eq!(json.meta.token_count, 2); +} diff --git a/src/views.rs b/src/views.rs index 228335e6e9e..646d246d691 100644 --- a/src/views.rs +++ b/src/views.rs @@ -161,6 +161,12 @@ pub struct OwnedCrate { pub struct EncodableMe { pub user: EncodablePrivateUser, pub owned_crates: Vec, + pub meta: EncodableMeMeta, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +pub struct EncodableMeMeta { + pub token_count: i64, } /// The serialization format for the `User` model.