From 46fe200fd60526049d6750922597bdf749c99724 Mon Sep 17 00:00:00 2001 From: zignis Date: Wed, 23 Apr 2025 19:26:57 +0530 Subject: [PATCH 1/7] impl blog login --- ...30_create_blog_login_tokens_table.down.sql | 1 + ...1930_create_blog_login_tokens_table.up.sql | 14 + session/src/config.rs | 7 +- session/src/lib.rs | 16 +- session/src/middleware.rs | 43 +- session/src/session.rs | 46 +- session/src/session_ext.rs | 7 +- src/cron/cleanup_blogs/cleanup_blogs.rs | 2 +- src/cron/cleanup_db/cleanup_db.rs | 104 ++- .../cleanup_db/fixtures/blog_login_tokens.sql | 18 + src/cron/cleanup_s3/cleanup_s3.rs | 4 +- src/cron/sitemap/sitemap.rs | 11 +- src/cron/sitemap/story/story.rs | 1 - src/cron/sitemap/tag/tag.rs | 1 - src/cron/sitemap/user/user.rs | 1 - src/middlewares/identity/error.rs | 2 +- src/middlewares/identity/identity.rs | 12 +- src/middlewares/identity/identity_ext.rs | 13 +- src/middlewares/identity/middleware.rs | 8 +- src/realms/realm.rs | 12 +- src/realms/server.rs | 2 +- src/routes/init.rs | 1 + src/routes/v1/auth/login.rs | 397 ++++++++- src/routes/v1/blogs/mod.rs | 1 + src/routes/v1/blogs/verify_login/mod.rs | 3 + .../v1/blogs/verify_login/verify_login.rs | 803 ++++++++++++++++++ src/routes/v1/me/assets/delete.rs | 2 +- src/routes/v1/me/assets/post.rs | 22 +- .../blogs/settings/appearance/fonts/delete.rs | 2 +- .../blogs/settings/appearance/fonts/post.rs | 2 +- .../v1/me/blogs/settings/delete_blog.rs | 2 +- src/routes/v1/me/gallery/post.rs | 2 +- src/test_utils/count_s3_objects.rs | 3 - src/utils/delete_s3_objects.rs | 1 - src/utils/delete_s3_objects_using_prefix.rs | 67 +- tests/tables/blogs/blogs.rs | 54 +- tests/tables/users/users.rs | 54 +- 37 files changed, 1579 insertions(+), 162 deletions(-) create mode 100644 migrations/20250423071930_create_blog_login_tokens_table.down.sql create mode 100644 migrations/20250423071930_create_blog_login_tokens_table.up.sql create mode 100644 src/cron/cleanup_db/fixtures/blog_login_tokens.sql create mode 100644 src/routes/v1/blogs/verify_login/mod.rs create mode 100644 src/routes/v1/blogs/verify_login/verify_login.rs diff --git a/migrations/20250423071930_create_blog_login_tokens_table.down.sql b/migrations/20250423071930_create_blog_login_tokens_table.down.sql new file mode 100644 index 0000000..e0e348f --- /dev/null +++ b/migrations/20250423071930_create_blog_login_tokens_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS blog_login_tokens CASCADE; \ No newline at end of file diff --git a/migrations/20250423071930_create_blog_login_tokens_table.up.sql b/migrations/20250423071930_create_blog_login_tokens_table.up.sql new file mode 100644 index 0000000..b02646b --- /dev/null +++ b/migrations/20250423071930_create_blog_login_tokens_table.up.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS blog_login_tokens +( + -- Hashed token value + id TEXT PRIMARY KEY, + blog_id BIGINT NOT NULL + REFERENCES blogs (id) + ON DELETE CASCADE, + user_id BIGINT NOT NULL + REFERENCES users (id) + ON DELETE CASCADE, + is_persistent_session BOOL NOT NULL DEFAULT TRUE, + expires_at TIMESTAMPTZ NOT NULL, + UNIQUE (blog_id, user_id) +); diff --git a/session/src/config.rs b/session/src/config.rs index 9b92d8e..2197fa9 100644 --- a/session/src/config.rs +++ b/session/src/config.rs @@ -1,9 +1,9 @@ //! Configuration options to tune the behaviour of [`SessionMiddleware`]. use actix_web::cookie::{ - time::Duration, Key, SameSite, + time::Duration, }; use serde::{ Deserialize, @@ -11,8 +11,9 @@ use serde::{ }; use crate::{ - storage::SessionStore, + Session, SessionMiddleware, + storage::SessionStore, }; /// Determines what type of session cookie should be used and how its lifecycle should be managed. @@ -109,7 +110,7 @@ impl SessionMiddlewareBuilder { /// Use `None` to leave the attribute unspecified. If unspecified, the attribute defaults /// to the same host that set the cookie, excluding subdomains. /// - /// By default, the attribute is left unspecified. + /// Note: Value set by [Session::set_cookie_domain] will override this value. pub fn cookie_domain(mut self, domain: Option) -> Self { self.configuration.cookie.domain = domain; self diff --git a/session/src/lib.rs b/session/src/lib.rs index 5a94e00..8c83ff6 100644 --- a/session/src/lib.rs +++ b/session/src/lib.rs @@ -61,6 +61,9 @@ pub mod test_helpers { mod acceptance_tests { use actix_web::{ + App, + HttpResponse, + Result, cookie::time, dev::{ Service, @@ -71,31 +74,28 @@ pub mod test_helpers { test, web::{ self, + Bytes, get, post, resource, - Bytes, }, - App, - HttpResponse, - Result, }; use serde::{ Deserialize, Serialize, }; use serde_json::{ - json, Value, + json, }; use crate::{ - config::SessionLifecycle, - storage::SessionStore, - test_helpers::key, Session, SessionExt, SessionMiddleware, + config::SessionLifecycle, + storage::SessionStore, + test_helpers::key, }; pub(super) async fn basic_workflow(store_builder: F) diff --git a/session/src/middleware.rs b/session/src/middleware.rs index 8cc6159..582bf28 100644 --- a/session/src/middleware.rs +++ b/session/src/middleware.rs @@ -1,4 +1,6 @@ use crate::{ + Session, + SessionStatus, config::{ self, Configuration, @@ -11,14 +13,13 @@ use crate::{ SessionKey, SessionStore, }, - Session, - SessionStatus, }; use actix_utils::future::{ - ready, Ready, + ready, }; use actix_web::{ + HttpResponse, body::MessageBody, cookie::{ Cookie, @@ -26,18 +27,17 @@ use actix_web::{ Key, }, dev::{ - forward_ready, ResponseHead, Service, ServiceRequest, ServiceResponse, Transform, + forward_ready, }, http::header::{ HeaderValue, SET_COOKIE, }, - HttpResponse, }; use anyhow::Context; use serde_json::{ @@ -135,6 +135,7 @@ fn e500(err: E) -> actix_web::Error { } static LIFECYCLE_KEY: &str = "lifecycle"; +static DOMAIN_KEY: &str = "cookie_domain"; #[doc(hidden)] #[non_exhaustive] @@ -167,6 +168,7 @@ where let (session_key, session_state) = load_session_state(session_key, storage_backend.as_ref()).await?; let mut session_lifecycle = SessionLifecycle::PersistentSession; + let mut cookie_domain: Option = None; if let Some(lifecycle) = session_state.get(LIFECYCLE_KEY) { let lifecycle = lifecycle @@ -176,18 +178,28 @@ where session_lifecycle = SessionLifecycle::from_i32(lifecycle as i32); } - Session::set_session(&mut req, session_state, session_lifecycle); + if let Some(domain) = session_state.get(DOMAIN_KEY) { + let domain = domain.as_str(); + cookie_domain = domain.map(|val| val.to_string()); + } + + Session::set_session(&mut req, session_state, session_lifecycle, cookie_domain); let mut res = service.call(req).await?; - let (lifecycle, status, mut session_state) = Session::get_changes(&mut res); + let (lifecycle, cookie_domain, status, mut session_state) = + Session::get_changes(&mut res); - // We only insert the lifecycle key into the session if it already exist or is non-empty - // to avoid creating sessions on every request. + // We only insert the dynamic properties into the session if they already exist or are + // non-empty to avoid creating sessions on every request. if session_key.is_some() || !session_state.is_empty() { session_state.insert( LIFECYCLE_KEY.to_string(), Value::from(lifecycle.clone() as i32), ); + + if let Some(domain) = cookie_domain.clone() { + session_state.insert(DOMAIN_KEY.to_string(), Value::from(domain)); + } } match session_key { @@ -205,6 +217,7 @@ where session_key, &configuration.cookie, lifecycle, + cookie_domain, ) .map_err(e500)?; } @@ -227,6 +240,7 @@ where session_key, &configuration.cookie, lifecycle, + cookie_domain, ) .map_err(e500)?; } @@ -236,6 +250,7 @@ where delete_session_cookie( res.response_mut().head_mut(), + cookie_domain, &configuration.cookie, ) .map_err(e500)?; @@ -254,6 +269,7 @@ where session_key, &configuration.cookie, lifecycle, + cookie_domain, ) .map_err(e500)?; } @@ -354,6 +370,7 @@ fn set_session_cookie( session_key: SessionKey, config: &CookieConfiguration, session_lifecycle: SessionLifecycle, + domain: Option, ) -> Result<(), anyhow::Error> { let value: String = session_key.into(); let mut cookie = Cookie::new(config.name.clone(), value); @@ -370,8 +387,8 @@ fn set_session_cookie( } } - if let Some(ref domain) = config.domain { - cookie.set_domain(domain.clone()); + if let Some(domain) = domain.clone().or_else(|| config.domain.clone()) { + cookie.set_domain(domain); } let mut jar = CookieJar::new(); @@ -389,6 +406,7 @@ fn set_session_cookie( fn delete_session_cookie( response: &mut ResponseHead, + domain: Option, config: &CookieConfiguration, ) -> Result<(), anyhow::Error> { let removal_cookie = Cookie::build(config.name.clone(), "") @@ -397,7 +415,8 @@ fn delete_session_cookie( .http_only(config.http_only) .same_site(config.same_site); - let mut removal_cookie = if let Some(ref domain) = config.domain { + let mut removal_cookie = if let Some(domain) = domain.clone().or_else(|| config.domain.clone()) + { removal_cookie.domain(domain) } else { removal_cookie diff --git a/session/src/session.rs b/session/src/session.rs index dc75429..2a9e6f2 100644 --- a/session/src/session.rs +++ b/session/src/session.rs @@ -1,9 +1,14 @@ use crate::config::SessionLifecycle; use actix_utils::future::{ - ready, Ready, + ready, }; use actix_web::{ + FromRequest, + HttpMessage, + HttpRequest, + HttpResponse, + ResponseError, body::BoxBody, dev::{ Extensions, @@ -12,11 +17,6 @@ use actix_web::{ ServiceResponse, }, error::Error, - FromRequest, - HttpMessage, - HttpRequest, - HttpResponse, - ResponseError, }; use anyhow::Context; use derive_more::{ @@ -95,6 +95,7 @@ struct SessionInner { state: Map, status: SessionStatus, lifecycle: SessionLifecycle, + cookie_domain: Option, } impl Session { @@ -208,6 +209,25 @@ impl Session { inner.lifecycle.to_owned() } + /// Sets the domain of the session cookie. + pub fn set_cookie_domain(&self, domain: Option) { + let mut inner = self.0.borrow_mut(); + + if inner.status != SessionStatus::Purged { + if inner.status != SessionStatus::Renewed { + inner.status = SessionStatus::Changed; + } + + inner.cookie_domain = domain; + } + } + + /// Returns the domain of the session cookie. + pub fn get_cookie_domain(&self) -> Option { + let inner = self.0.borrow_mut(); + inner.cookie_domain.to_owned() + } + /// Adds the given key-value pairs to the session on the request. /// /// Values that match keys already existing on the session will be overwritten. @@ -216,14 +236,17 @@ impl Session { req: &mut ServiceRequest, data: impl IntoIterator, lifecycle: SessionLifecycle, + cookie_domain: Option, ) { let session = Session::get_session(&mut req.extensions_mut()); let mut inner = session.0.borrow_mut(); inner.state.extend(data); inner.lifecycle = lifecycle; + inner.cookie_domain = cookie_domain; } - /// Returns session lifecycle, session status, and iterator of key-value pairs of changes. + /// Returns session lifecycle, session cookie domain, session status, and iterator of key-value + /// pairs of changes. /// /// This is a destructive operation - the session state is removed from the request extensions /// type-map, leaving behind a new empty map. It should only be used when the session is being @@ -231,7 +254,12 @@ impl Session { #[allow(clippy::needless_pass_by_ref_mut)] pub(crate) fn get_changes( res: &mut ServiceResponse, - ) -> (SessionLifecycle, SessionStatus, Map) { + ) -> ( + SessionLifecycle, + Option, + SessionStatus, + Map, + ) { if let Some(s_impl) = res .request() .extensions() @@ -240,12 +268,14 @@ impl Session { let state = mem::take(&mut s_impl.borrow_mut().state); ( s_impl.borrow().lifecycle.clone(), + s_impl.borrow().cookie_domain.clone(), s_impl.borrow().status.clone(), state, ) } else { ( SessionLifecycle::PersistentSession, + None, SessionStatus::Unchanged, Map::new(), ) diff --git a/session/src/session_ext.rs b/session/src/session_ext.rs index f78c36d..7aa7987 100644 --- a/session/src/session_ext.rs +++ b/session/src/session_ext.rs @@ -1,15 +1,14 @@ +use crate::Session; use actix_web::{ + HttpMessage, + HttpRequest, dev::{ ServiceRequest, ServiceResponse, }, guard::GuardContext, - HttpMessage, - HttpRequest, }; -use crate::Session; - /// Extract a [`Session`] object from various `actix-web` types (e.g. `HttpRequest`, /// `ServiceRequest`, `ServiceResponse`). pub trait SessionExt { diff --git a/src/cron/cleanup_blogs/cleanup_blogs.rs b/src/cron/cleanup_blogs/cleanup_blogs.rs index 7cbd655..d1b08bd 100644 --- a/src/cron/cleanup_blogs/cleanup_blogs.rs +++ b/src/cron/cleanup_blogs/cleanup_blogs.rs @@ -218,7 +218,7 @@ mod tests { } async fn teardown(self) { - delete_s3_objects_using_prefix(&self.s3_client, S3_FONTS_BUCKET, None, None) + delete_s3_objects_using_prefix(&self.s3_client, S3_FONTS_BUCKET, None) .await .unwrap(); } diff --git a/src/cron/cleanup_db/cleanup_db.rs b/src/cron/cleanup_db/cleanup_db.rs index d652be0..384eac2 100644 --- a/src/cron/cleanup_db/cleanup_db.rs +++ b/src/cron/cleanup_db/cleanup_db.rs @@ -147,6 +147,32 @@ WHERE expires_at < NOW() Ok(()) } +/// Cleans the `blog_login_tokens` table based on the conditions specified in [cleanup_db]. +/// +/// * `db_pool` - The Postgres connection pool. +#[tracing::instrument(skip_all, err)] +async fn clean_blog_login_tokens(db_pool: &Pool) -> Result<(), Error> { + trace!("attempting to clean the `blog_login_tokens` table..."); + + let delete_tokens_result = sqlx::query( + r#" +DELETE FROM blog_login_tokens +WHERE expires_at < NOW() +"#, + ) + .execute(db_pool) + .await + .map_err(|err| Box::from(err.to_string())) + .map_err(Error::Failed)?; + + debug!( + "deleted {} rows from the `blog_login_tokens` table", + delete_tokens_result.rows_affected() + ); + + Ok(()) +} + /// Cleans the `user_statuses` table based on the conditions specified in [cleanup_db]. /// /// * `db_pool` - The Postgres connection pool. @@ -215,8 +241,8 @@ WHERE NOT EXISTS ( /// permanently deleted. This would also permanently delete the relations that the story holds; /// such as likes, comments, and replies. /// -/// - Expired tokens, newsletter tokens, and user statuses (based on the `expires_at` column) will -/// be permanently deleted. +/// - Expired tokens, newsletter tokens, blog login tokens, and user statuses (based on the +/// `expires_at` column) will be permanently deleted. /// /// - Notifications that do not have any related row in `notification_outs` table will be /// permanently deleted. @@ -233,10 +259,11 @@ pub async fn cleanup_db( clean_users(db_pool).await?; // These can be run in parallel as they do not depend on each other. - future::try_join4( + future::try_join5( clean_stories(db_pool), clean_tokens(db_pool), clean_newsletter_tokens(db_pool), + clean_blog_login_tokens(db_pool), clean_user_statuses(db_pool), ) .await?; @@ -709,6 +736,77 @@ WHERE id = (SELECT id FROM selected_token) Ok(()) } + // Blog login tokens + + #[sqlx::test(fixtures("blog_login_tokens"))] + async fn can_clean_blog_login_tokens_table(pool: PgPool) -> sqlx::Result<()> { + let mut conn = pool.acquire().await?; + + // Rows should be present initially. + let result = sqlx::query(r#"SELECT 1 FROM blog_login_tokens"#) + .fetch_all(&mut *conn) + .await?; + + assert!(!result.is_empty()); + + let state = get_cron_job_state_for_test(pool, None).await; + let result = cleanup_db(DatabaseCleanupJob(Utc::now()), state).await; + + assert!(result.is_ok()); + + // Rows should get deleted from the database. + let result = sqlx::query(r#"SELECT 1 FROM blog_login_tokens"#) + .fetch_all(&mut *conn) + .await?; + + assert!(result.is_empty()); + + Ok(()) + } + + #[sqlx::test(fixtures("blog_login_tokens"))] + async fn should_not_delete_non_expired_blog_login_tokens(pool: PgPool) -> sqlx::Result<()> { + let mut conn = pool.acquire().await?; + + // Rows should be present initially. + let result = sqlx::query(r#"SELECT 1 FROM blog_login_tokens"#) + .fetch_all(&mut *conn) + .await?; + + assert!(!result.is_empty()); + + // Update `expires_at` for a single token. + let result = sqlx::query( + r#" +WITH selected_token AS ( + SELECT id FROM blog_login_tokens + LIMIT 1 +) +UPDATE blog_login_tokens +SET expires_at = NOW() + INTERVAL '7 days' +WHERE id = (SELECT id FROM selected_token) +"#, + ) + .execute(&mut *conn) + .await?; + + assert_eq!(result.rows_affected(), 1); + + let state = get_cron_job_state_for_test(pool, None).await; + let result = cleanup_db(DatabaseCleanupJob(Utc::now()), state).await; + + assert!(result.is_ok()); + + // Exactly one row should be present in the database. + let result = sqlx::query(r#"SELECT 1 FROM blog_login_tokens"#) + .fetch_all(&mut *conn) + .await?; + + assert_eq!(result.len(), 1); + + Ok(()) + } + // User statuses #[sqlx::test(fixtures("user_statuses"))] diff --git a/src/cron/cleanup_db/fixtures/blog_login_tokens.sql b/src/cron/cleanup_db/fixtures/blog_login_tokens.sql new file mode 100644 index 0000000..1d3558d --- /dev/null +++ b/src/cron/cleanup_db/fixtures/blog_login_tokens.sql @@ -0,0 +1,18 @@ +WITH inserted_user AS ( + INSERT INTO users (name, username, email) VALUES ('Sample user', 'sample_user', 'sample@example.com') + RETURNING id + ), + inserted_blog AS ( + INSERT INTO blogs (name, slug, user_id) + VALUES ('Sample blog', 'sample-blog', (SELECT id FROM inserted_user)) + RETURNING id + ) +INSERT +INTO + blog_login_tokens (id, user_id, blog_id, expires_at) +SELECT uuid_generate_v4(), + (SELECT id FROM inserted_user), + (SELECT id FROM inserted_blog), + NOW() - INTERVAL '7 days' +FROM + GENERATE_SERIES(1, 5); diff --git a/src/cron/cleanup_s3/cleanup_s3.rs b/src/cron/cleanup_s3/cleanup_s3.rs index 594c559..31ded8f 100644 --- a/src/cron/cleanup_s3/cleanup_s3.rs +++ b/src/cron/cleanup_s3/cleanup_s3.rs @@ -302,8 +302,8 @@ mod tests { async fn teardown(self) { future::try_join( - delete_s3_objects_using_prefix(&self.s3_client, S3_UPLOADS_BUCKET, None, None), - delete_s3_objects_using_prefix(&self.s3_client, S3_DOCS_BUCKET, None, None), + delete_s3_objects_using_prefix(&self.s3_client, S3_UPLOADS_BUCKET, None), + delete_s3_objects_using_prefix(&self.s3_client, S3_DOCS_BUCKET, None), ) .await .unwrap(); diff --git a/src/cron/sitemap/sitemap.rs b/src/cron/sitemap/sitemap.rs index b4fede7..6ac4bd7 100644 --- a/src/cron/sitemap/sitemap.rs +++ b/src/cron/sitemap/sitemap.rs @@ -57,11 +57,10 @@ pub async fn refresh_sitemap( info!("attempting to refresh sitemaps"); let s3_client = &state.s3_client; - let deleted_sitemaps = - delete_s3_objects_using_prefix(s3_client, S3_SITEMAPS_BUCKET, None, None) - .await - .map_err(|err| Box::from(err.to_string())) - .map_err(Error::Failed)?; + let deleted_sitemaps = delete_s3_objects_using_prefix(s3_client, S3_SITEMAPS_BUCKET, None) + .await + .map_err(|err| Box::from(err.to_string())) + .map_err(Error::Failed)?; debug!("deleted {} old sitemap files", deleted_sitemaps); @@ -244,7 +243,7 @@ mod tests { } async fn teardown(self) { - delete_s3_objects_using_prefix(&self.s3_client, S3_SITEMAPS_BUCKET, None, None) + delete_s3_objects_using_prefix(&self.s3_client, S3_SITEMAPS_BUCKET, None) .await .unwrap(); } diff --git a/src/cron/sitemap/story/story.rs b/src/cron/sitemap/story/story.rs index ae0282c..f506e21 100644 --- a/src/cron/sitemap/story/story.rs +++ b/src/cron/sitemap/story/story.rs @@ -219,7 +219,6 @@ mod tests { &self.s3_client, S3_SITEMAPS_BUCKET, Some("stories-".to_string()), - None, ) .await .unwrap(); diff --git a/src/cron/sitemap/tag/tag.rs b/src/cron/sitemap/tag/tag.rs index 89e6d10..9ea9ca3 100644 --- a/src/cron/sitemap/tag/tag.rs +++ b/src/cron/sitemap/tag/tag.rs @@ -186,7 +186,6 @@ mod tests { &self.s3_client, S3_SITEMAPS_BUCKET, Some("tags-".to_string()), - None, ) .await .unwrap(); diff --git a/src/cron/sitemap/user/user.rs b/src/cron/sitemap/user/user.rs index 2198234..bf57953 100644 --- a/src/cron/sitemap/user/user.rs +++ b/src/cron/sitemap/user/user.rs @@ -236,7 +236,6 @@ mod tests { &self.s3_client, S3_SITEMAPS_BUCKET, Some("users-".to_string()), - None, ) .await .unwrap(); diff --git a/src/middlewares/identity/error.rs b/src/middlewares/identity/error.rs index e911f8f..89249ad 100644 --- a/src/middlewares/identity/error.rs +++ b/src/middlewares/identity/error.rs @@ -1,9 +1,9 @@ //! Failure modes of identity operations. use actix_web::{ + ResponseError, cookie::time::error::ComponentRange, http::StatusCode, - ResponseError, }; use derive_more::{ Display, diff --git a/src/middlewares/identity/identity.rs b/src/middlewares/identity/identity.rs index 9bd4d98..a7fc159 100644 --- a/src/middlewares/identity/identity.rs +++ b/src/middlewares/identity/identity.rs @@ -6,20 +6,20 @@ use super::error::{ SessionExpiryError, }; use actix_utils::future::{ - ready, Ready, + ready, }; use actix_web::{ - cookie::time::OffsetDateTime, - dev::{ - Extensions, - Payload, - }, Error, FromRequest, HttpMessage, HttpRequest, HttpResponse, + cookie::time::OffsetDateTime, + dev::{ + Extensions, + Payload, + }, }; use serde_json::Value; use storiny_session::Session; diff --git a/src/middlewares/identity/identity_ext.rs b/src/middlewares/identity/identity_ext.rs index e474248..98b9d6d 100644 --- a/src/middlewares/identity/identity_ext.rs +++ b/src/middlewares/identity/identity_ext.rs @@ -1,14 +1,13 @@ -use actix_web::{ - dev::ServiceRequest, - guard::GuardContext, - HttpMessage, - HttpRequest, -}; - use super::{ error::GetIdentityError, identity::Identity, }; +use actix_web::{ + HttpMessage, + HttpRequest, + dev::ServiceRequest, + guard::GuardContext, +}; /// Helper trait to retrieve an [`Identity`] instance from various `actix-web`'s types. pub trait IdentityExt { diff --git a/src/middlewares/identity/middleware.rs b/src/middlewares/identity/middleware.rs index 3388ed8..97b9ef0 100644 --- a/src/middlewares/identity/middleware.rs +++ b/src/middlewares/identity/middleware.rs @@ -3,10 +3,13 @@ use super::{ identity::IdentityInner, }; use actix_utils::future::{ - ready, Ready, + ready, }; use actix_web::{ + Error, + HttpMessage as _, + Result, body::MessageBody, dev::{ Service, @@ -14,9 +17,6 @@ use actix_web::{ ServiceResponse, Transform, }, - Error, - HttpMessage as _, - Result, }; use futures_core::future::LocalBoxFuture; use std::rc::Rc; diff --git a/src/realms/realm.rs b/src/realms/realm.rs index 261f46d..a4ce610 100644 --- a/src/realms/realm.rs +++ b/src/realms/realm.rs @@ -6,12 +6,12 @@ use super::{ }, }; use crate::{ + S3Client, constants::buckets::S3_DOCS_BUCKET, utils::deflate_bytes_gzip::{ - deflate_bytes_gzip, CompressionLevel, + deflate_bytes_gzip, }, - S3Client, }; use hashbrown::HashMap; use lockable::{ @@ -34,9 +34,9 @@ use tokio::{ }, task::JoinHandle, time::{ + Duration, interval, timeout, - Duration, }, }; use tracing::{ @@ -672,8 +672,8 @@ mod tests { }; use crate::{ test_utils::{ - get_s3_client, TestContext, + get_s3_client, }, utils::delete_s3_objects_using_prefix::delete_s3_objects_using_prefix, }; @@ -684,12 +684,12 @@ mod tests { use sqlx::PgPool; use storiny_macros::test_context; use yrs::{ + Doc, encoding::read::{ Cursor, Read, }, updates::decoder::DecoderV1, - Doc, }; /// Initializes and returns a tuple consisting of a realm map and a realm instance. @@ -735,7 +735,7 @@ mod tests { } async fn teardown(self) { - delete_s3_objects_using_prefix(&self.s3_client, S3_DOCS_BUCKET, None, None) + delete_s3_objects_using_prefix(&self.s3_client, S3_DOCS_BUCKET, None) .await .unwrap(); } diff --git a/src/realms/server.rs b/src/realms/server.rs index d670cf1..4e20079 100644 --- a/src/realms/server.rs +++ b/src/realms/server.rs @@ -785,7 +785,7 @@ pub mod tests { .expect("failed to FLUSHDB"); }, async { - delete_s3_objects_using_prefix(&self.s3_client, S3_DOCS_BUCKET, None, None) + delete_s3_objects_using_prefix(&self.s3_client, S3_DOCS_BUCKET, None) .await .unwrap() }, diff --git a/src/routes/init.rs b/src/routes/init.rs index 14e8066..42ee562 100644 --- a/src/routes/init.rs +++ b/src/routes/init.rs @@ -65,6 +65,7 @@ pub fn init_v1_routes(cfg: &mut web::ServiceConfig) { v1::blogs::writers::init_routes(cfg); v1::blogs::feed::init_routes(cfg); v1::blogs::subscribe::init_routes(cfg); + v1::blogs::verify_login::init_routes(cfg); // Me v1::me::get::init_routes(cfg); // Me - User activity diff --git a/src/routes/v1/auth/login.rs b/src/routes/v1/auth/login.rs index 3b08bc6..9e8fe4e 100644 --- a/src/routes/v1/auth/login.rs +++ b/src/routes/v1/auth/login.rs @@ -1,5 +1,7 @@ use crate::{ + AppState, constants::{ + blog_domain_regex::BLOG_DOMAIN_REGEX, notification_entity_type::NotificationEntityType, resource_lock::ResourceLock, user_flag::UserFlag, @@ -16,6 +18,7 @@ use crate::{ Flag, Mask, }, + generate_hashed_token::generate_hashed_token, generate_totp::generate_totp, get_client_device::get_client_device, get_client_location::get_client_location, @@ -24,15 +27,14 @@ use crate::{ is_resource_locked::is_resource_locked, reset_resource_lock::reset_resource_lock, }, - AppState, }; use actix_http::HttpMessage; use actix_web::{ + HttpRequest, + HttpResponse, http::StatusCode, post, web, - HttpRequest, - HttpResponse, }; use actix_web_validator::{ Json, @@ -47,13 +49,20 @@ use serde::{ Deserialize, Serialize, }; -use sqlx::Row; +use sqlx::{ + Postgres, + Row, + Transaction, +}; use std::net::IpAddr; use storiny_session::{ - config::SessionLifecycle, Session, + config::SessionLifecycle, +}; +use time::{ + Duration, + OffsetDateTime, }; -use time::OffsetDateTime; use totp_rs::Secret; use tracing::{ debug, @@ -63,22 +72,28 @@ use tracing::{ use url::Url; use validator::Validate; +// This struct is public because it is used by `blogs/verify-login` route. #[derive(Debug, Serialize, Deserialize, Validate)] -struct Request { +pub struct Request { #[validate(email(message = "Invalid e-mail"))] #[validate(length(min = 3, max = 300, message = "Invalid e-mail length"))] - email: String, + pub email: String, #[validate(length(min = 6, max = 64, message = "Invalid password length"))] - password: String, - remember_me: bool, + pub password: String, + pub remember_me: bool, #[validate(length(min = 6, max = 12, message = "Invalid authentication code"))] - code: Option, + pub code: Option, + #[validate(regex = "BLOG_DOMAIN_REGEX")] + #[validate(length(min = 3, max = 512, message = "Invalid blog domain"))] + pub blog_domain: Option, } -#[derive(Debug, Clone, Serialize)] -struct Response { - result: String, - is_first_login: bool, +// This struct is public because it is used by `blogs/verify-login` route. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Response { + pub result: String, + pub is_first_login: bool, + pub blog_token: Option, } #[derive(Deserialize, Validate)] @@ -93,6 +108,7 @@ struct QueryParams { fields( email = %payload.email, remember_me = %payload.remember_me, + blog_domain = %payload.blog_domain, bypass = query.bypass ) )] @@ -307,6 +323,7 @@ WHERE code = $1 AND user_id = $2 return Ok(HttpResponse::Ok().json(Response { result: "suspended".to_string(), is_first_login, + blog_token: None, })); } } @@ -335,6 +352,7 @@ WHERE id = $1 return Ok(HttpResponse::Ok().json(Response { result: "held_for_deletion".to_string(), is_first_login, + blog_token: None, })); } } @@ -364,6 +382,7 @@ WHERE id = $1 return Ok(HttpResponse::Ok().json(Response { result: "deactivated".to_string(), is_first_login, + blog_token: None, })); } } @@ -379,6 +398,30 @@ WHERE id = $1 return Ok(HttpResponse::Ok().json(Response { result: "email_confirmation".to_string(), is_first_login, + blog_token: None, + })); + } + } + + // Handle blog login + if let Some(domain) = &payload.blog_domain { + if let Some(login_token) = handle_blog_login( + domain, + user_id, + payload.remember_me, + &data.config.token_salt, + &mut txn, + ) + .await? + { + txn.commit().await?; + + debug!("blog login token generated"); + + return Ok(HttpResponse::Ok().json(Response { + result: "success".to_string(), + is_first_login: false, + blog_token: Some(login_token), })); } } @@ -479,7 +522,7 @@ SELECT $2, (SELECT id FROM inserted_notification), $3 match clear_user_sessions(&data.redis, user_id).await { Ok(_) => { debug!( - "cleared {} ovreflowing sessions for the user", + "cleared {} overflowing sessions for the user", sessions.len() ); } @@ -500,7 +543,7 @@ SELECT $2, (SELECT id FROM inserted_notification), $3 } }; - let login_result = Identity::login(&req.extensions(), user.get::("id")); + let login_result = Identity::login(&req.extensions(), user_id); match login_result { Ok(_) => { @@ -511,6 +554,7 @@ SELECT $2, (SELECT id FROM inserted_notification), $3 Ok(HttpResponse::Ok().json(Response { result: "success".to_string(), is_first_login, + blog_token: None, })) } Err(error) => Err(AppError::InternalError(format!( @@ -520,12 +564,84 @@ SELECT $2, (SELECT id FROM inserted_notification), $3 } } +/// Generates a login token for an external blog. +/// +/// * `domain` - The blog's domain name. +/// * `user_id` - The ID of the user trying to log in. +/// * `token_salt` - The salt for generating login token. +/// * `persistent` - Whether to use a persistent browser session after login. +/// * `txn` - The Postgres transaction. +async fn handle_blog_login<'a>( + domain: &str, + user_id: i64, + persistent: bool, + token_salt: &str, + txn: &mut Transaction<'a, Postgres>, +) -> Result, AppError> { + let blog_result = sqlx::query( + r#" +SELECT b.id +FROM blogs b +WHERE + b.domain = $1 + AND b.deleted_at IS NULL +"#, + ) + .bind(domain.to_string()) + .fetch_one(&mut **txn) + .await; + + match blog_result { + Ok(blog) => { + let blog_id = blog.get::("id"); + + // Generate a new login token. + let (token_id, hashed_token) = generate_hashed_token(token_salt)?; + + // Delete old tokens + sqlx::query( + r#" +DELETE FROM blog_login_tokens +WHERE blog_id = $1 AND user_id = $2 +"#, + ) + .bind(blog_id) + .bind(user_id) + .execute(&mut **txn) + .await?; + + sqlx::query( + r#" +INSERT INTO blog_login_tokens (id, blog_id, user_id, is_persistent_session, expires_at) +VALUES ($1, $2, $3, $4, $5) +"#, + ) + .bind(&hashed_token) + .bind(blog_id) + .bind(user_id) + .bind(persistent) + .bind(OffsetDateTime::now_utc() + Duration::minutes(3)) // 3 minutes + .execute(&mut **txn) + .await?; + + Ok(Some(token_id)) + } + Err(error) => { + if matches!(error, sqlx::Error::RowNotFound) { + return Ok(None); + } + + Err(AppError::from(error)) + } + } +} + pub fn init_routes(cfg: &mut web::ServiceConfig) { cfg.service(post); } #[cfg(test)] -mod tests { +pub mod tests { use super::*; use crate::{ constants::{ @@ -533,13 +649,14 @@ mod tests { session_cookie::SESSION_COOKIE_NAME, }, test_utils::{ + RedisTestContext, assert_form_error_response, assert_response_body_text, assert_toast_error_response, exceed_resource_lock_attempts, get_resource_lock_attempts, init_app_for_test, - RedisTestContext, + res_to_string, }, utils::{ get_client_device::ClientDevice, @@ -548,17 +665,17 @@ mod tests { }, }; use actix_web::{ + Responder, get, services, test, - Responder, }; use argon2::{ + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - PasswordHasher, }; use redis::AsyncCommands; use serde_json::json; @@ -573,7 +690,7 @@ mod tests { /// Returns the device and session data present in the session for testing. #[get("/get-login-details")] - async fn get(session: Session) -> impl Responder { + pub async fn get(session: Session) -> impl Responder { let location = session.get::("location").unwrap(); let device = session.get::("device").unwrap(); let domain = session.get::("domain").unwrap(); @@ -586,7 +703,7 @@ mod tests { } /// Returns a sample email and hashed password. - fn get_sample_email_and_password() -> (String, String, String) { + pub fn get_sample_email_and_password() -> (String, String, String) { let password = "sample"; let email = "someone@example.com"; let salt = SaltString::generate(&mut OsRng); @@ -626,6 +743,7 @@ VALUES ($1, $2, $3, $4, TRUE) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -636,6 +754,7 @@ VALUES ($1, $2, $3, $4, TRUE) &serde_json::to_string(&Response { result: "success".to_string(), is_first_login: true, // Should be `true`. + blog_token: None, }) .unwrap_or_default(), ) @@ -724,6 +843,7 @@ VALUES ($1, $2) password: password.to_string(), remember_me: true, code: Some("0".repeat(12)), + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -734,6 +854,7 @@ VALUES ($1, $2) &serde_json::to_string(&Response { result: "success".to_string(), is_first_login: true, + blog_token: None, }) .unwrap_or_default(), ) @@ -797,6 +918,7 @@ VALUES ($1, $2, $3, $4, TRUE, TRUE, $5) password: password.to_string(), remember_me: true, code: Some(totp.generate_current().unwrap()), + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -807,6 +929,7 @@ VALUES ($1, $2, $3, $4, TRUE, TRUE, $5) &serde_json::to_string(&Response { result: "success".to_string(), is_first_login: true, + blog_token: None, }) .unwrap_or_default(), ) @@ -843,6 +966,7 @@ VALUES ($1, $2, $3, $4, TRUE, NOW()) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -853,6 +977,7 @@ VALUES ($1, $2, $3, $4, TRUE, NOW()) &serde_json::to_string(&Response { result: "success".to_string(), is_first_login: false, // Should be false. + blog_token: None, }) .unwrap_or_default(), ) @@ -889,6 +1014,7 @@ VALUES ($1, $2, $3, $4) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -925,6 +1051,7 @@ VALUES ($1, $2, $3) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -968,6 +1095,7 @@ VALUES ($1, $2, $3, $4, $5) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -978,6 +1106,7 @@ VALUES ($1, $2, $3, $4, $5) &serde_json::to_string(&Response { result: "suspended".to_string(), is_first_login: true, + blog_token: None, }) .unwrap(), ) @@ -1019,6 +1148,7 @@ VALUES ($1, $2, $3, $4, $5) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1029,6 +1159,7 @@ VALUES ($1, $2, $3, $4, $5) &serde_json::to_string(&Response { result: "suspended".to_string(), is_first_login: true, + blog_token: None, }) .unwrap(), ) @@ -1079,6 +1210,7 @@ WHERE email = $1 password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1089,6 +1221,7 @@ WHERE email = $1 &serde_json::to_string(&Response { result: "deactivated".to_string(), is_first_login: true, + blog_token: None, }) .unwrap(), ) @@ -1139,6 +1272,7 @@ WHERE email = $1 password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1149,6 +1283,7 @@ WHERE email = $1 &serde_json::to_string(&Response { result: "held_for_deletion".to_string(), is_first_login: true, + blog_token: None, }) .unwrap(), ) @@ -1187,6 +1322,7 @@ VALUES ($1, $2, $3, $4) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1197,6 +1333,7 @@ VALUES ($1, $2, $3, $4) &serde_json::to_string(&Response { result: "email_confirmation".to_string(), is_first_login: true, + blog_token: None, }) .unwrap(), ) @@ -1234,6 +1371,7 @@ VALUES ($1, $2, $3, TRUE) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1273,6 +1411,7 @@ VALUES ($1, $2, $3, TRUE) password: password.to_string(), remember_me: true, code: Some("0".repeat(7)), + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1315,6 +1454,7 @@ VALUES ($1, $2, $3, $4, TRUE) password: password.to_string(), remember_me: false, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1364,6 +1504,7 @@ VALUES ($1, $2, $3, $4, TRUE) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1423,6 +1564,7 @@ WHERE email = $1 password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1433,6 +1575,7 @@ WHERE email = $1 &serde_json::to_string(&Response { result: "success".to_string(), is_first_login: true, + blog_token: None, }) .unwrap_or_default(), ) @@ -1502,6 +1645,7 @@ WHERE email = $1 password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1512,6 +1656,7 @@ WHERE email = $1 &serde_json::to_string(&Response { result: "success".to_string(), is_first_login: true, + blog_token: None, }) .unwrap_or_default(), ) @@ -1537,6 +1682,199 @@ WHERE email = $1 Ok(()) } + // Blog login + + #[sqlx::test] + async fn can_handle_invalid_blog(pool: PgPool) -> sqlx::Result<()> { + let mut conn = pool.acquire().await?; + let app = init_app_for_test(post, pool, false, false, None).await.0; + + let (email, password_hash, password) = get_sample_email_and_password(); + + // Insert the user. + sqlx::query( + r#" +INSERT INTO users (name, username, email, password, email_verified) +VALUES ($1, $2, $3, $4, TRUE) +"#, + ) + .bind("Sample user".to_string()) + .bind("sample_user".to_string()) + .bind(email.to_string()) + .bind(password_hash) + .execute(&mut *conn) + .await?; + + let req = test::TestRequest::post() + .uri("/v1/auth/login") + .set_json(Request { + email: email.to_string(), + password: password.to_string(), + remember_me: true, + code: None, + blog_domain: Some("test.com".to_string()), + }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + assert_response_body_text( + res, + &serde_json::to_string(&Response { + result: "success".to_string(), + is_first_login: true, // Should be `true`. + blog_token: None, + }) + .unwrap_or_default(), + ) + .await; + + // Should also insert a notification. + let result = sqlx::query( + r#" +SELECT EXISTS ( + SELECT + 1 + FROM + notification_outs + WHERE + notification_id = ( + SELECT id FROM notifications + WHERE entity_type = $1 + ) + ) +"#, + ) + .bind(NotificationEntityType::LoginAttempt as i16) + .fetch_one(&mut *conn) + .await?; + + assert!(result.get::("exists")); + + // Should also update the `last_login_at` column. + let result = sqlx::query( + r#" +SELECT last_login_at FROM users +WHERE username = $1 +"#, + ) + .bind("sample_user") + .fetch_one(&mut *conn) + .await?; + + assert!( + result + .get::, _>("last_login_at") + .is_some() + ); + + Ok(()) + } + + #[sqlx::test] + async fn can_handle_blog_login(pool: PgPool) -> sqlx::Result<()> { + let mut conn = pool.acquire().await?; + let app = init_app_for_test(post, pool, false, false, None).await.0; + + let (email, password_hash, password) = get_sample_email_and_password(); + + // Insert the user and blog. + let blog = sqlx::query( + r#" +WITH inserted_user AS ( + INSERT INTO users (name, username, email, password, email_verified) + VALUES ($1, $2, $3, $4, TRUE) + RETURNING id +) +INSERT INTO blogs (name, slug, domain, user_id) +VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) +RETURNING id +"#, + ) + .bind("Sample user".to_string()) + .bind("sample_user".to_string()) + .bind(email.to_string()) + .bind(password_hash) + .fetch_one(&mut *conn) + .await?; + + let blog_id = blog.get::("id"); + + // With `remember_me` = false + + let req = test::TestRequest::post() + .uri("/v1/auth/login") + .set_json(Request { + email: email.to_string(), + password: password.to_string(), + remember_me: false, + code: None, + blog_domain: Some("test.com".to_string()), + }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + + let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); + + assert!(json.blog_token.is_some()); + assert_eq!(json.result, "success".to_string()); + + // Should insert a blog login token. + let result = sqlx::query( + r#" +SELECT is_persistent_session +FROM blog_login_tokens +WHERE blog_id = $1 +"#, + ) + .bind(blog_id) + .fetch_all(&mut *conn) + .await?; + + assert_eq!(result.len(), 1); + assert_eq!(result[0].get::("is_persistent_session"), false); + + // With `remember_me` = true + + let req = test::TestRequest::post() + .uri("/v1/auth/login") + .set_json(Request { + email: email.to_string(), + password: password.to_string(), + remember_me: true, + code: None, + blog_domain: Some("test.com".to_string()), + }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + + let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); + + assert!(json.blog_token.is_some()); + assert_eq!(json.result, "success".to_string()); + + // `is_persistent_session` should be true, and old token should get deleted. + let result = sqlx::query( + r#" +SELECT is_persistent_session +FROM blog_login_tokens +WHERE blog_id = $1 +"#, + ) + .bind(blog_id) + .fetch_all(&mut *conn) + .await?; + + assert_eq!(result.len(), 1); + assert_eq!(result[0].get::("is_persistent_session"), true); + + Ok(()) + } + mod serial { use super::*; @@ -1572,6 +1910,7 @@ VALUES ($1, $2, $3, $4) password: "some_invalid_password".to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1619,6 +1958,7 @@ VALUES ($1, $2, $3, TRUE) password: password.to_string(), remember_me: true, code: Some("0".repeat(12)), + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1669,6 +2009,7 @@ VALUES ($1, $2, $3, TRUE, $4) password: password.to_string(), remember_me: true, code: Some("0".repeat(6)), + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1731,6 +2072,7 @@ VALUES ($1, $2, NOW()) password: password.to_string(), remember_me: true, code: Some("0".repeat(12)), + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1770,6 +2112,7 @@ VALUES ($1, $2, NOW()) password: "bad_password".to_string(), remember_me: true, code: Some("0".repeat(12)), + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1822,6 +2165,7 @@ VALUES ($1, $2, $3, $4, TRUE) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1898,6 +2242,7 @@ VALUES ($1, $2, $3, $4, $5, TRUE) password: password.to_string(), remember_me: true, code: None, + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -1908,6 +2253,7 @@ VALUES ($1, $2, $3, $4, $5, TRUE) &serde_json::to_string(&Response { result: "success".to_string(), is_first_login: true, + blog_token: None, }) .unwrap_or_default(), ) @@ -1962,7 +2308,8 @@ VALUES ($1, $2, $3, $4, TRUE) email: email.to_string(), password: password.to_string(), remember_me: true, - code: None + code: None, + blog_domain: None }) .to_request(); let res = test::call_service(&app, req).await; diff --git a/src/routes/v1/blogs/mod.rs b/src/routes/v1/blogs/mod.rs index e70db98..463a726 100644 --- a/src/routes/v1/blogs/mod.rs +++ b/src/routes/v1/blogs/mod.rs @@ -2,4 +2,5 @@ pub mod archive; pub mod editors; pub mod feed; pub mod subscribe; +pub mod verify_login; pub mod writers; diff --git a/src/routes/v1/blogs/verify_login/mod.rs b/src/routes/v1/blogs/verify_login/mod.rs new file mode 100644 index 0000000..c5669fd --- /dev/null +++ b/src/routes/v1/blogs/verify_login/mod.rs @@ -0,0 +1,3 @@ +mod verify_login; + +pub use verify_login::*; diff --git a/src/routes/v1/blogs/verify_login/verify_login.rs b/src/routes/v1/blogs/verify_login/verify_login.rs new file mode 100644 index 0000000..fbdef36 --- /dev/null +++ b/src/routes/v1/blogs/verify_login/verify_login.rs @@ -0,0 +1,803 @@ +use crate::{ + AppState, + constants::{ + notification_entity_type::NotificationEntityType, + resource_lock::ResourceLock, + user_flag::UserFlag, + }, + error::{ + AppError, + ToastErrorResponse, + }, + middlewares::identity::identity::Identity, + utils::{ + clear_user_sessions::clear_user_sessions, + flag::Flag, + generate_hashed_token::generate_hashed_token, + generate_totp::generate_totp, + get_client_device::get_client_device, + get_client_location::get_client_location, + get_user_sessions::get_user_sessions, + incr_resource_lock_attempts::incr_resource_lock_attempts, + incr_subscription_limit::incr_subscription_limit, + }, +}; +use actix_http::HttpMessage; +use actix_web::{ + HttpRequest, + HttpResponse, + http::StatusCode, + post, + web, +}; +use actix_web_validator::Json; +use argon2::{ + Argon2, + PasswordHasher, + password_hash::SaltString, +}; +use chrono::Datelike; +use serde::{ + Deserialize, + Serialize, +}; +use sqlx::Row; +use std::net::IpAddr; +use storiny_session::{ + Session, + config::SessionLifecycle, +}; +use time::{ + Duration, + OffsetDateTime, +}; +use totp_rs::Secret; +use tracing::debug; +use url::Url; +use validator::Validate; + +#[derive(Deserialize, Validate)] +struct Fragments { + blog_id: String, +} + +#[derive(Debug, Serialize, Deserialize, Validate)] +struct Request { + #[validate(length(equal = 48, message = "Invalid token length"))] + token: String, +} + +#[derive(Debug, Clone, Serialize)] +struct Response { + result: String, +} + +#[post("/v1/blogs/{blog_id}/verify-login")] +#[tracing::instrument(name = "POST /v1/blogs/{blog_id}/verify-login", skip_all, err)] +async fn post( + req: HttpRequest, + payload: Json, + data: web::Data, + path: web::Path, + session: Session, +) -> Result { + let blog_id = path + .blog_id + .parse::() + .map_err(|_| AppError::from("Invalid blog ID"))?; + let token = &payload.token; + let mut req_host: Option = None; + + if let Some(origin) = req.headers().get(actix_http::header::ORIGIN) { + if let Ok(url) = Url::parse(origin.to_str().unwrap_or_default()) { + if let Some(domain) = url.domain() { + req_host = Some(domain.strip_prefix("www.").unwrap_or(domain).to_string()); + } + } + } + + if req_host.is_none() { + return Err(AppError::InternalError("missing host name".to_string())); + } + + let pg_pool = &data.db_pool; + let mut txn = pg_pool.begin().await?; + + let blog = sqlx::query( + r#" +SELECT domain +FROM blogs +WHERE + id = $1 + AND deleted_at IS NULL +"#, + ) + .bind(blog_id) + .fetch_one(&mut *txn) + .await + .map_err(|error| { + if matches!(error, sqlx::Error::RowNotFound) { + AppError::ToastError(ToastErrorResponse::new(None, "Unknown blog")) + } else { + AppError::SqlxError(error) + } + })?; + + if blog.get::, _>("domain") != req_host { + return Err(AppError::ToastError(ToastErrorResponse::new( + None, + "Blog domain does not match", + ))); + } + + let salt = SaltString::from_b64(&data.config.token_salt) + .map_err(|error| AppError::InternalError(error.to_string()))?; + + let hashed_token = Argon2::default() + .hash_password(token.as_bytes(), &salt) + .map_err(|error| AppError::InternalError(format!("unable to hash the token: {error:?}")))?; + + let (user_id, is_persistent_session) = match sqlx::query( + r#" +DELETE FROM blog_login_tokens +WHERE + id = $1 + AND blog_id = $2 + AND expires_at > NOW() +RETURNING user_id, is_persistent_session +"#, + ) + .bind(hashed_token.to_string()) + .bind(blog_id) + .fetch_one(&mut *txn) + .await + { + Ok(row) => { + let user_id = row.get::("user_id"); + let is_persistent_session = row.get::("is_persistent_session"); + + (user_id, is_persistent_session) + } + Err(error) => { + if matches!(error, sqlx::Error::RowNotFound) { + return Ok(HttpResponse::Ok().json(Response { + result: "invalid_token".to_string(), + })); + } + + return Err(AppError::from(error)); + } + }; + + // Proceed with login + + let mut client_device_value = "Unknown device".to_string(); + let mut client_location_value: Option = None; + + // Insert additional data to the session. + { + if let Ok(domain) = serde_json::to_value(req_host.clone().unwrap_or_default()) { + session.insert("domain", domain); + } + + if let Some(ip) = req.connection_info().realip_remote_addr() { + if let Ok(parsed_ip) = ip.parse::() { + if let Some(client_location_result) = get_client_location(parsed_ip, &data.geo_db) { + client_location_value = Some(client_location_result.display_name.to_string()); + + if let Ok(client_location) = serde_json::to_value(client_location_result) { + session.insert("location", client_location); + } + } + } + } + + if let Some(ua_header) = req.headers().get("user-agent") { + if let Ok(ua) = ua_header.to_str() { + let client_device_result = get_client_device(ua, &data.ua_parser); + client_device_value = client_device_result.display_name.to_string(); + + if let Ok(client_device) = serde_json::to_value(client_device_result) { + session.insert("device", client_device); + } + } + } + } + + // Update the `last_login_at` and insert a login notification for the user. + sqlx::query( + r#" +WITH updated_user AS ( + UPDATE users + SET last_login_at = NOW() + WHERE id = $2 +), +inserted_notification AS ( + INSERT INTO notifications (entity_type) + VALUES ($1) + RETURNING id +) +INSERT +INTO + notification_outs ( + notified_id, + notification_id, + rendered_content + ) +SELECT $2, (SELECT id FROM inserted_notification), $3 +"#, + ) + .bind(NotificationEntityType::LoginAttempt as i16) + .bind(user_id) + .bind(if let Some(location) = client_location_value { + format!("{client_device_value}:{location}") + } else { + client_device_value + }) + .execute(&mut *txn) + .await?; + + // Session cookie domain + session.set_cookie_domain(Some(req_host.unwrap_or_default())); + + // Session lifecycle depends on the `is_persistent_session` value. + session.set_lifecycle(if is_persistent_session { + SessionLifecycle::PersistentSession + } else { + SessionLifecycle::BrowserSession + }); + + // Check if the user maintains more than or equal to 10 sessions, and + // delete all the previous sessions if the current number of active + // sessions for the user exceeds the per user session limit (10). + match get_user_sessions(&data.redis, user_id).await { + Ok(sessions) => { + if sessions.len() >= 10 { + match clear_user_sessions(&data.redis, user_id).await { + Ok(_) => { + debug!( + "cleared {} overflowing sessions for the user", + sessions.len() + ); + } + Err(error) => { + return Err(AppError::InternalError(format!( + "unable to clear the overflowing sessions for the user: {:?}", + error + ))); + } + }; + } + } + Err(error) => { + return Err(AppError::InternalError(format!( + "unable to fetch the sessions for the user: {:?}", + error + ))); + } + }; + + let login_result = Identity::login(&req.extensions(), user_id); + + match login_result { + Ok(_) => { + txn.commit().await?; + + debug!("user logged in to blog"); + + Ok(HttpResponse::Ok().json(Response { + result: "success".to_string(), + })) + } + Err(error) => Err(AppError::InternalError(format!( + "identity error: {:?}", + error + ))), + } +} + +pub fn init_routes(cfg: &mut web::ServiceConfig) { + cfg.service(post); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + config::get_app_config, + constants::{ + redis_namespaces::RedisNamespace, + session_cookie::SESSION_COOKIE_NAME, + }, + routes::init::v1::auth::login::{ + Request as LoginRequest, + Response as LoginResponse, + post as login_post, + tests::{ + get as test_login_get, + get_sample_email_and_password, + }, + }, + test_utils::{ + RedisTestContext, + assert_response_body_text, + assert_toast_error_response, + init_app_for_test, + res_to_string, + }, + utils::{ + get_client_device::ClientDevice, + get_client_location::ClientLocation, + get_user_sessions::UserSession, + }, + }; + use actix_web::{ + Responder, + services, + test, + }; + use argon2::PasswordHasher; + use redis::AsyncCommands; + use sqlx::PgPool; + use std::net::{ + Ipv4Addr, + SocketAddr, + SocketAddrV4, + }; + use storiny_macros::test_context; + use uuid::Uuid; + + #[sqlx::test] + async fn can_verify_login(pool: PgPool) -> sqlx::Result<()> { + let mut conn = pool.acquire().await?; + let app = init_app_for_test(services![login_post, post], pool, false, false, None) + .await + .0; + + let (email, password_hash, password) = get_sample_email_and_password(); + + // Insert the user and blog. + let blog = sqlx::query( + r#" +WITH inserted_user AS ( + INSERT INTO users (name, username, email, password, email_verified) + VALUES ($1, $2, $3, $4, TRUE) + RETURNING id +) +INSERT INTO blogs (name, slug, domain, user_id) +VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) +RETURNING id +"#, + ) + .bind("Sample user".to_string()) + .bind("sample_user".to_string()) + .bind(email.to_string()) + .bind(password_hash) + .fetch_one(&mut *conn) + .await?; + + let blog_id = blog.get::("id"); + + // Send a login request. + let req = test::TestRequest::post() + .uri("/v1/auth/login") + .set_json(LoginRequest { + email: email.to_string(), + password: password.to_string(), + remember_me: true, + code: None, + blog_domain: Some("test.com".to_string()), + }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + + let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); + + assert_eq!(json.result, "success".to_string()); + assert!(json.blog_token.is_some()); + + let login_token = json.blog_token.unwrap(); + + // Verify login token. + let req = test::TestRequest::post() + .append_header(("origin", "https://test.com")) + .uri(&format!("/v1/blogs/{blog_id}/verify-login")) + .set_json(Request { token: login_token }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + assert_response_body_text( + res, + &serde_json::to_string(&Response { + result: "success".to_string(), + }) + .unwrap_or_default(), + ) + .await; + + // Should delete login token. + let result = sqlx::query( + r#" +SELECT EXISTS ( + SELECT + 1 + FROM + blog_login_tokens + WHERE + blog_id = $1 + ) +"#, + ) + .bind(blog_id) + .fetch_one(&mut *conn) + .await?; + + assert!(!result.get::("exists")); + + // Should also insert a notification. + let result = sqlx::query( + r#" +SELECT EXISTS ( + SELECT + 1 + FROM + notification_outs + WHERE + notification_id = ( + SELECT id FROM notifications + WHERE entity_type = $1 + ) + ) +"#, + ) + .bind(NotificationEntityType::LoginAttempt as i16) + .fetch_one(&mut *conn) + .await?; + + assert!(result.get::("exists")); + + // Should also update the `last_login_at` column. + let result = sqlx::query( + r#" +SELECT last_login_at FROM users +WHERE username = $1 +"#, + ) + .bind("sample_user") + .fetch_one(&mut *conn) + .await?; + + assert!( + result + .get::, _>("last_login_at") + .is_some() + ); + + Ok(()) + } + + #[sqlx::test] + async fn can_reject_login_from_unmatched_host(pool: PgPool) -> sqlx::Result<()> { + let mut conn = pool.acquire().await?; + let config = get_app_config().unwrap(); + let app = init_app_for_test(post, pool, false, false, None).await.0; + let (token_id, hashed_token) = generate_hashed_token(&config.token_salt).unwrap(); + + // Insert data. + let blog = sqlx::query( + r#" +WITH inserted_user AS ( + INSERT INTO users (name, username, email, email_verified) + VALUES ('Sample user', 'sample_user', 'sample@example.com', TRUE) + RETURNING id +), inserted_blog AS ( + INSERT INTO blogs (name, slug, domain, user_id) + VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) + RETURNING id +) +INSERT INTO blog_login_tokens (id, user_id, blog_id, expires_at) +VALUES ($1, (SELECT id FROM inserted_user), (SELECT id FROM inserted_blog), NOW()) +RETURNING blog_id +"#, + ) + .bind(&hashed_token) + .fetch_one(&mut *conn) + .await?; + + let blog_id = blog.get::("id"); + + // Try to verify login token. + let req = test::TestRequest::post() + .append_header(("origin", "https://invalid.com")) + .uri(&format!("/v1/blogs/{blog_id}/verify-login")) + .set_json(Request { token: token_id }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_client_error()); + assert_toast_error_response(res, "Blog domain does not match").await; + + Ok(()) + } + + #[sqlx::test] + async fn can_reject_login_for_expired_token(pool: PgPool) -> sqlx::Result<()> { + let mut conn = pool.acquire().await?; + let config = get_app_config().unwrap(); + let app = init_app_for_test(post, pool, false, false, None).await.0; + let (token_id, hashed_token) = generate_hashed_token(&config.token_salt).unwrap(); + + // Insert an expired token. + let blog = sqlx::query( + r#" +WITH inserted_user AS ( + INSERT INTO users (name, username, email, email_verified) + VALUES ('Sample user', 'sample_user', 'sample@example.com', TRUE) + RETURNING id +), inserted_blog AS ( + INSERT INTO blogs (name, slug, domain, user_id) + VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) + RETURNING id +) +INSERT INTO blog_login_tokens (id, user_id, blog_id, expires_at) +VALUES ($1, (SELECT id FROM inserted_user), (SELECT id FROM inserted_blog), $2) +RETURNING blog_id +"#, + ) + .bind(&hashed_token) + .bind(OffsetDateTime::now_utc() - Duration::days(1)) // Yesterday + .fetch_one(&mut *conn) + .await?; + + let blog_id = blog.get::("id"); + + // Try to verify login token. + let req = test::TestRequest::post() + .append_header(("origin", "https://test.com")) + .uri(&format!("/v1/blogs/{blog_id}/verify-login")) + .set_json(Request { token: token_id }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + assert_response_body_text( + res, + &serde_json::to_string(&Response { + result: "invalid_token".to_string(), + }) + .unwrap_or_default(), + ) + .await; + + Ok(()) + } + + mod serial { + use super::*; + + #[test_context(RedisTestContext)] + #[sqlx::test] + async fn can_clear_overflowing_sessions_on_login( + ctx: &mut RedisTestContext, + pool: PgPool, + ) -> sqlx::Result<()> { + let redis_pool = &ctx.redis_pool; + let mut redis_conn = redis_pool.get().await.unwrap(); + let mut conn = pool.acquire().await?; + let (app, _, user_id) = + init_app_for_test(services![login_post, post], pool, true, true, None).await; + + let (email, password_hash, password) = get_sample_email_and_password(); + + // Create 10 sessions (one is already created from `init_app_for_test`). + for _ in 0..9 { + let _: () = redis_conn + .set( + &format!( + "{}:{}:{}", + RedisNamespace::Session, + user_id.unwrap(), + Uuid::new_v4() + ), + &rmp_serde::to_vec_named(&UserSession { + user_id: user_id.unwrap(), + ..Default::default() + }) + .unwrap(), + ) + .await + .unwrap(); + } + + let sessions = get_user_sessions(redis_pool, user_id.unwrap()) + .await + .unwrap(); + + assert_eq!(sessions.len(), 10); + + // Insert the user and blog. + let blog = sqlx::query( + r#" +WITH inserted_user AS ( + INSERT INTO users (id, name, username, email, password, email_verified) + VALUES ($1, $2, $3, $4, $5, TRUE) +) +INSERT INTO blogs (name, slug, domain, user_id) +VALUES ('Sample blog', 'sample_blog', 'test.com', $1) +RETURNING id +"#, + ) + .bind(user_id.unwrap()) + .bind("Sample user".to_string()) + .bind("sample_user".to_string()) + .bind(email.to_string()) + .bind(password_hash) + .fetch_one(&mut *conn) + .await?; + + let blog_id = blog.get::("id"); + + // Send login request. + let req = test::TestRequest::post() + .uri("/v1/auth/login") + .set_json(LoginRequest { + email: email.to_string(), + password: password.to_string(), + remember_me: true, + code: None, + blog_domain: Some("test.com".to_string()), + }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + + let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); + + assert_eq!(json.result, "success".to_string()); + assert!(json.blog_token.is_some()); + + let login_token = json.blog_token.unwrap(); + + // Verify login token. + let req = test::TestRequest::post() + .uri(&format!("/v1/blogs/{blog_id}/verify-login")) + .set_json(Request { token: login_token }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + assert_response_body_text( + res, + &serde_json::to_string(&Response { + result: "success".to_string(), + }) + .unwrap_or_default(), + ) + .await; + + // Should remove previous sessions. + let sessions = get_user_sessions(redis_pool, user_id.unwrap()) + .await + .unwrap(); + + assert_eq!(sessions.len(), 1); + + Ok(()) + } + + #[test_context(RedisTestContext)] + #[sqlx::test] + async fn can_insert_client_device_and_location_into_the_session( + _ctx: &mut RedisTestContext, + pool: PgPool, + ) -> sqlx::Result<()> { + let mut conn = pool.acquire().await?; + let app = init_app_for_test( + services![test_login_get, login_post, post], + pool, + false, + false, + None, + ) + .await + .0; + + let (email, password_hash, password) = get_sample_email_and_password(); + + // Insert the user and blog. + let blog = sqlx::query( + r#" +WITH inserted_user AS ( + INSERT INTO users (name, username, email, password, email_verified) + VALUES ($1, $2, $3, $4, TRUE) + RETURNING id +) +INSERT INTO blogs (name, slug, domain, user_id) +VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) +RETURNING id +"#, + ) + .bind("Sample user".to_string()) + .bind("sample_user".to_string()) + .bind(email.to_string()) + .bind(password_hash) + .fetch_one(&mut *conn) + .await?; + + let blog_id = blog.get::("id"); + + // Send login request. + let req = test::TestRequest::post() + .uri("/v1/auth/login") + .set_json(LoginRequest { + email: email.to_string(), + password: password.to_string(), + remember_me: true, + code: None, + blog_domain: Some("test.com".to_string()), + }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + + let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); + + assert_eq!(json.result, "success".to_string()); + assert!(json.blog_token.is_some()); + + let login_token = json.blog_token.unwrap(); + + // Verify login token. + let req = test::TestRequest::post() + .peer_addr(SocketAddr::from(SocketAddrV4::new( + Ipv4Addr::new(8, 8, 8, 8), + 8080, + ))) + .append_header(("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:40.0) Gecko/20100101 Firefox/40.0")) + .append_header(("origin", "https://test.com")) + .uri(&format!("/v1/blogs/{blog_id}/verify-login")) + .set_json(Request { token: login_token }) + .to_request(); + let res = test::call_service(&app, req).await; + + let cookie_value = res + .response() + .cookies() + .find(|cookie| cookie.name() == SESSION_COOKIE_NAME); + + assert!(res.status().is_success()); + assert!(cookie_value.is_some()); + + let cookie_value = cookie_value.unwrap(); + + // Should use the correct domain. + assert_eq!(cookie_value.domain(), Some("test.com")); + + let req = test::TestRequest::get() + .cookie(cookie_value) + .uri("/get-login-details") + .to_request(); + let res = test::call_service(&app, req).await; + + #[derive(Deserialize)] + struct ClientSession { + device: Option, + location: Option, + domain: Option, + } + + let client_session = test::read_body_json::(res).await; + + assert!(client_session.device.is_some()); + assert!(client_session.location.is_some()); + assert_eq!(client_session.domain, Some("test.com".to_string())); + + Ok(()) + } + } +} diff --git a/src/routes/v1/me/assets/delete.rs b/src/routes/v1/me/assets/delete.rs index 31bc507..5f918de 100644 --- a/src/routes/v1/me/assets/delete.rs +++ b/src/routes/v1/me/assets/delete.rs @@ -127,7 +127,7 @@ mod tests { } async fn teardown(self) { - delete_s3_objects_using_prefix(&self.s3_client, S3_UPLOADS_BUCKET, None, None) + delete_s3_objects_using_prefix(&self.s3_client, S3_UPLOADS_BUCKET, None) .await .unwrap(); } diff --git a/src/routes/v1/me/assets/post.rs b/src/routes/v1/me/assets/post.rs index d92849b..14cfb26 100644 --- a/src/routes/v1/me/assets/post.rs +++ b/src/routes/v1/me/assets/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ buckets::S3_UPLOADS_BUCKET, resource_limit::ResourceLimit, @@ -12,26 +13,25 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_multipart::form::{ + MultipartForm, tempfile::TempFile, text::Text, - MultipartForm, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use colors_transform::Rgb; use dominant_color::get_colors; use image::{ - imageops::FilterType, EncodableLayout, GenericImageView, ImageError, ImageFormat, + imageops::FilterType, }; use mime::{ IMAGE_GIF, @@ -420,20 +420,20 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::{ + RedisPool, + S3Client, config::get_app_config, oauth::get_oauth_client_map, test_utils::{ + RedisTestContext, + TestContext, exceed_resource_limit, get_lapin_pool, get_redis_pool, get_resource_limit, get_s3_client, - RedisTestContext, - TestContext, }, utils::delete_s3_objects_using_prefix::delete_s3_objects_using_prefix, - RedisPool, - S3Client, }; use actix_web::{ App, @@ -441,12 +441,12 @@ mod tests { }; use futures::future; use reqwest::{ + Body, + StatusCode, multipart::{ Form, Part, }, - Body, - StatusCode, }; use sqlx::PgPool; use std::{ @@ -578,7 +578,7 @@ VALUES ($1, $2, $3, $4) .expect("failed to FLUSHDB"); }, async { - delete_s3_objects_using_prefix(&self.s3_client, S3_UPLOADS_BUCKET, None, None) + delete_s3_objects_using_prefix(&self.s3_client, S3_UPLOADS_BUCKET, None) .await .unwrap() }, diff --git a/src/routes/v1/me/blogs/settings/appearance/fonts/delete.rs b/src/routes/v1/me/blogs/settings/appearance/fonts/delete.rs index fa0e560..af5d168 100644 --- a/src/routes/v1/me/blogs/settings/appearance/fonts/delete.rs +++ b/src/routes/v1/me/blogs/settings/appearance/fonts/delete.rs @@ -166,7 +166,7 @@ mod tests { } async fn teardown(self) { - delete_s3_objects_using_prefix(&self.s3_client, S3_FONTS_BUCKET, None, None) + delete_s3_objects_using_prefix(&self.s3_client, S3_FONTS_BUCKET, None) .await .unwrap(); } diff --git a/src/routes/v1/me/blogs/settings/appearance/fonts/post.rs b/src/routes/v1/me/blogs/settings/appearance/fonts/post.rs index 783955e..a8195f9 100644 --- a/src/routes/v1/me/blogs/settings/appearance/fonts/post.rs +++ b/src/routes/v1/me/blogs/settings/appearance/fonts/post.rs @@ -515,7 +515,7 @@ VALUES ($5, $6, $7, (SELECT id FROM inserted_user), TRUE) .expect("failed to FLUSHDB"); }, async { - delete_s3_objects_using_prefix(&self.s3_client, S3_FONTS_BUCKET, None, None) + delete_s3_objects_using_prefix(&self.s3_client, S3_FONTS_BUCKET, None) .await .unwrap() }, diff --git a/src/routes/v1/me/blogs/settings/delete_blog.rs b/src/routes/v1/me/blogs/settings/delete_blog.rs index e6d88d5..56bcfd2 100644 --- a/src/routes/v1/me/blogs/settings/delete_blog.rs +++ b/src/routes/v1/me/blogs/settings/delete_blog.rs @@ -208,7 +208,7 @@ mod tests { } async fn teardown(self) { - delete_s3_objects_using_prefix(&self.s3_client, S3_FONTS_BUCKET, None, None) + delete_s3_objects_using_prefix(&self.s3_client, S3_FONTS_BUCKET, None) .await .unwrap(); } diff --git a/src/routes/v1/me/gallery/post.rs b/src/routes/v1/me/gallery/post.rs index 449499f..14b1d61 100644 --- a/src/routes/v1/me/gallery/post.rs +++ b/src/routes/v1/me/gallery/post.rs @@ -454,7 +454,7 @@ mod tests { .expect("failed to FLUSHDB"); }, async { - delete_s3_objects_using_prefix(&self.s3_client, S3_UPLOADS_BUCKET, None, None) + delete_s3_objects_using_prefix(&self.s3_client, S3_UPLOADS_BUCKET, None) .await .unwrap() }, diff --git a/src/test_utils/count_s3_objects.rs b/src/test_utils/count_s3_objects.rs index 3bde9d3..a884ff0 100644 --- a/src/test_utils/count_s3_objects.rs +++ b/src/test_utils/count_s3_objects.rs @@ -1,13 +1,11 @@ use crate::S3Client; use anyhow::anyhow; -use async_recursion::async_recursion; /// Counts the number of S3 objects in the specified bucket. /// /// * `client` - The S3 client instance. /// * `bucket_name` - The name of the target bucket containing the objects. /// * `prefix` - An optional string prefix to match keys and limit the objects. -#[async_recursion] pub async fn count_s3_objects( client: &S3Client, bucket_name: &str, @@ -70,7 +68,6 @@ mod tests { &self.s3_client, S3_BASE_BUCKET, Some("test-".to_string()), - None, ) .await .unwrap(); diff --git a/src/utils/delete_s3_objects.rs b/src/utils/delete_s3_objects.rs index 5562fa3..39192f7 100644 --- a/src/utils/delete_s3_objects.rs +++ b/src/utils/delete_s3_objects.rs @@ -111,7 +111,6 @@ mod tests { &self.s3_client, S3_BASE_BUCKET, Some("test-".to_string()), - None, ) .await .unwrap(); diff --git a/src/utils/delete_s3_objects_using_prefix.rs b/src/utils/delete_s3_objects_using_prefix.rs index 0fe67b5..efda7eb 100644 --- a/src/utils/delete_s3_objects_using_prefix.rs +++ b/src/utils/delete_s3_objects_using_prefix.rs @@ -3,7 +3,6 @@ use crate::{ utils::delete_s3_objects::delete_s3_objects, }; use anyhow::anyhow; -use async_recursion::async_recursion; /// Deletes all the S3 objects in the specified bucket matching the provided prefix. Returns the /// number of objects that were successfully deleted. @@ -11,46 +10,42 @@ use async_recursion::async_recursion; /// * `client` - The S3 client instance. /// * `bucket_name` - The name of the target bucket containing the objects. /// * `prefix` - An optional string prefix to match keys and limit the objects. -/// * `continuation_token` - (Private) A token used during recursion if there are more than 1000 -/// keys with the provided prefix. -#[async_recursion] pub async fn delete_s3_objects_using_prefix( client: &S3Client, bucket_name: &str, prefix: Option, - continuation_token: Option, ) -> anyhow::Result { - let mut num_deleted: u32; - - let list_objects_result = client - .list_objects_v2() - .bucket(bucket_name) - .max_keys(1_000_i32) - .set_prefix(prefix.clone()) - .set_continuation_token(continuation_token) - .send() - .await - .map_err(|error| error.into_service_error()) - .map_err(|error| anyhow!("unable to list objects: {:?}", error))?; - - { - let object_keys = list_objects_result - .contents() - .iter() - .filter_map(|obj| obj.key.clone()) - .collect::>(); - - num_deleted = delete_s3_objects(client, bucket_name, object_keys).await?; - } + let mut num_deleted: u32 = 0; + let mut continuation_token: Option = None; + + loop { + let list_objects_result = client + .list_objects_v2() + .bucket(bucket_name) + .max_keys(1_000_i32) + .set_prefix(prefix.clone()) + .set_continuation_token(continuation_token.clone()) + .send() + .await + .map_err(|error| error.into_service_error()) + .map_err(|error| anyhow!("unable to list objects: {:?}", error))?; - // Recurse until there are no more keys left - if list_objects_result.is_truncated.unwrap_or_default() { - let next_continuation_token = list_objects_result.next_continuation_token.clone(); - drop(list_objects_result); + { + let object_keys = list_objects_result + .contents() + .iter() + .filter_map(|obj| obj.key.clone()) + .collect::>(); - num_deleted += - delete_s3_objects_using_prefix(client, bucket_name, prefix, next_continuation_token) - .await?; + num_deleted += delete_s3_objects(client, bucket_name, object_keys).await?; + } + + // Repeat until there are no more keys left + if list_objects_result.is_truncated.unwrap_or(false) { + continuation_token = list_objects_result.next_continuation_token.clone(); + } else { + break; + } } Ok(num_deleted) @@ -86,7 +81,6 @@ mod tests { &self.s3_client, S3_BASE_BUCKET, Some("test-".to_string()), - None, ) .await .unwrap(); @@ -115,7 +109,6 @@ mod tests { s3_client, S3_BASE_BUCKET, Some("test-fruits/".to_string()), - None, ) .await .unwrap(); @@ -154,7 +147,6 @@ mod tests { s3_client, S3_BASE_BUCKET, Some("test-integers/".to_string()), - None, ) .await .unwrap(); @@ -193,7 +185,6 @@ mod tests { s3_client, S3_BASE_BUCKET, Some("test-trees/".to_string()), - None, ) .await .unwrap(); diff --git a/tests/tables/blogs/blogs.rs b/tests/tables/blogs/blogs.rs index be62026..e0a0ba6 100644 --- a/tests/tables/blogs/blogs.rs +++ b/tests/tables/blogs/blogs.rs @@ -2,12 +2,12 @@ mod tests { use nanoid::nanoid; use sqlx::{ - pool::PoolConnection, - postgres::PgRow, Error, PgPool, Postgres, Row, + pool::PoolConnection, + postgres::PgRow, }; use storiny::constants::{ sql_states::SqlState, @@ -2469,4 +2469,54 @@ SELECT EXISTS ( Ok(()) } + + #[sqlx::test(fixtures("user"))] + async fn can_delete_login_tokens_on_blog_hard_delete(pool: PgPool) -> sqlx::Result<()> { + let mut conn = pool.acquire().await?; + let blog_id = (insert_sample_blog(&mut conn).await?).get::("id"); + let token_id = nanoid!(TOKEN_LENGTH); + + // Insert a login token. + let result = sqlx::query( + r#" +INSERT INTO blog_login_tokens (id, blog_id, user_id, expires_at) +VALUES ($1, $2, $3, NOW()) +"#, + ) + .bind(token_id) + .bind(blog_id) + .bind(1_i64) + .execute(&mut *conn) + .await?; + + assert_eq!(result.rows_affected(), 1); + + // Delete the blog + sqlx::query( + r#" +DELETE FROM blogs +WHERE id = $1 +"#, + ) + .bind(blog_id) + .execute(&mut *conn) + .await?; + + // Token should get deleted + let result = sqlx::query( + r#" +SELECT EXISTS ( + SELECT 1 FROM blog_login_tokens + WHERE blog_id = $1 +) +"#, + ) + .bind(blog_id) + .fetch_one(&mut *conn) + .await?; + + assert!(!result.get::("exists")); + + Ok(()) + } } diff --git a/tests/tables/users/users.rs b/tests/tables/users/users.rs index 3c60a7d..afbe6ee 100644 --- a/tests/tables/users/users.rs +++ b/tests/tables/users/users.rs @@ -2,12 +2,12 @@ mod tests { use nanoid::nanoid; use sqlx::{ - pool::PoolConnection, - postgres::PgRow, Error, PgPool, Postgres, Row, + pool::PoolConnection, + postgres::PgRow, }; use storiny::constants::{ sql_states::SqlState, @@ -12510,6 +12510,56 @@ SELECT EXISTS ( Ok(()) } + #[sqlx::test(fixtures("user", "blog"))] + async fn can_delete_blog_login_token_on_user_hard_delete(pool: PgPool) -> sqlx::Result<()> { + let mut conn = pool.acquire().await?; + let token_id = nanoid!(TOKEN_LENGTH); + + // Insert a token + let insert_result = sqlx::query( + r#" +INSERT INTO blog_login_tokens (id, user_id, blog_id, expires_at) +VALUES ($1, $2, $3, NOW()) +RETURNING id +"#, + ) + .bind(&token_id) + .bind(1_i16) + .bind(1_i64) + .execute(&mut *conn) + .await?; + + assert_eq!(insert_result.rows_affected(), 1); + + // Delete the user + sqlx::query( + r#" +DELETE FROM users +WHERE id = $1 +"#, + ) + .bind(1_i64) + .execute(&mut *conn) + .await?; + + // Login token should get deleted + let result = sqlx::query( + r#" +SELECT EXISTS ( + SELECT 1 FROM blog_login_tokens + WHERE id = $1 +) +"#, + ) + .bind(&token_id) + .fetch_one(&mut *conn) + .await?; + + assert!(!result.get::("exists")); + + Ok(()) + } + #[sqlx::test] async fn can_delete_transmitted_notifications_on_user_hard_delete( pool: PgPool, From 51225174ef626406afb561cd214ce0ce52a7f4e9 Mon Sep 17 00:00:00 2001 From: zignis Date: Wed, 23 Apr 2025 19:28:35 +0530 Subject: [PATCH 2/7] run cargo fmt --- justfile | 4 +-- macros/src/lib.rs | 2 +- session/src/storage/utils.rs | 2 +- session/tests/opaque_errors.rs | 10 +++---- session/tests/session.rs | 2 +- src/amqp/consumers/newsletter/newsletter.rs | 8 +++--- .../notify_story_add/notify_story_add.rs | 4 +-- src/amqp/consumers/templated_email.rs | 6 ++-- src/amqp/init.rs | 8 +++--- src/cron/init.rs | 2 +- src/error.rs | 2 +- src/grpc/endpoints/create_draft.rs | 2 +- src/grpc/endpoints/get_blog/get_blog.rs | 2 +- .../get_blog_archive/get_blog_archive.rs | 2 +- .../get_blog_newsletter.rs | 2 +- src/grpc/endpoints/get_comment/get_comment.rs | 2 +- src/grpc/endpoints/get_connection_settings.rs | 2 +- src/grpc/endpoints/get_login_activity.rs | 4 +-- src/grpc/endpoints/get_story/get_story.rs | 4 +-- .../get_story_metadata/get_story_metadata.rs | 2 +- src/grpc/endpoints/get_token.rs | 2 +- src/grpc/endpoints/verify_email.rs | 2 +- .../verify_newsletter_subscription.rs | 2 +- src/grpc/server.rs | 6 ++-- src/grpc/service.rs | 2 +- src/iso8601.rs | 2 +- src/oauth/discord.rs | 2 +- src/oauth/dribbble.rs | 2 +- src/oauth/github.rs | 2 +- src/oauth/google.rs | 2 +- src/oauth/mod.rs | 10 +++---- src/oauth/spotify.rs | 2 +- src/oauth/youtube.rs | 2 +- src/realms/awareness.rs | 10 +++---- src/realms/broadcast.rs | 18 ++++++------ src/realms/connection.rs | 28 +++++++++---------- src/realms/protocol.rs | 20 ++++++------- src/routes/favicon.rs | 2 +- src/routes/health.rs | 2 +- src/routes/index.rs | 4 +-- src/routes/oauth/discord/callback.rs | 8 +++--- src/routes/oauth/discord/discord.rs | 4 +-- src/routes/oauth/dribbble/callback.rs | 8 +++--- src/routes/oauth/dribbble/dribbble.rs | 4 +-- src/routes/oauth/github/callback.rs | 8 +++--- src/routes/oauth/github/github.rs | 4 +-- src/routes/oauth/spotify/callback.rs | 8 +++--- src/routes/oauth/spotify/spotify.rs | 4 +-- src/routes/oauth/youtube/youtube.rs | 4 +-- src/routes/robots.rs | 2 +- .../v1/auth/external/google/callback.rs | 14 +++++----- src/routes/v1/auth/external/google/google.rs | 4 +-- src/routes/v1/auth/mfa_preflight.rs | 10 +++---- src/routes/v1/auth/recovery/recovery.rs | 10 +++---- .../resend_verification_email.rs | 10 +++---- .../v1/auth/reset_password/reset_password.rs | 16 +++++------ src/routes/v1/auth/signup.rs | 18 ++++++------ src/routes/v1/blogs/archive/archive.rs | 6 ++-- src/routes/v1/blogs/editors/editors.rs | 4 +-- src/routes/v1/blogs/feed/feed.rs | 6 ++-- src/routes/v1/blogs/writers/writers.rs | 4 +-- src/routes/v1/feed/feed.rs | 6 ++-- src/routes/v1/me/account_activity.rs | 6 ++-- src/routes/v1/me/assets/alt.rs | 4 +-- src/routes/v1/me/assets/favourite.rs | 4 +-- src/routes/v1/me/assets/rating.rs | 4 +-- src/routes/v1/me/blocked_users/delete.rs | 4 +-- src/routes/v1/me/blocked_users/get.rs | 4 +-- src/routes/v1/me/blocked_users/post.rs | 6 ++-- src/routes/v1/me/blog_requests/delete.rs | 4 +-- src/routes/v1/me/blog_requests/get.rs | 6 ++-- src/routes/v1/me/blog_requests/post.rs | 4 +-- .../pending_stories/pending_stories.rs | 6 ++-- .../published_stories/published_stories.rs | 6 ++-- .../v1/me/blogs/editor_requests/cancel.rs | 4 +-- src/routes/v1/me/blogs/editor_requests/get.rs | 6 ++-- src/routes/v1/me/blogs/editors/invite.rs | 6 ++-- src/routes/v1/me/blogs/editors/remove.rs | 4 +-- src/routes/v1/me/blogs/get.rs | 4 +-- src/routes/v1/me/blogs/post.rs | 6 ++-- .../me/blogs/settings/appearance/branding.rs | 4 +-- .../me/blogs/settings/appearance/favicon.rs | 4 +-- .../v1/me/blogs/settings/appearance/mark.rs | 4 +-- .../blogs/settings/appearance/page_layout.rs | 4 +-- .../blogs/settings/appearance/story_layout.rs | 4 +-- .../v1/me/blogs/settings/appearance/theme.rs | 4 +-- src/routes/v1/me/blogs/settings/banner.rs | 4 +-- .../v1/me/blogs/settings/connections.rs | 4 +-- .../me/blogs/settings/domain/code_request.rs | 6 ++-- .../v1/me/blogs/settings/domain/remove.rs | 4 +-- .../v1/me/blogs/settings/domain/verify.rs | 18 ++++++------ src/routes/v1/me/blogs/settings/general.rs | 4 +-- src/routes/v1/me/blogs/settings/logo.rs | 4 +-- .../v1/me/blogs/settings/newsletter_splash.rs | 4 +-- src/routes/v1/me/blogs/settings/seo.rs | 4 +-- .../v1/me/blogs/settings/sidebars/lsb.rs | 4 +-- .../v1/me/blogs/settings/sidebars/rsb.rs | 4 +-- src/routes/v1/me/blogs/settings/slug.rs | 4 +-- src/routes/v1/me/blogs/settings/visibility.rs | 4 +-- .../v1/me/blogs/stats/stories/stories.rs | 4 +-- src/routes/v1/me/blogs/stories/publish.rs | 12 ++++---- src/routes/v1/me/blogs/stories/remove.rs | 4 +-- src/routes/v1/me/blogs/subscribers/get.rs | 4 +-- src/routes/v1/me/blogs/subscribers/import.rs | 6 ++-- src/routes/v1/me/blogs/subscribers/remove.rs | 4 +-- .../v1/me/blogs/writer_requests/cancel.rs | 4 +-- src/routes/v1/me/blogs/writer_requests/get.rs | 6 ++-- src/routes/v1/me/blogs/writers/invite.rs | 6 ++-- src/routes/v1/me/blogs/writers/remove.rs | 4 +-- src/routes/v1/me/bookmarks/delete.rs | 4 +-- src/routes/v1/me/bookmarks/get.rs | 6 ++-- src/routes/v1/me/bookmarks/post.rs | 6 ++-- .../v1/me/collaboration_requests/cancel.rs | 4 +-- .../v1/me/collaboration_requests/delete.rs | 4 +-- .../v1/me/collaboration_requests/get.rs | 6 ++-- .../v1/me/collaboration_requests/post.rs | 4 +-- src/routes/v1/me/comments/delete.rs | 4 +-- src/routes/v1/me/comments/get.rs | 6 ++-- src/routes/v1/me/comments/patch.rs | 6 ++-- src/routes/v1/me/comments/post.rs | 8 +++--- .../v1/me/contributions/contributions.rs | 6 ++-- src/routes/v1/me/drafts/delete.rs | 4 +-- src/routes/v1/me/drafts/get.rs | 4 +-- src/routes/v1/me/drafts/recover.rs | 4 +-- src/routes/v1/me/followed_blogs/delete.rs | 4 +-- src/routes/v1/me/followed_blogs/post.rs | 6 ++-- src/routes/v1/me/followed_tags/delete.rs | 4 +-- src/routes/v1/me/followed_tags/get.rs | 4 +-- src/routes/v1/me/followed_tags/post.rs | 6 ++-- src/routes/v1/me/followers/delete.rs | 4 +-- src/routes/v1/me/followers/get.rs | 4 +-- src/routes/v1/me/following/delete.rs | 4 +-- src/routes/v1/me/following/get.rs | 4 +-- src/routes/v1/me/following/post.rs | 6 ++-- src/routes/v1/me/friend_requests/cancel.rs | 4 +-- src/routes/v1/me/friend_requests/delete.rs | 4 +-- src/routes/v1/me/friend_requests/get.rs | 6 ++-- src/routes/v1/me/friend_requests/post.rs | 4 +-- src/routes/v1/me/friends/delete.rs | 4 +-- src/routes/v1/me/friends/get.rs | 4 +-- src/routes/v1/me/friends/post.rs | 6 ++-- src/routes/v1/me/get.rs | 6 ++-- src/routes/v1/me/history/history.rs | 6 ++-- src/routes/v1/me/leave_blog/leave_blog.rs | 4 +-- src/routes/v1/me/leave_story/leave_story.rs | 4 +-- src/routes/v1/me/liked_comments/delete.rs | 4 +-- src/routes/v1/me/liked_comments/post.rs | 6 ++-- src/routes/v1/me/liked_replies/delete.rs | 4 +-- src/routes/v1/me/liked_replies/post.rs | 6 ++-- src/routes/v1/me/liked_stories/delete.rs | 4 +-- src/routes/v1/me/liked_stories/get.rs | 6 ++-- src/routes/v1/me/liked_stories/post.rs | 6 ++-- src/routes/v1/me/lookup/username/username.rs | 4 +-- src/routes/v1/me/muted_users/delete.rs | 4 +-- src/routes/v1/me/muted_users/get.rs | 4 +-- src/routes/v1/me/muted_users/post.rs | 6 ++-- src/routes/v1/me/newsletters/delete.rs | 4 +-- src/routes/v1/me/newsletters/post.rs | 6 ++-- src/routes/v1/me/notifications/get.rs | 6 ++-- src/routes/v1/me/notifications/read.rs | 4 +-- src/routes/v1/me/notifications/read_all.rs | 4 +-- src/routes/v1/me/replies/delete.rs | 4 +-- src/routes/v1/me/replies/get.rs | 6 ++-- src/routes/v1/me/replies/patch.rs | 6 ++-- src/routes/v1/me/replies/post.rs | 8 +++--- .../settings/accounts/add/google/callback.rs | 10 +++---- .../me/settings/accounts/add/google/google.rs | 8 +++--- src/routes/v1/me/settings/accounts/remove.rs | 8 +++--- src/routes/v1/me/settings/avatar.rs | 4 +-- src/routes/v1/me/settings/banner.rs | 4 +-- .../v1/me/settings/connections/remove.rs | 4 +-- .../v1/me/settings/connections/visibility.rs | 4 +-- src/routes/v1/me/settings/email.rs | 12 ++++---- .../v1/me/settings/mfa/generate_codes.rs | 4 +-- .../v1/me/settings/mfa/recovery_codes.rs | 4 +-- src/routes/v1/me/settings/mfa/remove.rs | 4 +-- src/routes/v1/me/settings/mfa/request.rs | 4 +-- src/routes/v1/me/settings/mfa/verify.rs | 4 +-- .../v1/me/settings/notifications/mail.rs | 4 +-- .../v1/me/settings/notifications/site.rs | 4 +-- .../me/settings/notifications/unsubscribe.rs | 4 +-- src/routes/v1/me/settings/password/add.rs | 16 +++++------ .../settings/password/request_verification.rs | 10 +++---- src/routes/v1/me/settings/password/update.rs | 16 +++++------ .../v1/me/settings/privacy/delete_account.rs | 8 +++--- .../v1/me/settings/privacy/disable_account.rs | 8 +++--- .../v1/me/settings/privacy/following_list.rs | 4 +-- .../v1/me/settings/privacy/friend_list.rs | 4 +-- .../privacy/incoming_blog_requests.rs | 4 +-- .../incoming_collaboration_requests.rs | 4 +-- .../privacy/incoming_friend_requests.rs | 4 +-- .../v1/me/settings/privacy/private_account.rs | 4 +-- .../v1/me/settings/privacy/read_history.rs | 4 +-- .../me/settings/privacy/sensitive_content.rs | 4 +-- src/routes/v1/me/settings/profile.rs | 6 ++-- .../v1/me/settings/sessions/acknowledge.rs | 12 +++----- src/routes/v1/me/settings/sessions/destroy.rs | 8 +++--- src/routes/v1/me/settings/sessions/logout.rs | 14 ++++------ src/routes/v1/me/settings/username.rs | 8 +++--- src/routes/v1/me/stats/account/account.rs | 4 +-- src/routes/v1/me/stats/stories/stories.rs | 4 +-- src/routes/v1/me/status/delete.rs | 4 +-- src/routes/v1/me/status/post.rs | 4 +-- src/routes/v1/me/stories/contributors/get.rs | 6 ++-- .../v1/me/stories/contributors/invite.rs | 6 ++-- .../v1/me/stories/contributors/remove.rs | 4 +-- .../v1/me/stories/contributors/update.rs | 4 +-- src/routes/v1/me/stories/delete.rs | 4 +-- src/routes/v1/me/stories/get.rs | 4 +-- src/routes/v1/me/stories/metadata/metadata.rs | 4 +-- src/routes/v1/me/stories/publish.rs | 10 +++---- src/routes/v1/me/stories/recover.rs | 4 +-- src/routes/v1/me/stories/stats/stats.rs | 6 ++-- src/routes/v1/me/stories/unpublish.rs | 4 +-- src/routes/v1/me/subscriptions/delete.rs | 4 +-- src/routes/v1/me/subscriptions/post.rs | 4 +-- .../unread_notifications.rs | 4 +-- .../v1/newsletters/unsubscribe/unsubscribe.rs | 6 ++-- .../recommendations/recommendations.rs | 6 ++-- src/routes/v1/public/cards/user/user.rs | 6 ++-- .../v1/public/comments/replies/replies.rs | 6 ++-- .../public/comments/visibility/visibility.rs | 4 +-- .../v1/public/explore/stories/stories.rs | 6 ++-- src/routes/v1/public/explore/tags/tags.rs | 4 +-- .../v1/public/explore/writers/writers.rs | 4 +-- src/routes/v1/public/preview/preview.rs | 6 ++-- .../public/replies/visibility/visibility.rs | 4 +-- .../v1/public/stories/comments/comments.rs | 6 ++-- src/routes/v1/public/stories/read/read.rs | 8 +++--- .../recommendations/recommendations.rs | 6 ++-- src/routes/v1/public/tags/tags.rs | 6 ++-- src/routes/v1/public/validation/username.rs | 4 +-- src/routes/v1/rsb_content/rsb_content.rs | 10 +++---- src/routes/v1/rsb_content/stories.rs | 2 +- src/routes/v1/tags/stories/stories.rs | 6 ++-- src/routes/v1/tags/writers/writers.rs | 4 +-- src/routes/v1/users/followers/followers.rs | 4 +-- src/routes/v1/users/following/following.rs | 4 +-- src/routes/v1/users/friends/friends.rs | 4 +-- src/routes/v1/users/stories/stories.rs | 6 ++-- src/snowflake_id.rs | 4 +-- src/telemetry.rs | 6 ++-- src/test_utils/get_cron_job_state_for_test.rs | 2 +- src/test_utils/get_lapin_pool.rs | 2 +- src/test_utils/get_redis_pool.rs | 2 +- src/test_utils/get_resource_limit.rs | 2 +- src/test_utils/get_resource_lock_attempts.rs | 2 +- src/test_utils/redis_test_context.rs | 4 +-- src/test_utils/test_grpc_service.rs | 2 +- src/utils/clear_user_sessions.rs | 8 ++---- .../decode_uri_encoded_story_categories.rs | 6 ++-- src/utils/encode_unsubscribe_fragment.rs | 2 +- src/utils/generate_hashed_token.rs | 2 +- src/utils/get_client_country.rs | 2 +- src/utils/get_client_location.rs | 2 +- src/utils/get_user_sessions.rs | 2 +- src/utils/incr_report_limit.rs | 2 +- src/utils/incr_resource_limit.rs | 2 +- src/utils/incr_subscription_limit.rs | 2 +- src/utils/md_to_html.rs | 2 +- src/utils/to_iso8601.rs | 4 +-- tests/tables/blog_lsb_items/blog_lsb_items.rs | 4 +-- tests/tables/blog_rsb_items/blog_rsb_items.rs | 4 +-- tests/tables/comments/comments.rs | 4 +-- tests/tables/replies/replies.rs | 4 +-- tests/tables/stories/stories.rs | 4 +-- 266 files changed, 684 insertions(+), 694 deletions(-) diff --git a/justfile b/justfile index c78d632..2c88939 100644 --- a/justfile +++ b/justfile @@ -11,7 +11,7 @@ build_img: docker build --platform linux/arm64 -t storiny_api . fmt: - cargo fmt + cargo +nightly fmt test: cargo nextest run --workspace @@ -29,4 +29,4 @@ update_geo: docker compose -f geo/docker-compose.yaml up migrate: - sqlx migrate run \ No newline at end of file + sqlx migrate run diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 2ca830a..4a0f46d 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -4,10 +4,10 @@ use quote::{ quote, }; use syn::{ + FnArg, punctuated::Punctuated, spanned::Spanned, token::Comma, - FnArg, }; /// Macro to use on tests to add the setup/teardown functionality. diff --git a/session/src/storage/utils.rs b/session/src/storage/utils.rs index 3c104b1..46386d1 100644 --- a/session/src/storage/utils.rs +++ b/session/src/storage/utils.rs @@ -1,8 +1,8 @@ use crate::storage::SessionKey; use rand::{ + Rng as _, distributions::Alphanumeric, rngs::OsRng, - Rng as _, }; use std::convert::TryInto; diff --git a/session/tests/opaque_errors.rs b/session/tests/opaque_errors.rs index 41e9ad6..1d62807 100644 --- a/session/tests/opaque_errors.rs +++ b/session/tests/opaque_errors.rs @@ -1,15 +1,15 @@ use actix_web::{ + App, + Responder, body::MessageBody, cookie::{ - time::Duration, Key, + time::Duration, }, dev::Service, http::StatusCode, test, web, - App, - Responder, }; use anyhow::Error; use serde_json::{ @@ -18,6 +18,8 @@ use serde_json::{ }; use std::convert::TryInto; use storiny_session::{ + Session, + SessionMiddleware, storage::{ LoadError, SaveError, @@ -25,8 +27,6 @@ use storiny_session::{ SessionStore, UpdateError, }, - Session, - SessionMiddleware, }; #[actix_web::test] diff --git a/session/tests/session.rs b/session/tests/session.rs index 70d7d21..9a0b09c 100644 --- a/session/tests/session.rs +++ b/session/tests/session.rs @@ -1,6 +1,6 @@ use actix_web::{ - test, HttpResponse, + test, }; use serde_json::Value; use storiny_session::{ diff --git a/src/amqp/consumers/newsletter/newsletter.rs b/src/amqp/consumers/newsletter/newsletter.rs index 9691fd5..313ac8c 100644 --- a/src/amqp/consumers/newsletter/newsletter.rs +++ b/src/amqp/consumers/newsletter/newsletter.rs @@ -1,4 +1,6 @@ use crate::{ + LapinPool, + SesClient, amqp::init::SharedQueueState, constants::{ email_template::EmailTemplate, @@ -16,8 +18,6 @@ use crate::{ get_cdn_url::get_cdn_url, get_read_time::get_read_time, }, - LapinPool, - SesClient, }; use anyhow::anyhow; use aws_sdk_sesv2::types::{ @@ -50,8 +50,8 @@ use std::{ sync::Arc, }; use time::{ - format_description::FormatItem, OffsetDateTime, + format_description::FormatItem, }; use tracing::{ debug, @@ -388,8 +388,8 @@ mod tests { }, }; use deadpool_lapin::lapin::{ - options::BasicPublishOptions, BasicProperties, + options::BasicPublishOptions, }; use sqlx::PgPool; use std::{ diff --git a/src/amqp/consumers/notify_story_add/notify_story_add.rs b/src/amqp/consumers/notify_story_add/notify_story_add.rs index fefef32..728a45c 100644 --- a/src/amqp/consumers/notify_story_add/notify_story_add.rs +++ b/src/amqp/consumers/notify_story_add/notify_story_add.rs @@ -1,7 +1,7 @@ use crate::{ + LapinPool, amqp::init::SharedQueueState, constants::notification_entity_type::NotificationEntityType, - LapinPool, }; use anyhow::anyhow; use deadpool_lapin::lapin::{ @@ -195,8 +195,8 @@ mod tests { get_queue_state_for_test, }; use deadpool_lapin::lapin::{ - options::BasicPublishOptions, BasicProperties, + options::BasicPublishOptions, }; use sqlx::{ PgPool, diff --git a/src/amqp/consumers/templated_email.rs b/src/amqp/consumers/templated_email.rs index 5efb8c2..7fecc64 100644 --- a/src/amqp/consumers/templated_email.rs +++ b/src/amqp/consumers/templated_email.rs @@ -1,8 +1,8 @@ use crate::{ - amqp::init::SharedQueueState, - constants::email_source::EMAIL_SOURCE, LapinPool, SesClient, + amqp::init::SharedQueueState, + constants::email_source::EMAIL_SOURCE, }; use anyhow::anyhow; use aws_sdk_sesv2::types::{ @@ -160,8 +160,8 @@ mod tests { }, }; use deadpool_lapin::lapin::{ - options::BasicPublishOptions, BasicProperties, + options::BasicPublishOptions, }; use sqlx::PgPool; use std::{ diff --git a/src/amqp/init.rs b/src/amqp/init.rs index a6b0de2..843d65a 100644 --- a/src/amqp/init.rs +++ b/src/amqp/init.rs @@ -1,4 +1,8 @@ use crate::{ + LapinPool, + RedisPool, + S3Client, + SesClient, amqp::consumers::{ newsletter::newsletter_consumer, notify_story_add::notify_story_add_consumer, @@ -7,10 +11,6 @@ use crate::{ config, config::get_app_config, models::email_templates::blog_newsletter::BlogNewsletterEmailTemplateData, - LapinPool, - RedisPool, - S3Client, - SesClient, }; use sqlx::{ Pool, diff --git a/src/cron/init.rs b/src/cron/init.rs index d038a13..c8341f3 100644 --- a/src/cron/init.rs +++ b/src/cron/init.rs @@ -1,4 +1,5 @@ use crate::{ + S3Client, config, config::get_app_config, cron::{ @@ -7,7 +8,6 @@ use crate::{ cleanup_s3::cleanup_s3, sitemap::refresh_sitemap, }, - S3Client, }; use apalis::{ cron::{ diff --git a/src/error.rs b/src/error.rs index f10eb00..0010c40 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,9 +3,9 @@ use crate::{ utils::incr_resource_lock_attempts::IncrResourceLockError, }; use actix_web::{ - http::StatusCode, HttpResponse, ResponseError, + http::StatusCode, }; use serde::{ Deserialize, diff --git a/src/grpc/endpoints/create_draft.rs b/src/grpc/endpoints/create_draft.rs index e791212..9b28025 100644 --- a/src/grpc/endpoints/create_draft.rs +++ b/src/grpc/endpoints/create_draft.rs @@ -110,10 +110,10 @@ mod tests { constants::resource_limit::ResourceLimit, grpc::defs::story_def::v1::CreateDraftRequest, test_utils::{ + RedisTestContext, exceed_resource_limit, get_resource_limit, test_grpc_service, - RedisTestContext, }, }; use sqlx::{ diff --git a/src/grpc/endpoints/get_blog/get_blog.rs b/src/grpc/endpoints/get_blog/get_blog.rs index 67b8ca3..e105749 100644 --- a/src/grpc/endpoints/get_blog/get_blog.rs +++ b/src/grpc/endpoints/get_blog/get_blog.rs @@ -12,8 +12,8 @@ use crate::{ }; use serde::Deserialize; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use tonic::{ diff --git a/src/grpc/endpoints/get_blog_archive/get_blog_archive.rs b/src/grpc/endpoints/get_blog_archive/get_blog_archive.rs index bb21a06..d771c40 100644 --- a/src/grpc/endpoints/get_blog_archive/get_blog_archive.rs +++ b/src/grpc/endpoints/get_blog_archive/get_blog_archive.rs @@ -8,10 +8,10 @@ use crate::grpc::{ }; use serde::Deserialize; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, + types::Json, }; use tonic::{ Request, diff --git a/src/grpc/endpoints/get_blog_newsletter/get_blog_newsletter.rs b/src/grpc/endpoints/get_blog_newsletter/get_blog_newsletter.rs index f17d213..e02fbc0 100644 --- a/src/grpc/endpoints/get_blog_newsletter/get_blog_newsletter.rs +++ b/src/grpc/endpoints/get_blog_newsletter/get_blog_newsletter.rs @@ -10,10 +10,10 @@ use crate::grpc::{ }; use serde::Deserialize; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, + types::Json, }; use tonic::{ Request, diff --git a/src/grpc/endpoints/get_comment/get_comment.rs b/src/grpc/endpoints/get_comment/get_comment.rs index 0e5dc87..38b90c7 100644 --- a/src/grpc/endpoints/get_comment/get_comment.rs +++ b/src/grpc/endpoints/get_comment/get_comment.rs @@ -13,10 +13,10 @@ use crate::{ }; use serde::Deserialize; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, + types::Json, }; use time::OffsetDateTime; use tonic::{ diff --git a/src/grpc/endpoints/get_connection_settings.rs b/src/grpc/endpoints/get_connection_settings.rs index 29b769a..699e354 100644 --- a/src/grpc/endpoints/get_connection_settings.rs +++ b/src/grpc/endpoints/get_connection_settings.rs @@ -15,8 +15,8 @@ use crate::{ }, }; use sqlx::{ - postgres::PgRow, Row, + postgres::PgRow, }; use time::OffsetDateTime; use tonic::{ diff --git a/src/grpc/endpoints/get_login_activity.rs b/src/grpc/endpoints/get_login_activity.rs index 01ffc15..2d86ef4 100644 --- a/src/grpc/endpoints/get_login_activity.rs +++ b/src/grpc/endpoints/get_login_activity.rs @@ -13,8 +13,8 @@ use crate::{ utils::{ extract_session_key_from_cookie::extract_session_key_from_cookie, get_user_sessions::{ - get_user_sessions, UserSession, + get_user_sessions, }, to_iso8601::to_iso8601, }, @@ -142,8 +142,8 @@ mod tests { }, grpc::defs::login_activity_def::v1::GetLoginActivityRequest, test_utils::{ - test_grpc_service, RedisTestContext, + test_grpc_service, }, utils::{ get_client_device::ClientDevice, diff --git a/src/grpc/endpoints/get_story/get_story.rs b/src/grpc/endpoints/get_story/get_story.rs index ac8d78d..ec7c049 100644 --- a/src/grpc/endpoints/get_story/get_story.rs +++ b/src/grpc/endpoints/get_story/get_story.rs @@ -24,8 +24,8 @@ use crate::{ use redis::AsyncCommands; use serde::Deserialize; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use tonic::{ @@ -351,8 +351,8 @@ mod tests { constants::redis_namespaces::RedisNamespace, grpc::defs::story_def::v1::GetStoryRequest, test_utils::{ - test_grpc_service, RedisTestContext, + test_grpc_service, }, }; use redis::AsyncCommands; diff --git a/src/grpc/endpoints/get_story_metadata/get_story_metadata.rs b/src/grpc/endpoints/get_story_metadata/get_story_metadata.rs index 8eda836..1ccf2ed 100644 --- a/src/grpc/endpoints/get_story_metadata/get_story_metadata.rs +++ b/src/grpc/endpoints/get_story_metadata/get_story_metadata.rs @@ -15,8 +15,8 @@ use crate::{ }; use serde::Deserialize; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use tonic::{ diff --git a/src/grpc/endpoints/get_token.rs b/src/grpc/endpoints/get_token.rs index 0f31bea..a420b06 100644 --- a/src/grpc/endpoints/get_token.rs +++ b/src/grpc/endpoints/get_token.rs @@ -10,9 +10,9 @@ use crate::{ }, }; use argon2::{ - password_hash::SaltString, Argon2, PasswordHasher, + password_hash::SaltString, }; use sqlx::Row; use time::OffsetDateTime; diff --git a/src/grpc/endpoints/verify_email.rs b/src/grpc/endpoints/verify_email.rs index cf39803..8789fd2 100644 --- a/src/grpc/endpoints/verify_email.rs +++ b/src/grpc/endpoints/verify_email.rs @@ -10,9 +10,9 @@ use crate::{ }, }; use argon2::{ - password_hash::SaltString, Argon2, PasswordHasher, + password_hash::SaltString, }; use sqlx::Row; use tonic::{ diff --git a/src/grpc/endpoints/verify_newsletter_subscription/verify_newsletter_subscription.rs b/src/grpc/endpoints/verify_newsletter_subscription/verify_newsletter_subscription.rs index d928d02..6d329de 100644 --- a/src/grpc/endpoints/verify_newsletter_subscription/verify_newsletter_subscription.rs +++ b/src/grpc/endpoints/verify_newsletter_subscription/verify_newsletter_subscription.rs @@ -9,9 +9,9 @@ use crate::{ }, }; use argon2::{ - password_hash::SaltString, Argon2, PasswordHasher, + password_hash::SaltString, }; use sqlx::Row; use tonic::{ diff --git a/src/grpc/server.rs b/src/grpc/server.rs index 6a12b5f..838d996 100644 --- a/src/grpc/server.rs +++ b/src/grpc/server.rs @@ -1,10 +1,10 @@ use crate::{ + RedisPool, config::Config, grpc::{ defs::grpc_service::v1::api_service_server::ApiServiceServer, service::GrpcService, }, - RedisPool, }; use sqlx::{ Pool, @@ -15,14 +15,14 @@ use std::{ time::Duration, }; use tonic::{ + Request, + Status, codec::CompressionEncoding, codegen::InterceptedService, metadata::{ Ascii, MetadataValue, }, - Request, - Status, }; /// Authentication middleware. diff --git a/src/grpc/service.rs b/src/grpc/service.rs index ab943dd..f471654 100644 --- a/src/grpc/service.rs +++ b/src/grpc/service.rs @@ -1,4 +1,5 @@ use crate::{ + RedisPool, config, grpc::{ defs::{ @@ -110,7 +111,6 @@ use crate::{ }, endpoints, }, - RedisPool, }; use sqlx::{ Pool, diff --git a/src/iso8601.rs b/src/iso8601.rs index 098b3bf..5de2ccf 100644 --- a/src/iso8601.rs +++ b/src/iso8601.rs @@ -1,10 +1,10 @@ use ::time::format_description::well_known::{ + Iso8601, iso8601::{ Config, EncodedConfig, TimePrecision, }, - Iso8601, }; use std::num::NonZeroU8; diff --git a/src/oauth/discord.rs b/src/oauth/discord.rs index cf24403..2d29ed4 100644 --- a/src/oauth/discord.rs +++ b/src/oauth/discord.rs @@ -1,11 +1,11 @@ use crate::oauth::OAuthClient; use oauth2::{ - basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl, + basic::BasicClient, }; /// Builds and returns Discord oauth client. diff --git a/src/oauth/dribbble.rs b/src/oauth/dribbble.rs index 21196d8..616e804 100644 --- a/src/oauth/dribbble.rs +++ b/src/oauth/dribbble.rs @@ -1,11 +1,11 @@ use crate::oauth::OAuthClient; use oauth2::{ - basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl, + basic::BasicClient, }; /// Builds and returns Dribbble oauth client. diff --git a/src/oauth/github.rs b/src/oauth/github.rs index 7e9213e..e5fb749 100644 --- a/src/oauth/github.rs +++ b/src/oauth/github.rs @@ -1,11 +1,11 @@ use crate::oauth::OAuthClient; use oauth2::{ - basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl, + basic::BasicClient, }; /// Builds and returns GitHub oauth client. diff --git a/src/oauth/google.rs b/src/oauth/google.rs index 7b1332e..3296abd 100644 --- a/src/oauth/google.rs +++ b/src/oauth/google.rs @@ -1,11 +1,11 @@ use crate::oauth::OAuthClient; use oauth2::{ - basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl, + basic::BasicClient, }; use serde::Deserialize; use validator::Validate; diff --git a/src/oauth/mod.rs b/src/oauth/mod.rs index 917ee66..50238a6 100644 --- a/src/oauth/mod.rs +++ b/src/oauth/mod.rs @@ -1,18 +1,18 @@ use crate::{ - config::Config, OAuthClientMap, + config::Config, }; use oauth2::{ + Client, + EndpointNotSet, + EndpointSet, + StandardRevocableToken, basic::{ BasicErrorResponse, BasicRevocationErrorResponse, BasicTokenIntrospectionResponse, BasicTokenResponse, }, - Client, - EndpointNotSet, - EndpointSet, - StandardRevocableToken, }; pub mod icons; diff --git a/src/oauth/spotify.rs b/src/oauth/spotify.rs index 981f487..313580f 100644 --- a/src/oauth/spotify.rs +++ b/src/oauth/spotify.rs @@ -1,11 +1,11 @@ use crate::oauth::OAuthClient; use oauth2::{ - basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl, + basic::BasicClient, }; /// Builds and returns Spotify oauth client. diff --git a/src/oauth/youtube.rs b/src/oauth/youtube.rs index af89006..8a57460 100644 --- a/src/oauth/youtube.rs +++ b/src/oauth/youtube.rs @@ -1,11 +1,11 @@ use crate::oauth::OAuthClient; use oauth2::{ - basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl, + basic::BasicClient, }; /// Builds and returns YouTube oauth client. diff --git a/src/realms/awareness.rs b/src/realms/awareness.rs index f427119..41457aa 100644 --- a/src/realms/awareness.rs +++ b/src/realms/awareness.rs @@ -4,8 +4,8 @@ use serde::{ }; use std::{ collections::{ - hash_map::Entry, HashMap, + hash_map::Entry, }, fmt::Formatter, mem::MaybeUninit, @@ -15,6 +15,9 @@ use std::{ use thiserror::Error; use tokio::sync::RwLock; use yrs::{ + Doc, + Observer, + Subscription, block::ClientID, encoding::read, updates::{ @@ -27,9 +30,6 @@ use yrs::{ Encoder, }, }, - Doc, - Observer, - Subscription, }; const NULL_STR: &str = "null"; @@ -433,8 +433,8 @@ impl std::fmt::Debug for Awareness { mod test { use super::*; use std::sync::mpsc::{ - channel, Receiver, + channel, }; use yrs::Doc; diff --git a/src/realms/broadcast.rs b/src/realms/broadcast.rs index 5ec421f..bf2d246 100644 --- a/src/realms/broadcast.rs +++ b/src/realms/broadcast.rs @@ -3,9 +3,9 @@ use super::{ connection::handle_message, protocol::{ Error, - Message, MSG_SYNC, MSG_SYNC_UPDATE, + Message, }, }; use futures_util::{ @@ -16,13 +16,13 @@ use std::sync::Arc; use tokio::{ select, sync::{ + Mutex, broadcast::{ - channel, - error::SendError, Receiver, Sender, + channel, + error::SendError, }, - Mutex, }, task::JoinHandle, }; @@ -286,9 +286,9 @@ mod test { }, }; use futures_util::{ - ready, SinkExt, StreamExt, + ready, }; use std::{ collections::HashMap, @@ -305,14 +305,14 @@ mod test { }; use tokio_util::sync::PollSender; use yrs::{ - updates::{ - decoder::Decode, - encoder::Encode, - }, Doc, StateVector, Text, Transact, + updates::{ + decoder::Decode, + encoder::Encode, + }, }; #[derive(Debug)] diff --git a/src/realms/connection.rs b/src/realms/connection.rs index cb4ace0..6cd1c50 100644 --- a/src/realms/connection.rs +++ b/src/realms/connection.rs @@ -26,8 +26,8 @@ use warp::ws::{ WebSocket, }; use yrs::{ - updates::decoder::Decode, Update, + updates::decoder::Decode, }; /// A warp websocket sink wrapper, that implements futures `Sink` in a way, that makes it @@ -183,14 +183,14 @@ mod test { }; use crate::realms::awareness::AwarenessRef; use futures_util::{ + SinkExt, + Stream, + StreamExt, ready, stream::{ SplitSink, SplitStream, }, - SinkExt, - Stream, - StreamExt, }; use serde::{ Deserialize, @@ -228,21 +228,26 @@ mod test { }, }; use tokio_tungstenite::{ - tungstenite::Message, MaybeTlsStream, WebSocketStream, + tungstenite::Message, }; use warp::{ - ws::{ - WebSocket, - Ws, - }, Filter, Rejection, Reply, Sink, + ws::{ + WebSocket, + Ws, + }, }; use yrs::{ + Doc, + GetString, + Subscription, + Text, + Transact, encoding::read::Cursor, updates::{ decoder::DecoderV1, @@ -252,11 +257,6 @@ mod test { EncoderV1, }, }, - Doc, - GetString, - Subscription, - Text, - Transact, }; /// The connection handler over a pair of message streams, which implements an awareness and diff --git a/src/realms/protocol.rs b/src/realms/protocol.rs index 6af1a02..10042d2 100644 --- a/src/realms/protocol.rs +++ b/src/realms/protocol.rs @@ -8,6 +8,10 @@ use super::{ use thiserror::Error; use tracing::warn; use yrs::{ + ReadTxn, + StateVector, + Transact, + Update, encoding::read, updates::{ decoder::{ @@ -19,10 +23,6 @@ use yrs::{ Encoder, }, }, - ReadTxn, - StateVector, - Transact, - Update, }; /// The maximum size (in bytes) of the individual incoming awareness update. Awareness updates @@ -423,6 +423,12 @@ mod test { use super::*; use std::collections::HashMap; use yrs::{ + Doc, + GetString, + ReadTxn, + StateVector, + Text, + Transact, encoding::read::Cursor, updates::{ decoder::{ @@ -434,12 +440,6 @@ mod test { EncoderV1, }, }, - Doc, - GetString, - ReadTxn, - StateVector, - Text, - Transact, }; #[test] diff --git a/src/routes/favicon.rs b/src/routes/favicon.rs index 894e100..9f25ed5 100644 --- a/src/routes/favicon.rs +++ b/src/routes/favicon.rs @@ -1,8 +1,8 @@ use crate::error::AppError; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use tracing::error; diff --git a/src/routes/health.rs b/src/routes/health.rs index 64b6b57..b61dafc 100644 --- a/src/routes/health.rs +++ b/src/routes/health.rs @@ -1,9 +1,9 @@ use crate::error::AppError; use actix_web::{ + HttpResponse, get, http::header::ContentType, web, - HttpResponse, }; #[get("/health")] diff --git a/src/routes/index.rs b/src/routes/index.rs index b2fdabc..6fb1c29 100644 --- a/src/routes/index.rs +++ b/src/routes/index.rs @@ -1,12 +1,12 @@ use crate::{ - error::AppError, IndexTemplate, + error::AppError, }; use actix_web::{ + HttpResponse, get, http::header::ContentType, web, - HttpResponse, }; use sailfish::TemplateOnce; use tracing_actix_web::RequestId; diff --git a/src/routes/oauth/discord/callback.rs b/src/routes/oauth/discord/callback.rs index 494aac7..7e6ffb8 100644 --- a/src/routes/oauth/discord/callback.rs +++ b/src/routes/oauth/discord/callback.rs @@ -1,4 +1,6 @@ use crate::{ + AppState, + ConnectionTemplate, constants::connection_provider::ConnectionProvider, error::AppError, middlewares::identity::identity::Identity, @@ -7,14 +9,12 @@ use crate::{ AuthRequest, ConnectionError, }, - AppState, - ConnectionTemplate, }; use actix_web::{ + HttpResponse, get, http::header::ContentType, web, - HttpResponse, }; use actix_web_validator::QsQuery; use http::header; @@ -183,8 +183,8 @@ mod tests { init_app_for_test, }; use actix_web::{ - test, Responder, + test, }; use sqlx::{ PgPool, diff --git a/src/routes/oauth/discord/discord.rs b/src/routes/oauth/discord/discord.rs index d339b5a..f785a50 100644 --- a/src/routes/oauth/discord/discord.rs +++ b/src/routes/oauth/discord/discord.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, http::header, web, - HttpResponse, }; use oauth2::{ CsrfToken, diff --git a/src/routes/oauth/dribbble/callback.rs b/src/routes/oauth/dribbble/callback.rs index 073bae4..eda32e3 100644 --- a/src/routes/oauth/dribbble/callback.rs +++ b/src/routes/oauth/dribbble/callback.rs @@ -1,4 +1,6 @@ use crate::{ + AppState, + ConnectionTemplate, constants::connection_provider::ConnectionProvider, error::AppError, middlewares::identity::identity::Identity, @@ -7,14 +9,12 @@ use crate::{ AuthRequest, ConnectionError, }, - AppState, - ConnectionTemplate, }; use actix_web::{ + HttpResponse, get, http::header::ContentType, web, - HttpResponse, }; use actix_web_validator::QsQuery; use http::header; @@ -183,8 +183,8 @@ mod tests { init_app_for_test, }; use actix_web::{ - test, Responder, + test, }; use sqlx::{ PgPool, diff --git a/src/routes/oauth/dribbble/dribbble.rs b/src/routes/oauth/dribbble/dribbble.rs index 0769273..28159ae 100644 --- a/src/routes/oauth/dribbble/dribbble.rs +++ b/src/routes/oauth/dribbble/dribbble.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, http::header, web, - HttpResponse, }; use oauth2::{ CsrfToken, diff --git a/src/routes/oauth/github/callback.rs b/src/routes/oauth/github/callback.rs index 8dccfc8..0cc698c 100644 --- a/src/routes/oauth/github/callback.rs +++ b/src/routes/oauth/github/callback.rs @@ -1,4 +1,6 @@ use crate::{ + AppState, + ConnectionTemplate, constants::connection_provider::ConnectionProvider, error::AppError, middlewares::identity::identity::Identity, @@ -7,14 +9,12 @@ use crate::{ AuthRequest, ConnectionError, }, - AppState, - ConnectionTemplate, }; use actix_web::{ + HttpResponse, get, http::header::ContentType, web, - HttpResponse, }; use actix_web_validator::QsQuery; use http::header; @@ -235,8 +235,8 @@ mod tests { init_app_for_test, }; use actix_web::{ - test, Responder, + test, }; use sqlx::{ PgPool, diff --git a/src/routes/oauth/github/github.rs b/src/routes/oauth/github/github.rs index 43a4088..002ae9c 100644 --- a/src/routes/oauth/github/github.rs +++ b/src/routes/oauth/github/github.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, http::header, web, - HttpResponse, }; use oauth2::{ CsrfToken, diff --git a/src/routes/oauth/spotify/callback.rs b/src/routes/oauth/spotify/callback.rs index f2015fb..00f8e7d 100644 --- a/src/routes/oauth/spotify/callback.rs +++ b/src/routes/oauth/spotify/callback.rs @@ -1,4 +1,6 @@ use crate::{ + AppState, + ConnectionTemplate, constants::connection_provider::ConnectionProvider, error::AppError, middlewares::identity::identity::Identity, @@ -7,14 +9,12 @@ use crate::{ AuthRequest, ConnectionError, }, - AppState, - ConnectionTemplate, }; use actix_web::{ + HttpResponse, get, http::header::ContentType, web, - HttpResponse, }; use actix_web_validator::QsQuery; use http::header; @@ -185,8 +185,8 @@ mod tests { init_app_for_test, }; use actix_web::{ - test, Responder, + test, }; use sqlx::{ PgPool, diff --git a/src/routes/oauth/spotify/spotify.rs b/src/routes/oauth/spotify/spotify.rs index 23c6fde..02d1a8d 100644 --- a/src/routes/oauth/spotify/spotify.rs +++ b/src/routes/oauth/spotify/spotify.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, http::header, web, - HttpResponse, }; use oauth2::{ CsrfToken, diff --git a/src/routes/oauth/youtube/youtube.rs b/src/routes/oauth/youtube/youtube.rs index 758078e..89aa1cf 100644 --- a/src/routes/oauth/youtube/youtube.rs +++ b/src/routes/oauth/youtube/youtube.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, http::header, web, - HttpResponse, }; use oauth2::{ CsrfToken, diff --git a/src/routes/robots.rs b/src/routes/robots.rs index cef1a1a..33d67d4 100644 --- a/src/routes/robots.rs +++ b/src/routes/robots.rs @@ -1,9 +1,9 @@ use crate::error::AppError; use actix_web::{ + HttpResponse, get, http::header::ContentType, web, - HttpResponse, }; const ROBOTS_TXT: &str = include_str!("../../static/robots.txt"); diff --git a/src/routes/v1/auth/external/google/callback.rs b/src/routes/v1/auth/external/google/callback.rs index 85ae176..d0a9826 100644 --- a/src/routes/v1/auth/external/google/callback.rs +++ b/src/routes/v1/auth/external/google/callback.rs @@ -1,4 +1,6 @@ use crate::{ + AppState, + ExternalAuthTemplate, constants::{ notification_entity_type::NotificationEntityType, user_flag::UserFlag, @@ -9,8 +11,8 @@ use crate::{ }, middlewares::identity::identity::Identity, oauth::{ - icons::google::GOOGLE_LOGO, GoogleOAuthResponse, + icons::google::GOOGLE_LOGO, }, routes::oauth::AuthRequest, utils::{ @@ -25,19 +27,17 @@ use crate::{ get_user_sessions::get_user_sessions, truncate_str::truncate_str, }, - AppState, - ExternalAuthTemplate, }; use actix_http::{ - header, HttpMessage, + header, }; use actix_web::{ + HttpRequest, + HttpResponse, get, http::header::ContentType, web, - HttpRequest, - HttpResponse, }; use actix_web_validator::QsQuery; use oauth2::{ @@ -415,8 +415,8 @@ mod tests { init_app_for_test, }; use actix_web::{ - test, Responder, + test, }; use sqlx::PgPool; diff --git a/src/routes/v1/auth/external/google/google.rs b/src/routes/v1/auth/external/google/google.rs index c6ea689..97513a2 100644 --- a/src/routes/v1/auth/external/google/google.rs +++ b/src/routes/v1/auth/external/google/google.rs @@ -1,12 +1,12 @@ use crate::{ - error::AppError, AppState, + error::AppError, }; use actix_web::{ + HttpResponse, get, http::header, web, - HttpResponse, }; use oauth2::{ CsrfToken, diff --git a/src/routes/v1/auth/mfa_preflight.rs b/src/routes/v1/auth/mfa_preflight.rs index 9dfcaed..c2b1fdf 100644 --- a/src/routes/v1/auth/mfa_preflight.rs +++ b/src/routes/v1/auth/mfa_preflight.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::resource_lock::ResourceLock, error::{ AppError, @@ -9,13 +10,12 @@ use crate::{ is_resource_locked::is_resource_locked, reset_resource_lock::reset_resource_lock, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ @@ -132,20 +132,20 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_toast_error_response, exceed_resource_lock_attempts, get_resource_lock_attempts, init_app_for_test, res_to_string, - RedisTestContext, }; use actix_web::test; use argon2::{ + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - PasswordHasher, }; use sqlx::PgPool; use storiny_macros::test_context; diff --git a/src/routes/v1/auth/recovery/recovery.rs b/src/routes/v1/auth/recovery/recovery.rs index 0e537db..7a0836e 100644 --- a/src/routes/v1/auth/recovery/recovery.rs +++ b/src/routes/v1/auth/recovery/recovery.rs @@ -1,7 +1,8 @@ use crate::{ + AppState, amqp::consumers::templated_email::{ - TemplatedEmailMessage, TEMPLATED_EMAIL_QUEUE_NAME, + TemplatedEmailMessage, }, constants::{ email_template::EmailTemplate, @@ -19,13 +20,12 @@ use crate::{ incr_resource_lock_attempts::incr_resource_lock_attempts, is_resource_locked::is_resource_locked, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use actix_web_validator::Json; use chrono::{ @@ -33,8 +33,8 @@ use chrono::{ Local, }; use deadpool_lapin::lapin::{ - options::BasicPublishOptions, BasicProperties, + options::BasicPublishOptions, }; use serde::{ Deserialize, @@ -176,11 +176,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_form_error_response, exceed_resource_lock_attempts, get_resource_lock_attempts, init_app_for_test, - RedisTestContext, }; use actix_web::test; use sqlx::PgPool; diff --git a/src/routes/v1/auth/resend_verification_email/resend_verification_email.rs b/src/routes/v1/auth/resend_verification_email/resend_verification_email.rs index fe98a2a..88802a8 100644 --- a/src/routes/v1/auth/resend_verification_email/resend_verification_email.rs +++ b/src/routes/v1/auth/resend_verification_email/resend_verification_email.rs @@ -1,7 +1,8 @@ use crate::{ + AppState, amqp::consumers::templated_email::{ - TemplatedEmailMessage, TEMPLATED_EMAIL_QUEUE_NAME, + TemplatedEmailMessage, }, constants::{ email_template::EmailTemplate, @@ -22,13 +23,12 @@ use crate::{ incr_resource_lock_attempts::incr_resource_lock_attempts, is_resource_locked::is_resource_locked, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use actix_web_validator::Json; use chrono::{ @@ -36,8 +36,8 @@ use chrono::{ Local, }; use deadpool_lapin::lapin::{ - options::BasicPublishOptions, BasicProperties, + options::BasicPublishOptions, }; use serde::{ Deserialize, @@ -257,12 +257,12 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_form_error_response, assert_toast_error_response, exceed_resource_lock_attempts, get_resource_lock_attempts, init_app_for_test, - RedisTestContext, }; use actix_web::test; use sqlx::PgPool; diff --git a/src/routes/v1/auth/reset_password/reset_password.rs b/src/routes/v1/auth/reset_password/reset_password.rs index fd30c3e..84374c6 100644 --- a/src/routes/v1/auth/reset_password/reset_password.rs +++ b/src/routes/v1/auth/reset_password/reset_password.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::resource_lock::ResourceLock, error::{ AppError, @@ -12,24 +13,23 @@ use crate::{ is_resource_locked::is_resource_locked, reset_resource_lock::reset_resource_lock, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ - password_hash::{ - rand_core::OsRng, - SaltString, - }, Argon2, PasswordHash, PasswordHasher, PasswordVerifier, + password_hash::{ + SaltString, + rand_core::OsRng, + }, }; use serde::{ Deserialize, @@ -218,18 +218,18 @@ mod tests { token::TOKEN_LENGTH, }, test_utils::{ + RedisTestContext, assert_form_error_response, assert_toast_error_response, exceed_resource_lock_attempts, get_resource_lock_attempts, init_app_for_test, - RedisTestContext, }, utils::{ generate_hashed_token::generate_hashed_token, get_user_sessions::{ - get_user_sessions, UserSession, + get_user_sessions, }, }, }; diff --git a/src/routes/v1/auth/signup.rs b/src/routes/v1/auth/signup.rs index 70e63f1..6592872 100644 --- a/src/routes/v1/auth/signup.rs +++ b/src/routes/v1/auth/signup.rs @@ -1,7 +1,8 @@ use crate::{ + AppState, amqp::consumers::templated_email::{ - TemplatedEmailMessage, TEMPLATED_EMAIL_QUEUE_NAME, + TemplatedEmailMessage, }, constants::{ email_template::EmailTemplate, @@ -22,31 +23,30 @@ use crate::{ incr_resource_lock_attempts::incr_resource_lock_attempts, is_resource_locked::is_resource_locked, }, - AppState, }; use actix_web::{ + HttpRequest, + HttpResponse, http::StatusCode, post, web, - HttpRequest, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ + Argon2, + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - Argon2, - PasswordHasher, }; use chrono::{ Datelike, Local, }; use deadpool_lapin::lapin::{ - options::BasicPublishOptions, BasicProperties, + options::BasicPublishOptions, }; use serde::{ Deserialize, @@ -257,11 +257,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_form_error_response, exceed_resource_lock_attempts, get_resource_lock_attempts, init_app_for_test, - RedisTestContext, }; use actix_web::test; use argon2::{ diff --git a/src/routes/v1/blogs/archive/archive.rs b/src/routes/v1/blogs/archive/archive.rs index 8573246..69db763 100644 --- a/src/routes/v1/blogs/archive/archive.rs +++ b/src/routes/v1/blogs/archive/archive.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -14,8 +14,8 @@ use serde::{ Serialize, }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/blogs/editors/editors.rs b/src/routes/v1/blogs/editors/editors.rs index f0ff161..e88ca7c 100644 --- a/src/routes/v1/blogs/editors/editors.rs +++ b/src/routes/v1/blogs/editors/editors.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ diff --git a/src/routes/v1/blogs/feed/feed.rs b/src/routes/v1/blogs/feed/feed.rs index fa35131..26312bc 100644 --- a/src/routes/v1/blogs/feed/feed.rs +++ b/src/routes/v1/blogs/feed/feed.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -17,8 +17,8 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/blogs/writers/writers.rs b/src/routes/v1/blogs/writers/writers.rs index b84f60d..b3fad39 100644 --- a/src/routes/v1/blogs/writers/writers.rs +++ b/src/routes/v1/blogs/writers/writers.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ diff --git a/src/routes/v1/feed/feed.rs b/src/routes/v1/feed/feed.rs index e9cf9b4..53f4412 100644 --- a/src/routes/v1/feed/feed.rs +++ b/src/routes/v1/feed/feed.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -16,8 +16,8 @@ use serde::{ Serialize, }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/account_activity.rs b/src/routes/v1/me/account_activity.rs index b53e9c9..2b91705 100644 --- a/src/routes/v1/me/account_activity.rs +++ b/src/routes/v1/me/account_activity.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, constants::account_activity_type::AccountActivityType, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -17,8 +17,8 @@ use serde::{ use sqlx::FromRow; use time::{ - format_description, OffsetDateTime, + format_description, }; use validator::Validate; diff --git a/src/routes/v1/me/assets/alt.rs b/src/routes/v1/me/assets/alt.rs index 2ef14dc..d0d2fa2 100644 --- a/src/routes/v1/me/assets/alt.rs +++ b/src/routes/v1/me/assets/alt.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/assets/favourite.rs b/src/routes/v1/me/assets/favourite.rs index 55f4f3c..d7b8efd 100644 --- a/src/routes/v1/me/assets/favourite.rs +++ b/src/routes/v1/me/assets/favourite.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/assets/rating.rs b/src/routes/v1/me/assets/rating.rs index 38f6a20..81db857 100644 --- a/src/routes/v1/me/assets/rating.rs +++ b/src/routes/v1/me/assets/rating.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blocked_users/delete.rs b/src/routes/v1/me/blocked_users/delete.rs index 8326a77..38b0711 100644 --- a/src/routes/v1/me/blocked_users/delete.rs +++ b/src/routes/v1/me/blocked_users/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/blocked_users/get.rs b/src/routes/v1/me/blocked_users/get.rs index 303c311..50bfc3f 100644 --- a/src/routes/v1/me/blocked_users/get.rs +++ b/src/routes/v1/me/blocked_users/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ diff --git a/src/routes/v1/me/blocked_users/post.rs b/src/routes/v1/me/blocked_users/post.rs index cd1cac1..d172854 100644 --- a/src/routes/v1/me/blocked_users/post.rs +++ b/src/routes/v1/me/blocked_users/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ resource_limit::ResourceLimit, sql_states::SqlState, @@ -9,13 +10,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -116,11 +116,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_http::StatusCode; use actix_web::test; diff --git a/src/routes/v1/me/blog_requests/delete.rs b/src/routes/v1/me/blog_requests/delete.rs index 82c0784..556e252 100644 --- a/src/routes/v1/me/blog_requests/delete.rs +++ b/src/routes/v1/me/blog_requests/delete.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use sqlx::Row; diff --git a/src/routes/v1/me/blog_requests/get.rs b/src/routes/v1/me/blog_requests/get.rs index 0d27ed7..e1e595a 100644 --- a/src/routes/v1/me/blog_requests/get.rs +++ b/src/routes/v1/me/blog_requests/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -14,10 +14,10 @@ use serde::{ Serialize, }; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/blog_requests/post.rs b/src/routes/v1/me/blog_requests/post.rs index e88079a..31937ff 100644 --- a/src/routes/v1/me/blog_requests/post.rs +++ b/src/routes/v1/me/blog_requests/post.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use sqlx::Row; diff --git a/src/routes/v1/me/blogs/content/pending_stories/pending_stories.rs b/src/routes/v1/me/blogs/content/pending_stories/pending_stories.rs index d3aac3c..cefc9f5 100644 --- a/src/routes/v1/me/blogs/content/pending_stories/pending_stories.rs +++ b/src/routes/v1/me/blogs/content/pending_stories/pending_stories.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -16,9 +16,9 @@ use serde::{ Serialize, }; use sqlx::{ - types::Json, FromRow, Row, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/blogs/content/published_stories/published_stories.rs b/src/routes/v1/me/blogs/content/published_stories/published_stories.rs index 834ca57..408e8ae 100644 --- a/src/routes/v1/me/blogs/content/published_stories/published_stories.rs +++ b/src/routes/v1/me/blogs/content/published_stories/published_stories.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -16,9 +16,9 @@ use serde::{ Serialize, }; use sqlx::{ - types::Json, FromRow, Row, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/blogs/editor_requests/cancel.rs b/src/routes/v1/me/blogs/editor_requests/cancel.rs index bc47a64..6cfab32 100644 --- a/src/routes/v1/me/blogs/editor_requests/cancel.rs +++ b/src/routes/v1/me/blogs/editor_requests/cancel.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/blogs/editor_requests/get.rs b/src/routes/v1/me/blogs/editor_requests/get.rs index 58805bc..311f9f8 100644 --- a/src/routes/v1/me/blogs/editor_requests/get.rs +++ b/src/routes/v1/me/blogs/editor_requests/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -14,11 +14,11 @@ use serde::{ Serialize, }; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, Row, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/blogs/editors/invite.rs b/src/routes/v1/me/blogs/editors/invite.rs index 81c991c..8cb468f 100644 --- a/src/routes/v1/me/blogs/editors/invite.rs +++ b/src/routes/v1/me/blogs/editors/invite.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ notification_entity_type::NotificationEntityType, resource_limit::ResourceLimit, @@ -14,13 +15,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ @@ -222,12 +222,12 @@ mod tests { use crate::{ grpc::defs::privacy_settings_def::v1::IncomingBlogRequest, test_utils::{ + RedisTestContext, assert_response_body_text, assert_toast_error_response, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }, }; use actix_http::StatusCode; diff --git a/src/routes/v1/me/blogs/editors/remove.rs b/src/routes/v1/me/blogs/editors/remove.rs index e95c001..0ddf57a 100644 --- a/src/routes/v1/me/blogs/editors/remove.rs +++ b/src/routes/v1/me/blogs/editors/remove.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/blogs/get.rs b/src/routes/v1/me/blogs/get.rs index b0e737d..c44d58a 100644 --- a/src/routes/v1/me/blogs/get.rs +++ b/src/routes/v1/me/blogs/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ diff --git a/src/routes/v1/me/blogs/post.rs b/src/routes/v1/me/blogs/post.rs index 9f2b7d6..5dbf838 100644 --- a/src/routes/v1/me/blogs/post.rs +++ b/src/routes/v1/me/blogs/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ blog_slug_regex::BLOG_SLUG_REGEX, reserved_keywords::RESERVED_KEYWORDS, @@ -14,13 +15,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ @@ -161,10 +161,10 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_http::StatusCode; use actix_web::test; diff --git a/src/routes/v1/me/blogs/settings/appearance/branding.rs b/src/routes/v1/me/blogs/settings/appearance/branding.rs index 1cad41f..420630b 100644 --- a/src/routes/v1/me/blogs/settings/appearance/branding.rs +++ b/src/routes/v1/me/blogs/settings/appearance/branding.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/appearance/favicon.rs b/src/routes/v1/me/blogs/settings/appearance/favicon.rs index f4fa2f2..0e6867c 100644 --- a/src/routes/v1/me/blogs/settings/appearance/favicon.rs +++ b/src/routes/v1/me/blogs/settings/appearance/favicon.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/appearance/mark.rs b/src/routes/v1/me/blogs/settings/appearance/mark.rs index 314702b..cf62356 100644 --- a/src/routes/v1/me/blogs/settings/appearance/mark.rs +++ b/src/routes/v1/me/blogs/settings/appearance/mark.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/blogs/settings/appearance/page_layout.rs b/src/routes/v1/me/blogs/settings/appearance/page_layout.rs index 1674071..f0fbf7b 100644 --- a/src/routes/v1/me/blogs/settings/appearance/page_layout.rs +++ b/src/routes/v1/me/blogs/settings/appearance/page_layout.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/appearance/story_layout.rs b/src/routes/v1/me/blogs/settings/appearance/story_layout.rs index ea67a66..84701cc 100644 --- a/src/routes/v1/me/blogs/settings/appearance/story_layout.rs +++ b/src/routes/v1/me/blogs/settings/appearance/story_layout.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/appearance/theme.rs b/src/routes/v1/me/blogs/settings/appearance/theme.rs index 6fe113d..09cb052 100644 --- a/src/routes/v1/me/blogs/settings/appearance/theme.rs +++ b/src/routes/v1/me/blogs/settings/appearance/theme.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/blogs/settings/banner.rs b/src/routes/v1/me/blogs/settings/banner.rs index e1231dc..2097395 100644 --- a/src/routes/v1/me/blogs/settings/banner.rs +++ b/src/routes/v1/me/blogs/settings/banner.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/connections.rs b/src/routes/v1/me/blogs/settings/connections.rs index fe21985..0ad4966 100644 --- a/src/routes/v1/me/blogs/settings/connections.rs +++ b/src/routes/v1/me/blogs/settings/connections.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, FormErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/domain/code_request.rs b/src/routes/v1/me/blogs/settings/domain/code_request.rs index 518de38..b710036 100644 --- a/src/routes/v1/me/blogs/settings/domain/code_request.rs +++ b/src/routes/v1/me/blogs/settings/domain/code_request.rs @@ -1,4 +1,6 @@ use crate::{ + AppState, + HmacSha1, constants::{ blog_domain_regex::BLOG_DOMAIN_REGEX, domain_verification_key::DOMAIN_VERIFICATION_TXT_RECORD_KEY, @@ -9,13 +11,11 @@ use crate::{ ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, - HmacSha1, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use hex::encode as encode_hex; diff --git a/src/routes/v1/me/blogs/settings/domain/remove.rs b/src/routes/v1/me/blogs/settings/domain/remove.rs index 6089cd7..5cafdc9 100644 --- a/src/routes/v1/me/blogs/settings/domain/remove.rs +++ b/src/routes/v1/me/blogs/settings/domain/remove.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use sqlx::Row; diff --git a/src/routes/v1/me/blogs/settings/domain/verify.rs b/src/routes/v1/me/blogs/settings/domain/verify.rs index 0320228..c30cfa4 100644 --- a/src/routes/v1/me/blogs/settings/domain/verify.rs +++ b/src/routes/v1/me/blogs/settings/domain/verify.rs @@ -1,4 +1,6 @@ use crate::{ + AppState, + HmacSha1, config::Config, constants::{ blog_domain_regex::BLOG_DOMAIN_REGEX, @@ -10,22 +12,20 @@ use crate::{ ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, - HmacSha1, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use hex::encode as encode_hex; use hickory_resolver::{ + TokioAsyncResolver, config::{ ResolverConfig, ResolverOpts, }, - TokioAsyncResolver, }; use hmac::Mac; use lazy_static::lazy_static; @@ -250,6 +250,7 @@ mod tests { Protocol, }; use hickory_server::{ + ServerFuture, authority::MessageResponseBuilder, proto::{ error::ProtoError, @@ -258,14 +259,14 @@ mod tests { ResponseCode, }, rr::{ + LowerName, + RData, + Record, rdata::{ A, AAAA, TXT, }, - LowerName, - RData, - Record, }, }, server::{ @@ -273,11 +274,10 @@ mod tests { ResponseHandler, ResponseInfo, }, - ServerFuture, }; use sqlx::{ - testing::TestTermination, PgPool, + testing::TestTermination, }; use std::{ net::{ diff --git a/src/routes/v1/me/blogs/settings/general.rs b/src/routes/v1/me/blogs/settings/general.rs index 38acb36..dcd10d5 100644 --- a/src/routes/v1/me/blogs/settings/general.rs +++ b/src/routes/v1/me/blogs/settings/general.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, constants::story_category::STORY_CATEGORY_VEC, error::{ AppError, FormErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/logo.rs b/src/routes/v1/me/blogs/settings/logo.rs index 0f6d06c..423ce17 100644 --- a/src/routes/v1/me/blogs/settings/logo.rs +++ b/src/routes/v1/me/blogs/settings/logo.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/newsletter_splash.rs b/src/routes/v1/me/blogs/settings/newsletter_splash.rs index ee19085..a4f862f 100644 --- a/src/routes/v1/me/blogs/settings/newsletter_splash.rs +++ b/src/routes/v1/me/blogs/settings/newsletter_splash.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/seo.rs b/src/routes/v1/me/blogs/settings/seo.rs index 081e051..067bdf6 100644 --- a/src/routes/v1/me/blogs/settings/seo.rs +++ b/src/routes/v1/me/blogs/settings/seo.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/sidebars/lsb.rs b/src/routes/v1/me/blogs/settings/sidebars/lsb.rs index 00c0056..b994a31 100644 --- a/src/routes/v1/me/blogs/settings/sidebars/lsb.rs +++ b/src/routes/v1/me/blogs/settings/sidebars/lsb.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, error::{ AppError, FormErrorResponse, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/sidebars/rsb.rs b/src/routes/v1/me/blogs/settings/sidebars/rsb.rs index 50d25d2..70ef6fd 100644 --- a/src/routes/v1/me/blogs/settings/sidebars/rsb.rs +++ b/src/routes/v1/me/blogs/settings/sidebars/rsb.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/slug.rs b/src/routes/v1/me/blogs/settings/slug.rs index 45606d0..40b4806 100644 --- a/src/routes/v1/me/blogs/settings/slug.rs +++ b/src/routes/v1/me/blogs/settings/slug.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ blog_slug_regex::BLOG_SLUG_REGEX, reserved_keywords::RESERVED_KEYWORDS, @@ -8,13 +9,12 @@ use crate::{ FormErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/settings/visibility.rs b/src/routes/v1/me/blogs/settings/visibility.rs index dba9e2a..4e898fc 100644 --- a/src/routes/v1/me/blogs/settings/visibility.rs +++ b/src/routes/v1/me/blogs/settings/visibility.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/blogs/stats/stories/stories.rs b/src/routes/v1/me/blogs/stats/stories/stories.rs index 385c498..4dcead2 100644 --- a/src/routes/v1/me/blogs/stats/stories/stories.rs +++ b/src/routes/v1/me/blogs/stats/stories/stories.rs @@ -1,17 +1,17 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_http::header; use actix_web::{ + HttpResponse, get, http::header::{ CacheControl, CacheDirective, }, web, - HttpResponse, }; use serde::{ Deserialize, diff --git a/src/routes/v1/me/blogs/stories/publish.rs b/src/routes/v1/me/blogs/stories/publish.rs index c330c68..f4320ff 100644 --- a/src/routes/v1/me/blogs/stories/publish.rs +++ b/src/routes/v1/me/blogs/stories/publish.rs @@ -1,13 +1,14 @@ use crate::{ + AppState, amqp::consumers::{ newsletter::{ - NewsletterMessage, NEWSLETTER_QUEUE_NAME, + NewsletterMessage, }, notify_story_add::{ + NOTIFY_STORY_ADD_QUEUE_NAME, NotifyStoryAddMessage, StoryAddSource, - NOTIFY_STORY_ADD_QUEUE_NAME, }, }, error::{ @@ -21,18 +22,17 @@ use crate::{ RealmDestroyReason, }, utils::generate_story_slug::generate_story_slug, - AppState, }; use actix_web::{ + HttpResponse, post, put, web, web::Json, - HttpResponse, }; use deadpool_lapin::lapin::{ - options::BasicPublishOptions, BasicProperties, + options::BasicPublishOptions, }; use futures_util::future; use lockable::AsyncLimit; @@ -547,9 +547,9 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_toast_error_response, init_app_for_test, - RedisTestContext, }; use actix_web::{ services, diff --git a/src/routes/v1/me/blogs/stories/remove.rs b/src/routes/v1/me/blogs/stories/remove.rs index c9577f7..8fe43fa 100644 --- a/src/routes/v1/me/blogs/stories/remove.rs +++ b/src/routes/v1/me/blogs/stories/remove.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, realms::realm::{ RealmData, RealmDestroyReason, }, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use lockable::AsyncLimit; use serde::Deserialize; diff --git a/src/routes/v1/me/blogs/subscribers/get.rs b/src/routes/v1/me/blogs/subscribers/get.rs index 4a9c8e8..7ee2635 100644 --- a/src/routes/v1/me/blogs/subscribers/get.rs +++ b/src/routes/v1/me/blogs/subscribers/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ diff --git a/src/routes/v1/me/blogs/subscribers/import.rs b/src/routes/v1/me/blogs/subscribers/import.rs index 94082b7..1eee5b0 100644 --- a/src/routes/v1/me/blogs/subscribers/import.rs +++ b/src/routes/v1/me/blogs/subscribers/import.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ @@ -23,8 +23,8 @@ use time::{ OffsetDateTime, }; use validator::{ - validate_email, Validate, + validate_email, }; #[derive(Deserialize, Validate)] diff --git a/src/routes/v1/me/blogs/subscribers/remove.rs b/src/routes/v1/me/blogs/subscribers/remove.rs index acdb8ca..fcd6a3b 100644 --- a/src/routes/v1/me/blogs/subscribers/remove.rs +++ b/src/routes/v1/me/blogs/subscribers/remove.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/blogs/writer_requests/cancel.rs b/src/routes/v1/me/blogs/writer_requests/cancel.rs index dc28e63..990ad99 100644 --- a/src/routes/v1/me/blogs/writer_requests/cancel.rs +++ b/src/routes/v1/me/blogs/writer_requests/cancel.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/blogs/writer_requests/get.rs b/src/routes/v1/me/blogs/writer_requests/get.rs index 3b56b42..17f095e 100644 --- a/src/routes/v1/me/blogs/writer_requests/get.rs +++ b/src/routes/v1/me/blogs/writer_requests/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -14,11 +14,11 @@ use serde::{ Serialize, }; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, Row, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/blogs/writers/invite.rs b/src/routes/v1/me/blogs/writers/invite.rs index d1c46c3..10e84c0 100644 --- a/src/routes/v1/me/blogs/writers/invite.rs +++ b/src/routes/v1/me/blogs/writers/invite.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ notification_entity_type::NotificationEntityType, resource_limit::ResourceLimit, @@ -14,13 +15,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ @@ -235,12 +235,12 @@ mod tests { use crate::{ grpc::defs::privacy_settings_def::v1::IncomingBlogRequest, test_utils::{ + RedisTestContext, assert_response_body_text, assert_toast_error_response, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }, }; use actix_http::StatusCode; diff --git a/src/routes/v1/me/blogs/writers/remove.rs b/src/routes/v1/me/blogs/writers/remove.rs index 6aa089c..62ca4cf 100644 --- a/src/routes/v1/me/blogs/writers/remove.rs +++ b/src/routes/v1/me/blogs/writers/remove.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/bookmarks/delete.rs b/src/routes/v1/me/bookmarks/delete.rs index 8026ca0..a634356 100644 --- a/src/routes/v1/me/bookmarks/delete.rs +++ b/src/routes/v1/me/bookmarks/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/bookmarks/get.rs b/src/routes/v1/me/bookmarks/get.rs index bcbcb83..bd85f77 100644 --- a/src/routes/v1/me/bookmarks/get.rs +++ b/src/routes/v1/me/bookmarks/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -17,8 +17,8 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/bookmarks/post.rs b/src/routes/v1/me/bookmarks/post.rs index f1626e6..de3e8bf 100644 --- a/src/routes/v1/me/bookmarks/post.rs +++ b/src/routes/v1/me/bookmarks/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ resource_limit::ResourceLimit, sql_states::SqlState, @@ -9,13 +10,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -109,11 +109,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_http::StatusCode; use actix_web::test; diff --git a/src/routes/v1/me/collaboration_requests/cancel.rs b/src/routes/v1/me/collaboration_requests/cancel.rs index 300d1e9..1b48991 100644 --- a/src/routes/v1/me/collaboration_requests/cancel.rs +++ b/src/routes/v1/me/collaboration_requests/cancel.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/collaboration_requests/delete.rs b/src/routes/v1/me/collaboration_requests/delete.rs index 25bd832..f956831 100644 --- a/src/routes/v1/me/collaboration_requests/delete.rs +++ b/src/routes/v1/me/collaboration_requests/delete.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/collaboration_requests/get.rs b/src/routes/v1/me/collaboration_requests/get.rs index 1d34590..74333d7 100644 --- a/src/routes/v1/me/collaboration_requests/get.rs +++ b/src/routes/v1/me/collaboration_requests/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -16,8 +16,8 @@ use serde::{ Serialize, }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/collaboration_requests/post.rs b/src/routes/v1/me/collaboration_requests/post.rs index c4760da..ee27cd9 100644 --- a/src/routes/v1/me/collaboration_requests/post.rs +++ b/src/routes/v1/me/collaboration_requests/post.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, constants::notification_entity_type::NotificationEntityType, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/comments/delete.rs b/src/routes/v1/me/comments/delete.rs index dd008e0..887e094 100644 --- a/src/routes/v1/me/comments/delete.rs +++ b/src/routes/v1/me/comments/delete.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/comments/get.rs b/src/routes/v1/me/comments/get.rs index 0343e3e..28f1ebb 100644 --- a/src/routes/v1/me/comments/get.rs +++ b/src/routes/v1/me/comments/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -17,10 +17,10 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/comments/patch.rs b/src/routes/v1/me/comments/patch.rs index 9b6c63d..87fe284 100644 --- a/src/routes/v1/me/comments/patch.rs +++ b/src/routes/v1/me/comments/patch.rs @@ -1,19 +1,19 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, utils::md_to_html::{ - md_to_html, MarkdownSource, + md_to_html, }, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/comments/post.rs b/src/routes/v1/me/comments/post.rs index bc3cb8a..95c7c22 100644 --- a/src/routes/v1/me/comments/post.rs +++ b/src/routes/v1/me/comments/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ notification_entity_type::NotificationEntityType, resource_limit::ResourceLimit, @@ -13,17 +14,16 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, md_to_html::{ - md_to_html, MarkdownSource, + md_to_html, }, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ @@ -169,11 +169,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_toast_error_response, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_http::StatusCode; use actix_web::test; diff --git a/src/routes/v1/me/contributions/contributions.rs b/src/routes/v1/me/contributions/contributions.rs index f3d3922..99e3825 100644 --- a/src/routes/v1/me/contributions/contributions.rs +++ b/src/routes/v1/me/contributions/contributions.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -16,8 +16,8 @@ use serde::{ Serialize, }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/drafts/delete.rs b/src/routes/v1/me/drafts/delete.rs index 8e6906f..1179c8c 100644 --- a/src/routes/v1/me/drafts/delete.rs +++ b/src/routes/v1/me/drafts/delete.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, @@ -8,12 +9,11 @@ use crate::{ RealmData, RealmDestroyReason, }, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use lockable::AsyncLimit; use serde::Deserialize; diff --git a/src/routes/v1/me/drafts/get.rs b/src/routes/v1/me/drafts/get.rs index af72cad..9832516 100644 --- a/src/routes/v1/me/drafts/get.rs +++ b/src/routes/v1/me/drafts/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/drafts/recover.rs b/src/routes/v1/me/drafts/recover.rs index 3ae0bee..7f0d9b2 100644 --- a/src/routes/v1/me/drafts/recover.rs +++ b/src/routes/v1/me/drafts/recover.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/followed_blogs/delete.rs b/src/routes/v1/me/followed_blogs/delete.rs index abbaccd..9ce4e6a 100644 --- a/src/routes/v1/me/followed_blogs/delete.rs +++ b/src/routes/v1/me/followed_blogs/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/followed_blogs/post.rs b/src/routes/v1/me/followed_blogs/post.rs index 9801e65..7bd6cb2 100644 --- a/src/routes/v1/me/followed_blogs/post.rs +++ b/src/routes/v1/me/followed_blogs/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::resource_limit::ResourceLimit, error::AppError, middlewares::identity::identity::Identity, @@ -6,13 +7,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -99,11 +99,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_web::{ http::StatusCode, diff --git a/src/routes/v1/me/followed_tags/delete.rs b/src/routes/v1/me/followed_tags/delete.rs index 2afcd9b..d2d85b8 100644 --- a/src/routes/v1/me/followed_tags/delete.rs +++ b/src/routes/v1/me/followed_tags/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/followed_tags/get.rs b/src/routes/v1/me/followed_tags/get.rs index 2d92260..2781db3 100644 --- a/src/routes/v1/me/followed_tags/get.rs +++ b/src/routes/v1/me/followed_tags/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/followed_tags/post.rs b/src/routes/v1/me/followed_tags/post.rs index 95322fd..0a55fef 100644 --- a/src/routes/v1/me/followed_tags/post.rs +++ b/src/routes/v1/me/followed_tags/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::resource_limit::ResourceLimit, error::AppError, middlewares::identity::identity::Identity, @@ -6,13 +7,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -99,11 +99,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_web::{ http::StatusCode, diff --git a/src/routes/v1/me/followers/delete.rs b/src/routes/v1/me/followers/delete.rs index fe6532c..402e8ba 100644 --- a/src/routes/v1/me/followers/delete.rs +++ b/src/routes/v1/me/followers/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/followers/get.rs b/src/routes/v1/me/followers/get.rs index 9d5ba61..7100f8c 100644 --- a/src/routes/v1/me/followers/get.rs +++ b/src/routes/v1/me/followers/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/following/delete.rs b/src/routes/v1/me/following/delete.rs index a606043..62ea8d4 100644 --- a/src/routes/v1/me/following/delete.rs +++ b/src/routes/v1/me/following/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/following/get.rs b/src/routes/v1/me/following/get.rs index 50d85d3..07086aa 100644 --- a/src/routes/v1/me/following/get.rs +++ b/src/routes/v1/me/following/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/following/post.rs b/src/routes/v1/me/following/post.rs index e64c97d..ed0a57f 100644 --- a/src/routes/v1/me/following/post.rs +++ b/src/routes/v1/me/following/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ notification_entity_type::NotificationEntityType, resource_limit::ResourceLimit, @@ -10,13 +11,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -140,11 +140,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_web::{ http::StatusCode, diff --git a/src/routes/v1/me/friend_requests/cancel.rs b/src/routes/v1/me/friend_requests/cancel.rs index 027486b..25bebef 100644 --- a/src/routes/v1/me/friend_requests/cancel.rs +++ b/src/routes/v1/me/friend_requests/cancel.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/friend_requests/delete.rs b/src/routes/v1/me/friend_requests/delete.rs index 79de318..1e7f44e 100644 --- a/src/routes/v1/me/friend_requests/delete.rs +++ b/src/routes/v1/me/friend_requests/delete.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/friend_requests/get.rs b/src/routes/v1/me/friend_requests/get.rs index f3e2cab..b61cdac 100644 --- a/src/routes/v1/me/friend_requests/get.rs +++ b/src/routes/v1/me/friend_requests/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -16,10 +16,10 @@ use serde::{ Serialize, }; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/friend_requests/post.rs b/src/routes/v1/me/friend_requests/post.rs index cce4968..477be74 100644 --- a/src/routes/v1/me/friend_requests/post.rs +++ b/src/routes/v1/me/friend_requests/post.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, constants::notification_entity_type::NotificationEntityType, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/friends/delete.rs b/src/routes/v1/me/friends/delete.rs index c0b2065..f36b347 100644 --- a/src/routes/v1/me/friends/delete.rs +++ b/src/routes/v1/me/friends/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/friends/get.rs b/src/routes/v1/me/friends/get.rs index 9ff097e..7596978 100644 --- a/src/routes/v1/me/friends/get.rs +++ b/src/routes/v1/me/friends/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/friends/post.rs b/src/routes/v1/me/friends/post.rs index d086b1c..217bc9c 100644 --- a/src/routes/v1/me/friends/post.rs +++ b/src/routes/v1/me/friends/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ notification_entity_type::NotificationEntityType, resource_limit::ResourceLimit, @@ -13,13 +14,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -171,11 +171,11 @@ mod tests { use crate::{ grpc::defs::privacy_settings_def::v1::IncomingFriendRequest, test_utils::{ + RedisTestContext, assert_toast_error_response, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }, }; use actix_web::{ diff --git a/src/routes/v1/me/get.rs b/src/routes/v1/me/get.rs index a84571e..c921ca5 100644 --- a/src/routes/v1/me/get.rs +++ b/src/routes/v1/me/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use serde::{ Deserialize, @@ -14,9 +14,9 @@ use serde::{ }; use sqlx::{ - postgres::PgRow, FromRow, Row, + postgres::PgRow, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/history/history.rs b/src/routes/v1/me/history/history.rs index ffe76a2..32266b3 100644 --- a/src/routes/v1/me/history/history.rs +++ b/src/routes/v1/me/history/history.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -15,8 +15,8 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/leave_blog/leave_blog.rs b/src/routes/v1/me/leave_blog/leave_blog.rs index 3133034..ffbaafe 100644 --- a/src/routes/v1/me/leave_blog/leave_blog.rs +++ b/src/routes/v1/me/leave_blog/leave_blog.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use sqlx::Row; diff --git a/src/routes/v1/me/leave_story/leave_story.rs b/src/routes/v1/me/leave_story/leave_story.rs index 4d134bc..5b85ade 100644 --- a/src/routes/v1/me/leave_story/leave_story.rs +++ b/src/routes/v1/me/leave_story/leave_story.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/liked_comments/delete.rs b/src/routes/v1/me/liked_comments/delete.rs index bcf5e25..1e5a3bc 100644 --- a/src/routes/v1/me/liked_comments/delete.rs +++ b/src/routes/v1/me/liked_comments/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/liked_comments/post.rs b/src/routes/v1/me/liked_comments/post.rs index a074305..e4ed1bb 100644 --- a/src/routes/v1/me/liked_comments/post.rs +++ b/src/routes/v1/me/liked_comments/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ resource_limit::ResourceLimit, sql_states::SqlState, @@ -9,13 +10,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -109,11 +109,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_http::StatusCode; use actix_web::test; diff --git a/src/routes/v1/me/liked_replies/delete.rs b/src/routes/v1/me/liked_replies/delete.rs index 31538de..8d12475 100644 --- a/src/routes/v1/me/liked_replies/delete.rs +++ b/src/routes/v1/me/liked_replies/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/liked_replies/post.rs b/src/routes/v1/me/liked_replies/post.rs index bcfba30..6765b00 100644 --- a/src/routes/v1/me/liked_replies/post.rs +++ b/src/routes/v1/me/liked_replies/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ resource_limit::ResourceLimit, sql_states::SqlState, @@ -9,13 +10,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -109,11 +109,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_http::StatusCode; use actix_web::test; diff --git a/src/routes/v1/me/liked_stories/delete.rs b/src/routes/v1/me/liked_stories/delete.rs index 9ecbd14..5a16908 100644 --- a/src/routes/v1/me/liked_stories/delete.rs +++ b/src/routes/v1/me/liked_stories/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/liked_stories/get.rs b/src/routes/v1/me/liked_stories/get.rs index eb93403..f5bebed 100644 --- a/src/routes/v1/me/liked_stories/get.rs +++ b/src/routes/v1/me/liked_stories/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -17,8 +17,8 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/liked_stories/post.rs b/src/routes/v1/me/liked_stories/post.rs index 9d2a81e..d762e04 100644 --- a/src/routes/v1/me/liked_stories/post.rs +++ b/src/routes/v1/me/liked_stories/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ notification_entity_type::NotificationEntityType, resource_limit::ResourceLimit, @@ -10,13 +11,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -137,11 +137,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_http::StatusCode; use actix_web::test; diff --git a/src/routes/v1/me/lookup/username/username.rs b/src/routes/v1/me/lookup/username/username.rs index 9b86b6c..8c022b9 100644 --- a/src/routes/v1/me/lookup/username/username.rs +++ b/src/routes/v1/me/lookup/username/username.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ diff --git a/src/routes/v1/me/muted_users/delete.rs b/src/routes/v1/me/muted_users/delete.rs index bd75b55..006babe 100644 --- a/src/routes/v1/me/muted_users/delete.rs +++ b/src/routes/v1/me/muted_users/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/muted_users/get.rs b/src/routes/v1/me/muted_users/get.rs index 9fe735c..d6cfcb1 100644 --- a/src/routes/v1/me/muted_users/get.rs +++ b/src/routes/v1/me/muted_users/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ diff --git a/src/routes/v1/me/muted_users/post.rs b/src/routes/v1/me/muted_users/post.rs index a0d5f3f..f6dcbd9 100644 --- a/src/routes/v1/me/muted_users/post.rs +++ b/src/routes/v1/me/muted_users/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ resource_limit::ResourceLimit, sql_states::SqlState, @@ -9,13 +10,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -116,11 +116,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_web::{ http::StatusCode, diff --git a/src/routes/v1/me/newsletters/delete.rs b/src/routes/v1/me/newsletters/delete.rs index d4f9414..d97ae84 100644 --- a/src/routes/v1/me/newsletters/delete.rs +++ b/src/routes/v1/me/newsletters/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/newsletters/post.rs b/src/routes/v1/me/newsletters/post.rs index aea7c3f..b3ac52a 100644 --- a/src/routes/v1/me/newsletters/post.rs +++ b/src/routes/v1/me/newsletters/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::resource_limit::ResourceLimit, error::AppError, middlewares::identity::identity::Identity, @@ -6,13 +7,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; @@ -104,11 +104,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_http::StatusCode; use actix_web::test; diff --git a/src/routes/v1/me/notifications/get.rs b/src/routes/v1/me/notifications/get.rs index 4a23f22..218f583 100644 --- a/src/routes/v1/me/notifications/get.rs +++ b/src/routes/v1/me/notifications/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -17,10 +17,10 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/me/notifications/read.rs b/src/routes/v1/me/notifications/read.rs index de741bf..fa4e2c1 100644 --- a/src/routes/v1/me/notifications/read.rs +++ b/src/routes/v1/me/notifications/read.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/notifications/read_all.rs b/src/routes/v1/me/notifications/read_all.rs index 74a5b9c..c0ff2ca 100644 --- a/src/routes/v1/me/notifications/read_all.rs +++ b/src/routes/v1/me/notifications/read_all.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; #[post("/v1/me/notifications/read-all")] diff --git a/src/routes/v1/me/replies/delete.rs b/src/routes/v1/me/replies/delete.rs index 2eae505..b28835e 100644 --- a/src/routes/v1/me/replies/delete.rs +++ b/src/routes/v1/me/replies/delete.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/replies/get.rs b/src/routes/v1/me/replies/get.rs index 2a39b41..310c8f3 100644 --- a/src/routes/v1/me/replies/get.rs +++ b/src/routes/v1/me/replies/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -17,10 +17,10 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, + types::Json, }; use time::OffsetDateTime; use validator::Validate; diff --git a/src/routes/v1/me/replies/patch.rs b/src/routes/v1/me/replies/patch.rs index d078f2c..cfa534b 100644 --- a/src/routes/v1/me/replies/patch.rs +++ b/src/routes/v1/me/replies/patch.rs @@ -1,19 +1,19 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, utils::md_to_html::{ - md_to_html, MarkdownSource, + md_to_html, }, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/replies/post.rs b/src/routes/v1/me/replies/post.rs index 135d47a..fa26096 100644 --- a/src/routes/v1/me/replies/post.rs +++ b/src/routes/v1/me/replies/post.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ notification_entity_type::NotificationEntityType, resource_limit::ResourceLimit, @@ -13,17 +14,16 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, md_to_html::{ - md_to_html, MarkdownSource, + md_to_html, }, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ @@ -172,11 +172,11 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_toast_error_response, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }; use actix_web::{ http::StatusCode, diff --git a/src/routes/v1/me/settings/accounts/add/google/callback.rs b/src/routes/v1/me/settings/accounts/add/google/callback.rs index 7cac9ee..ffbdf75 100644 --- a/src/routes/v1/me/settings/accounts/add/google/callback.rs +++ b/src/routes/v1/me/settings/accounts/add/google/callback.rs @@ -1,4 +1,6 @@ use crate::{ + AddAccountTemplate, + AppState, constants::account_activity_type::AccountActivityType, error::{ AddAccountError, @@ -6,18 +8,16 @@ use crate::{ }, middlewares::identity::identity::Identity, oauth::{ - icons::google::GOOGLE_LOGO, GoogleOAuthResponse, + icons::google::GOOGLE_LOGO, }, routes::oauth::AuthRequest, - AddAccountTemplate, - AppState, }; use actix_web::{ + HttpResponse, get, http::header::ContentType, web, - HttpResponse, }; use actix_web_validator::QsQuery; use http::header; @@ -235,8 +235,8 @@ mod tests { init_app_for_test, }; use actix_web::{ - test, Responder, + test, }; use sqlx::PgPool; diff --git a/src/routes/v1/me/settings/accounts/add/google/google.rs b/src/routes/v1/me/settings/accounts/add/google/google.rs index ab830aa..2beaa0a 100644 --- a/src/routes/v1/me/settings/accounts/add/google/google.rs +++ b/src/routes/v1/me/settings/accounts/add/google/google.rs @@ -1,17 +1,17 @@ use crate::{ + AppState, error::{ AppError, FormErrorResponse, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ @@ -140,11 +140,11 @@ mod tests { }; use actix_web::test; use argon2::{ + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - PasswordHasher, }; use sqlx::PgPool; diff --git a/src/routes/v1/me/settings/accounts/remove.rs b/src/routes/v1/me/settings/accounts/remove.rs index 54ad4a8..c9f5569 100644 --- a/src/routes/v1/me/settings/accounts/remove.rs +++ b/src/routes/v1/me/settings/accounts/remove.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::account_activity_type::AccountActivityType, error::{ AppError, @@ -6,13 +7,12 @@ use crate::{ ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ @@ -146,11 +146,11 @@ mod tests { }; use actix_web::test; use argon2::{ + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - PasswordHasher, }; use sqlx::{ PgPool, diff --git a/src/routes/v1/me/settings/avatar.rs b/src/routes/v1/me/settings/avatar.rs index 363f76f..e72277a 100644 --- a/src/routes/v1/me/settings/avatar.rs +++ b/src/routes/v1/me/settings/avatar.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/banner.rs b/src/routes/v1/me/settings/banner.rs index 4bf2117..a7859ae 100644 --- a/src/routes/v1/me/settings/banner.rs +++ b/src/routes/v1/me/settings/banner.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/connections/remove.rs b/src/routes/v1/me/settings/connections/remove.rs index 40ed94f..f5ed54d 100644 --- a/src/routes/v1/me/settings/connections/remove.rs +++ b/src/routes/v1/me/settings/connections/remove.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/settings/connections/visibility.rs b/src/routes/v1/me/settings/connections/visibility.rs index 82ac949..cf6537e 100644 --- a/src/routes/v1/me/settings/connections/visibility.rs +++ b/src/routes/v1/me/settings/connections/visibility.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/email.rs b/src/routes/v1/me/settings/email.rs index c97a090..6c9d2b9 100644 --- a/src/routes/v1/me/settings/email.rs +++ b/src/routes/v1/me/settings/email.rs @@ -1,7 +1,8 @@ use crate::{ + AppState, amqp::consumers::templated_email::{ - TemplatedEmailMessage, TEMPLATED_EMAIL_QUEUE_NAME, + TemplatedEmailMessage, }, constants::{ account_activity_type::AccountActivityType, @@ -19,13 +20,12 @@ use crate::{ clear_user_sessions::clear_user_sessions, generate_hashed_token::generate_hashed_token, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ @@ -38,8 +38,8 @@ use chrono::{ Local, }; use deadpool_lapin::lapin::{ - options::BasicPublishOptions, BasicProperties, + options::BasicPublishOptions, }; use serde::{ Deserialize, @@ -229,11 +229,11 @@ mod tests { }; use actix_web::test; use argon2::{ + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - PasswordHasher, }; use sqlx::{ PgPool, diff --git a/src/routes/v1/me/settings/mfa/generate_codes.rs b/src/routes/v1/me/settings/mfa/generate_codes.rs index f3cb854..0570640 100644 --- a/src/routes/v1/me/settings/mfa/generate_codes.rs +++ b/src/routes/v1/me/settings/mfa/generate_codes.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, utils::generate_recovery_codes::generate_recovery_codes, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::{ Deserialize, diff --git a/src/routes/v1/me/settings/mfa/recovery_codes.rs b/src/routes/v1/me/settings/mfa/recovery_codes.rs index 20225ea..452266e 100644 --- a/src/routes/v1/me/settings/mfa/recovery_codes.rs +++ b/src/routes/v1/me/settings/mfa/recovery_codes.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use serde::{ Deserialize, diff --git a/src/routes/v1/me/settings/mfa/remove.rs b/src/routes/v1/me/settings/mfa/remove.rs index 862214c..aea1053 100644 --- a/src/routes/v1/me/settings/mfa/remove.rs +++ b/src/routes/v1/me/settings/mfa/remove.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, error::{ AppError, FormErrorResponse, @@ -6,12 +7,11 @@ use crate::{ }, middlewares::identity::identity::Identity, utils::generate_totp::generate_totp, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/mfa/request.rs b/src/routes/v1/me/settings/mfa/request.rs index 8bccbf8..16a9406 100644 --- a/src/routes/v1/me/settings/mfa/request.rs +++ b/src/routes/v1/me/settings/mfa/request.rs @@ -1,17 +1,17 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, utils::generate_totp::generate_totp, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use serde::{ Deserialize, diff --git a/src/routes/v1/me/settings/mfa/verify.rs b/src/routes/v1/me/settings/mfa/verify.rs index d6b9c0c..65b1616 100644 --- a/src/routes/v1/me/settings/mfa/verify.rs +++ b/src/routes/v1/me/settings/mfa/verify.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, error::{ AppError, FormErrorResponse, @@ -9,13 +10,12 @@ use crate::{ generate_recovery_codes::generate_recovery_codes, generate_totp::generate_totp, }, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/notifications/mail.rs b/src/routes/v1/me/settings/notifications/mail.rs index 628db5b..2f59278 100644 --- a/src/routes/v1/me/settings/notifications/mail.rs +++ b/src/routes/v1/me/settings/notifications/mail.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/notifications/site.rs b/src/routes/v1/me/settings/notifications/site.rs index f58c95e..8a765ce 100644 --- a/src/routes/v1/me/settings/notifications/site.rs +++ b/src/routes/v1/me/settings/notifications/site.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/notifications/unsubscribe.rs b/src/routes/v1/me/settings/notifications/unsubscribe.rs index 013ff55..5d87e2c 100644 --- a/src/routes/v1/me/settings/notifications/unsubscribe.rs +++ b/src/routes/v1/me/settings/notifications/unsubscribe.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, constants::notification_entity_type::NotificationEntityType, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/password/add.rs b/src/routes/v1/me/settings/password/add.rs index 3ca20b6..7632a03 100644 --- a/src/routes/v1/me/settings/password/add.rs +++ b/src/routes/v1/me/settings/password/add.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::account_activity_type::AccountActivityType, error::{ AppError, @@ -7,23 +8,22 @@ use crate::{ grpc::defs::token_def::v1::TokenType, middlewares::identity::identity::Identity, utils::clear_user_sessions::clear_user_sessions, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ - password_hash::{ - rand_core::OsRng, - SaltString, - }, Argon2, PasswordHash, PasswordHasher, PasswordVerifier, + password_hash::{ + SaltString, + rand_core::OsRng, + }, }; use serde::{ Deserialize, @@ -170,11 +170,11 @@ mod tests { }; use actix_web::test; use argon2::{ + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - PasswordHasher, }; use nanoid::nanoid; use sqlx::{ diff --git a/src/routes/v1/me/settings/password/request_verification.rs b/src/routes/v1/me/settings/password/request_verification.rs index 95a4d9d..bbc5e42 100644 --- a/src/routes/v1/me/settings/password/request_verification.rs +++ b/src/routes/v1/me/settings/password/request_verification.rs @@ -1,7 +1,8 @@ use crate::{ + AppState, amqp::consumers::templated_email::{ - TemplatedEmailMessage, TEMPLATED_EMAIL_QUEUE_NAME, + TemplatedEmailMessage, }, constants::email_template::EmailTemplate, error::{ @@ -11,25 +12,24 @@ use crate::{ grpc::defs::token_def::v1::TokenType, middlewares::identity::identity::Identity, models::email_templates::password_add_verification::PasswordAddVerificationEmailTemplateData, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use argon2::{ - password_hash::SaltString, Argon2, PasswordHasher, + password_hash::SaltString, }; use chrono::{ Datelike, Local, }; use deadpool_lapin::lapin::{ - options::BasicPublishOptions, BasicProperties, + options::BasicPublishOptions, }; use nanoid::nanoid; use sqlx::Row; diff --git a/src/routes/v1/me/settings/password/update.rs b/src/routes/v1/me/settings/password/update.rs index b3886c1..4014443 100644 --- a/src/routes/v1/me/settings/password/update.rs +++ b/src/routes/v1/me/settings/password/update.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::account_activity_type::AccountActivityType, error::{ AppError, @@ -6,24 +7,23 @@ use crate::{ }, middlewares::identity::identity::Identity, utils::clear_user_sessions::clear_user_sessions, - AppState, }; use actix_web::{ + HttpResponse, http::StatusCode, patch, web, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ - password_hash::{ - rand_core::OsRng, - SaltString, - }, Argon2, PasswordHash, PasswordHasher, PasswordVerifier, + password_hash::{ + SaltString, + rand_core::OsRng, + }, }; use serde::{ Deserialize, @@ -136,11 +136,11 @@ mod tests { }; use actix_web::test; use argon2::{ + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - PasswordHasher, }; use sqlx::{ PgPool, diff --git a/src/routes/v1/me/settings/privacy/delete_account.rs b/src/routes/v1/me/settings/privacy/delete_account.rs index 14f9e81..1bbefde 100644 --- a/src/routes/v1/me/settings/privacy/delete_account.rs +++ b/src/routes/v1/me/settings/privacy/delete_account.rs @@ -1,17 +1,17 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, utils::clear_user_sessions::clear_user_sessions, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ @@ -122,11 +122,11 @@ mod tests { }; use actix_web::test; use argon2::{ + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - PasswordHasher, }; use sqlx::{ PgPool, diff --git a/src/routes/v1/me/settings/privacy/disable_account.rs b/src/routes/v1/me/settings/privacy/disable_account.rs index a1b1cee..65fbfbb 100644 --- a/src/routes/v1/me/settings/privacy/disable_account.rs +++ b/src/routes/v1/me/settings/privacy/disable_account.rs @@ -1,17 +1,17 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, utils::clear_user_sessions::clear_user_sessions, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ @@ -122,11 +122,11 @@ mod tests { }; use actix_web::test; use argon2::{ + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - PasswordHasher, }; use sqlx::{ PgPool, diff --git a/src/routes/v1/me/settings/privacy/following_list.rs b/src/routes/v1/me/settings/privacy/following_list.rs index 2608f7e..0c74e56 100644 --- a/src/routes/v1/me/settings/privacy/following_list.rs +++ b/src/routes/v1/me/settings/privacy/following_list.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, grpc::defs::privacy_settings_def::v1::RelationVisibility, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/settings/privacy/friend_list.rs b/src/routes/v1/me/settings/privacy/friend_list.rs index 9f1dd83..d205344 100644 --- a/src/routes/v1/me/settings/privacy/friend_list.rs +++ b/src/routes/v1/me/settings/privacy/friend_list.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, grpc::defs::privacy_settings_def::v1::RelationVisibility, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/settings/privacy/incoming_blog_requests.rs b/src/routes/v1/me/settings/privacy/incoming_blog_requests.rs index 1a6ec9c..2030708 100644 --- a/src/routes/v1/me/settings/privacy/incoming_blog_requests.rs +++ b/src/routes/v1/me/settings/privacy/incoming_blog_requests.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, grpc::defs::privacy_settings_def::v1::IncomingBlogRequest, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/settings/privacy/incoming_collaboration_requests.rs b/src/routes/v1/me/settings/privacy/incoming_collaboration_requests.rs index 223735c..6cb5a40 100644 --- a/src/routes/v1/me/settings/privacy/incoming_collaboration_requests.rs +++ b/src/routes/v1/me/settings/privacy/incoming_collaboration_requests.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, grpc::defs::privacy_settings_def::v1::IncomingCollaborationRequest, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/settings/privacy/incoming_friend_requests.rs b/src/routes/v1/me/settings/privacy/incoming_friend_requests.rs index 5a7f06f..ee03995 100644 --- a/src/routes/v1/me/settings/privacy/incoming_friend_requests.rs +++ b/src/routes/v1/me/settings/privacy/incoming_friend_requests.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, grpc::defs::privacy_settings_def::v1::IncomingFriendRequest, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/settings/privacy/private_account.rs b/src/routes/v1/me/settings/privacy/private_account.rs index 5272b9d..046ad5a 100644 --- a/src/routes/v1/me/settings/privacy/private_account.rs +++ b/src/routes/v1/me/settings/privacy/private_account.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, constants::account_activity_type::AccountActivityType, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/privacy/read_history.rs b/src/routes/v1/me/settings/privacy/read_history.rs index 16bed12..29bcceb 100644 --- a/src/routes/v1/me/settings/privacy/read_history.rs +++ b/src/routes/v1/me/settings/privacy/read_history.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/privacy/sensitive_content.rs b/src/routes/v1/me/settings/privacy/sensitive_content.rs index 4d028e3..d7b53a1 100644 --- a/src/routes/v1/me/settings/privacy/sensitive_content.rs +++ b/src/routes/v1/me/settings/privacy/sensitive_content.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/profile.rs b/src/routes/v1/me/settings/profile.rs index 493110a..d9324f5 100644 --- a/src/routes/v1/me/settings/profile.rs +++ b/src/routes/v1/me/settings/profile.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, utils::md_to_html::{ - md_to_html, MarkdownSource, + md_to_html, }, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/settings/sessions/acknowledge.rs b/src/routes/v1/me/settings/sessions/acknowledge.rs index 37b1cac..9230601 100644 --- a/src/routes/v1/me/settings/sessions/acknowledge.rs +++ b/src/routes/v1/me/settings/sessions/acknowledge.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::redis_namespaces::RedisNamespace, error::{ AppError, @@ -6,12 +7,11 @@ use crate::{ }, middlewares::identity::identity::Identity, utils::get_user_sessions::UserSession, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use redis::AsyncCommands; @@ -42,11 +42,7 @@ async fn post( let user_id = user.id()?; let mut redis_conn = data.redis.get().await?; - let cache_key = format!( - "{}:{user_id}:{}", - RedisNamespace::Session, - &payload.id - ); + let cache_key = format!("{}:{user_id}:{}", RedisNamespace::Session, &payload.id); let result = redis_conn .get::<_, Option>>(&cache_key) @@ -92,9 +88,9 @@ mod tests { use super::*; use crate::{ test_utils::{ + RedisTestContext, assert_toast_error_response, init_app_for_test, - RedisTestContext, }, utils::get_user_sessions::get_user_sessions, }; diff --git a/src/routes/v1/me/settings/sessions/destroy.rs b/src/routes/v1/me/settings/sessions/destroy.rs index be6e398..5500d93 100644 --- a/src/routes/v1/me/settings/sessions/destroy.rs +++ b/src/routes/v1/me/settings/sessions/destroy.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, utils::clear_user_sessions::clear_user_sessions, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; #[post("/v1/me/settings/sessions/destroy")] @@ -36,12 +36,12 @@ mod tests { use crate::{ constants::redis_namespaces::RedisNamespace, test_utils::{ - init_app_for_test, RedisTestContext, + init_app_for_test, }, utils::get_user_sessions::{ - get_user_sessions, UserSession, + get_user_sessions, }, }; use actix_web::test; diff --git a/src/routes/v1/me/settings/sessions/logout.rs b/src/routes/v1/me/settings/sessions/logout.rs index 43b264d..db208d4 100644 --- a/src/routes/v1/me/settings/sessions/logout.rs +++ b/src/routes/v1/me/settings/sessions/logout.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, constants::redis_namespaces::RedisNamespace, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use redis::AsyncCommands; @@ -38,11 +38,7 @@ async fn post( let user_id = user.id()?; let mut redis_conn = data.redis.get().await?; - let cache_key = format!( - "{}:{user_id}:{}", - RedisNamespace::Session, - &payload.id - ); + let cache_key = format!("{}:{user_id}:{}", RedisNamespace::Session, &payload.id); redis_conn.del::<_, ()>(cache_key).await.map_err(|error| { AppError::InternalError(format!("unable to delete the session: {error:?}")) @@ -60,12 +56,12 @@ mod tests { use super::*; use crate::{ test_utils::{ - init_app_for_test, RedisTestContext, + init_app_for_test, }, utils::get_user_sessions::{ - get_user_sessions, UserSession, + get_user_sessions, }, }; use actix_web::test; diff --git a/src/routes/v1/me/settings/username.rs b/src/routes/v1/me/settings/username.rs index 17d4c80..3f5ca94 100644 --- a/src/routes/v1/me/settings/username.rs +++ b/src/routes/v1/me/settings/username.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ account_activity_type::AccountActivityType, reserved_keywords::RESERVED_KEYWORDS, @@ -11,13 +12,12 @@ use crate::{ ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use argon2::{ @@ -180,11 +180,11 @@ mod tests { }; use actix_web::test; use argon2::{ + PasswordHasher, password_hash::{ - rand_core::OsRng, SaltString, + rand_core::OsRng, }, - PasswordHasher, }; use sqlx::{ PgPool, diff --git a/src/routes/v1/me/stats/account/account.rs b/src/routes/v1/me/stats/account/account.rs index 44bae84..a74c5ed 100644 --- a/src/routes/v1/me/stats/account/account.rs +++ b/src/routes/v1/me/stats/account/account.rs @@ -1,17 +1,17 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_http::header; use actix_web::{ + HttpResponse, get, http::header::{ CacheControl, CacheDirective, }, web, - HttpResponse, }; use serde::{ Deserialize, diff --git a/src/routes/v1/me/stats/stories/stories.rs b/src/routes/v1/me/stats/stories/stories.rs index 15cfea5..1a99572 100644 --- a/src/routes/v1/me/stats/stories/stories.rs +++ b/src/routes/v1/me/stats/stories/stories.rs @@ -1,17 +1,17 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_http::header; use actix_web::{ + HttpResponse, get, http::header::{ CacheControl, CacheDirective, }, web, - HttpResponse, }; use serde::{ Deserialize, diff --git a/src/routes/v1/me/status/delete.rs b/src/routes/v1/me/status/delete.rs index 1a75afa..55b43bd 100644 --- a/src/routes/v1/me/status/delete.rs +++ b/src/routes/v1/me/status/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; #[delete("/v1/me/status")] diff --git a/src/routes/v1/me/status/post.rs b/src/routes/v1/me/status/post.rs index fe7838d..a687c76 100644 --- a/src/routes/v1/me/status/post.rs +++ b/src/routes/v1/me/status/post.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, error::{ AppError, FormErrorResponse, }, grpc::defs::user_def::v1::StatusDuration, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/stories/contributors/get.rs b/src/routes/v1/me/stories/contributors/get.rs index 2e26cb7..120b93b 100644 --- a/src/routes/v1/me/stories/contributors/get.rs +++ b/src/routes/v1/me/stories/contributors/get.rs @@ -1,20 +1,20 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use serde::{ Deserialize, Serialize, }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use uuid::Uuid; use validator::Validate; diff --git a/src/routes/v1/me/stories/contributors/invite.rs b/src/routes/v1/me/stories/contributors/invite.rs index a642b1e..5ff5fe1 100644 --- a/src/routes/v1/me/stories/contributors/invite.rs +++ b/src/routes/v1/me/stories/contributors/invite.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ notification_entity_type::NotificationEntityType, resource_limit::ResourceLimit, @@ -15,13 +16,12 @@ use crate::{ check_resource_limit::check_resource_limit, incr_resource_limit::incr_resource_limit, }, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use lazy_static::lazy_static; @@ -246,12 +246,12 @@ mod tests { use crate::{ grpc::defs::privacy_settings_def::v1::IncomingCollaborationRequest, test_utils::{ + RedisTestContext, assert_response_body_text, assert_toast_error_response, exceed_resource_limit, get_resource_limit, init_app_for_test, - RedisTestContext, }, }; use actix_http::StatusCode; diff --git a/src/routes/v1/me/stories/contributors/remove.rs b/src/routes/v1/me/stories/contributors/remove.rs index ca8b7c3..b5355b9 100644 --- a/src/routes/v1/me/stories/contributors/remove.rs +++ b/src/routes/v1/me/stories/contributors/remove.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, realms::realm::RealmData, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use lockable::AsyncLimit; use serde::Deserialize; diff --git a/src/routes/v1/me/stories/contributors/update.rs b/src/routes/v1/me/stories/contributors/update.rs index 47156b3..96619e7 100644 --- a/src/routes/v1/me/stories/contributors/update.rs +++ b/src/routes/v1/me/stories/contributors/update.rs @@ -1,16 +1,16 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, realms::realm::{ PeerRole, RealmData, }, - AppState, }; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/stories/delete.rs b/src/routes/v1/me/stories/delete.rs index a242110..54503ba 100644 --- a/src/routes/v1/me/stories/delete.rs +++ b/src/routes/v1/me/stories/delete.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, @@ -8,12 +9,11 @@ use crate::{ RealmData, RealmDestroyReason, }, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use lockable::AsyncLimit; use serde::Deserialize; diff --git a/src/routes/v1/me/stories/get.rs b/src/routes/v1/me/stories/get.rs index dc03bbb..c7ab18f 100644 --- a/src/routes/v1/me/stories/get.rs +++ b/src/routes/v1/me/stories/get.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; diff --git a/src/routes/v1/me/stories/metadata/metadata.rs b/src/routes/v1/me/stories/metadata/metadata.rs index 2545a51..57b7521 100644 --- a/src/routes/v1/me/stories/metadata/metadata.rs +++ b/src/routes/v1/me/stories/metadata/metadata.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ sql_states::SqlState, story_category::STORY_CATEGORY_VEC, @@ -10,13 +11,12 @@ use crate::{ ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, patch, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/me/stories/publish.rs b/src/routes/v1/me/stories/publish.rs index 68eb64e..4a95b70 100644 --- a/src/routes/v1/me/stories/publish.rs +++ b/src/routes/v1/me/stories/publish.rs @@ -1,8 +1,9 @@ use crate::{ + AppState, amqp::consumers::notify_story_add::{ + NOTIFY_STORY_ADD_QUEUE_NAME, NotifyStoryAddMessage, StoryAddSource, - NOTIFY_STORY_ADD_QUEUE_NAME, }, error::{ AppError, @@ -15,18 +16,17 @@ use crate::{ RealmDestroyReason, }, utils::generate_story_slug::generate_story_slug, - AppState, }; use actix_web::{ + HttpResponse, post, put, web, - HttpResponse, }; use actix_web_validator::Json; use deadpool_lapin::lapin::{ - options::BasicPublishOptions, BasicProperties, + options::BasicPublishOptions, }; use futures::future; use lockable::AsyncLimit; @@ -317,9 +317,9 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_toast_error_response, init_app_for_test, - RedisTestContext, }; use actix_web::{ services, diff --git a/src/routes/v1/me/stories/recover.rs b/src/routes/v1/me/stories/recover.rs index 5a12602..02652ed 100644 --- a/src/routes/v1/me/stories/recover.rs +++ b/src/routes/v1/me/stories/recover.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/stories/stats/stats.rs b/src/routes/v1/me/stories/stats/stats.rs index 1855b15..d98b260 100644 --- a/src/routes/v1/me/stories/stats/stats.rs +++ b/src/routes/v1/me/stories/stats/stats.rs @@ -1,17 +1,17 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, get, http::header::{ CacheControl, CacheDirective, }, web, - HttpResponse, }; use serde::{ Deserialize, @@ -408,8 +408,8 @@ mod tests { }; use actix_web::test; use chrono::{ - prelude::*, Duration, + prelude::*, }; use sqlx::{ PgPool, diff --git a/src/routes/v1/me/stories/unpublish.rs b/src/routes/v1/me/stories/unpublish.rs index e38e362..e63c8b9 100644 --- a/src/routes/v1/me/stories/unpublish.rs +++ b/src/routes/v1/me/stories/unpublish.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, @@ -8,12 +9,11 @@ use crate::{ RealmData, RealmDestroyReason, }, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use lockable::AsyncLimit; use serde::Deserialize; diff --git a/src/routes/v1/me/subscriptions/delete.rs b/src/routes/v1/me/subscriptions/delete.rs index ca78444..e3fff1f 100644 --- a/src/routes/v1/me/subscriptions/delete.rs +++ b/src/routes/v1/me/subscriptions/delete.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, delete, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/subscriptions/post.rs b/src/routes/v1/me/subscriptions/post.rs index b864503..5aa7371 100644 --- a/src/routes/v1/me/subscriptions/post.rs +++ b/src/routes/v1/me/subscriptions/post.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use serde::Deserialize; use validator::Validate; diff --git a/src/routes/v1/me/unread_notifications/unread_notifications.rs b/src/routes/v1/me/unread_notifications/unread_notifications.rs index 0793731..2da28b0 100644 --- a/src/routes/v1/me/unread_notifications/unread_notifications.rs +++ b/src/routes/v1/me/unread_notifications/unread_notifications.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use sqlx::Row; diff --git a/src/routes/v1/newsletters/unsubscribe/unsubscribe.rs b/src/routes/v1/newsletters/unsubscribe/unsubscribe.rs index 5646f94..f856dbd 100644 --- a/src/routes/v1/newsletters/unsubscribe/unsubscribe.rs +++ b/src/routes/v1/newsletters/unsubscribe/unsubscribe.rs @@ -1,16 +1,16 @@ use crate::{ - error::AppError, - utils::decode_unsubscribe_fragment::decode_unsubscribe_fragment, AppState, UnsubscribeTemplate, + error::AppError, + utils::decode_unsubscribe_fragment::decode_unsubscribe_fragment, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, get, http::header::ContentType, post, web, - HttpResponse, }; use sailfish::TemplateOnce; use serde::Deserialize; diff --git a/src/routes/v1/public/blogs/stories/recommendations/recommendations.rs b/src/routes/v1/public/blogs/stories/recommendations/recommendations.rs index 15167be..b775624 100644 --- a/src/routes/v1/public/blogs/stories/recommendations/recommendations.rs +++ b/src/routes/v1/public/blogs/stories/recommendations/recommendations.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -15,8 +15,8 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/public/cards/user/user.rs b/src/routes/v1/public/cards/user/user.rs index f8a56ce..8310167 100644 --- a/src/routes/v1/public/cards/user/user.rs +++ b/src/routes/v1/public/cards/user/user.rs @@ -1,24 +1,24 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_http::StatusCode; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use serde::{ Deserialize, Serialize, }; use sqlx::{ - postgres::PgRow, FromRow, Postgres, QueryBuilder, Row, + postgres::PgRow, }; use uuid::Uuid; use validator::Validate; diff --git a/src/routes/v1/public/comments/replies/replies.rs b/src/routes/v1/public/comments/replies/replies.rs index 1b94f5e..cf33a6f 100644 --- a/src/routes/v1/public/comments/replies/replies.rs +++ b/src/routes/v1/public/comments/replies/replies.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -15,10 +15,10 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/public/comments/visibility/visibility.rs b/src/routes/v1/public/comments/visibility/visibility.rs index 6247dd5..5964bdd 100644 --- a/src/routes/v1/public/comments/visibility/visibility.rs +++ b/src/routes/v1/public/comments/visibility/visibility.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/public/explore/stories/stories.rs b/src/routes/v1/public/explore/stories/stories.rs index e3ec739..629c49a 100644 --- a/src/routes/v1/public/explore/stories/stories.rs +++ b/src/routes/v1/public/explore/stories/stories.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, constants::story_category::STORY_CATEGORY_VEC, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -16,8 +16,8 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/public/explore/tags/tags.rs b/src/routes/v1/public/explore/tags/tags.rs index 77391cb..5b8b38f 100644 --- a/src/routes/v1/public/explore/tags/tags.rs +++ b/src/routes/v1/public/explore/tags/tags.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, constants::story_category::STORY_CATEGORY_VEC, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ diff --git a/src/routes/v1/public/explore/writers/writers.rs b/src/routes/v1/public/explore/writers/writers.rs index 61e1051..5412e8a 100644 --- a/src/routes/v1/public/explore/writers/writers.rs +++ b/src/routes/v1/public/explore/writers/writers.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, constants::story_category::STORY_CATEGORY_VEC, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ diff --git a/src/routes/v1/public/preview/preview.rs b/src/routes/v1/public/preview/preview.rs index ec19af8..f31e166 100644 --- a/src/routes/v1/public/preview/preview.rs +++ b/src/routes/v1/public/preview/preview.rs @@ -1,11 +1,11 @@ use crate::{ - error::AppError, AppState, + error::AppError, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use serde::{ Deserialize, @@ -14,8 +14,8 @@ use serde::{ use crate::middlewares::identity::identity::Identity; use sqlx::{ - types::Json, FromRow, + types::Json, }; use uuid::Uuid; use validator::Validate; diff --git a/src/routes/v1/public/replies/visibility/visibility.rs b/src/routes/v1/public/replies/visibility/visibility.rs index 6ed888d..d6b74b0 100644 --- a/src/routes/v1/public/replies/visibility/visibility.rs +++ b/src/routes/v1/public/replies/visibility/visibility.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, error::{ AppError, ToastErrorResponse, }, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/public/stories/comments/comments.rs b/src/routes/v1/public/stories/comments/comments.rs index 631e9e9..2d46a20 100644 --- a/src/routes/v1/public/stories/comments/comments.rs +++ b/src/routes/v1/public/stories/comments/comments.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -17,10 +17,10 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, Postgres, QueryBuilder, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/public/stories/read/read.rs b/src/routes/v1/public/stories/read/read.rs index e56a92e..ff200ec 100644 --- a/src/routes/v1/public/stories/read/read.rs +++ b/src/routes/v1/public/stories/read/read.rs @@ -1,4 +1,5 @@ use crate::{ + AppState, constants::{ reading_session::MAXIMUM_READING_SESSION_DURATION, redis_namespaces::RedisNamespace, @@ -10,13 +11,12 @@ use crate::{ get_client_country::get_client_country, get_client_device::get_client_device, }, - AppState, }; use actix_web::{ - post, - web, HttpRequest, HttpResponse, + post, + web, }; use actix_web_validator::QsQuery; use redis::AsyncCommands; @@ -190,9 +190,9 @@ pub fn init_routes(cfg: &mut web::ServiceConfig) { mod tests { use super::*; use crate::test_utils::{ + RedisTestContext, assert_response_body_text, init_app_for_test, - RedisTestContext, }; use actix_web::test; use sqlx::{ diff --git a/src/routes/v1/public/stories/recommendations/recommendations.rs b/src/routes/v1/public/stories/recommendations/recommendations.rs index 287611c..9297197 100644 --- a/src/routes/v1/public/stories/recommendations/recommendations.rs +++ b/src/routes/v1/public/stories/recommendations/recommendations.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -15,8 +15,8 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/public/tags/tags.rs b/src/routes/v1/public/tags/tags.rs index 8dbff9d..4f0fd02 100644 --- a/src/routes/v1/public/tags/tags.rs +++ b/src/routes/v1/public/tags/tags.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use serde::{ @@ -14,8 +14,8 @@ use serde::{ Serialize, }; use sqlx::{ - postgres::PgRow, Row, + postgres::PgRow, }; use validator::Validate; diff --git a/src/routes/v1/public/validation/username.rs b/src/routes/v1/public/validation/username.rs index c938ef9..34c7c91 100644 --- a/src/routes/v1/public/validation/username.rs +++ b/src/routes/v1/public/validation/username.rs @@ -1,15 +1,15 @@ use crate::{ + AppState, constants::{ reserved_keywords::RESERVED_KEYWORDS, username_regex::USERNAME_REGEX, }, error::AppError, - AppState, }; use actix_web::{ + HttpResponse, post, web, - HttpResponse, }; use actix_web_validator::Json; use serde::{ diff --git a/src/routes/v1/rsb_content/rsb_content.rs b/src/routes/v1/rsb_content/rsb_content.rs index ab0c5ae..f558ad7 100644 --- a/src/routes/v1/rsb_content/rsb_content.rs +++ b/src/routes/v1/rsb_content/rsb_content.rs @@ -1,26 +1,26 @@ use super::{ stories::{ - get_rsb_content_stories, Story, + get_rsb_content_stories, }, tags::{ - get_rsb_content_tags, Tag, + get_rsb_content_tags, }, users::{ - get_rsb_content_users, User, + get_rsb_content_users, }, }; use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use serde::{ Deserialize, diff --git a/src/routes/v1/rsb_content/stories.rs b/src/routes/v1/rsb_content/stories.rs index cdb8f8c..ed6c9c7 100644 --- a/src/routes/v1/rsb_content/stories.rs +++ b/src/routes/v1/rsb_content/stories.rs @@ -4,11 +4,11 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, Pool, Postgres, QueryBuilder, + types::Json, }; use uuid::Uuid; diff --git a/src/routes/v1/tags/stories/stories.rs b/src/routes/v1/tags/stories/stories.rs index 3daeac5..7429ef6 100644 --- a/src/routes/v1/tags/stories/stories.rs +++ b/src/routes/v1/tags/stories/stories.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, constants::tag_regex::TAG_REGEX, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -18,8 +18,8 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/routes/v1/tags/writers/writers.rs b/src/routes/v1/tags/writers/writers.rs index c8df1a2..a870e91 100644 --- a/src/routes/v1/tags/writers/writers.rs +++ b/src/routes/v1/tags/writers/writers.rs @@ -1,13 +1,13 @@ use crate::{ + AppState, constants::tag_regex::TAG_REGEX, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use serde::{ Deserialize, diff --git a/src/routes/v1/users/followers/followers.rs b/src/routes/v1/users/followers/followers.rs index d8a3a76..acc5ace 100644 --- a/src/routes/v1/users/followers/followers.rs +++ b/src/routes/v1/users/followers/followers.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; diff --git a/src/routes/v1/users/following/following.rs b/src/routes/v1/users/following/following.rs index 02feb9c..8df2744 100644 --- a/src/routes/v1/users/following/following.rs +++ b/src/routes/v1/users/following/following.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; diff --git a/src/routes/v1/users/friends/friends.rs b/src/routes/v1/users/friends/friends.rs index a331a52..65c7396 100644 --- a/src/routes/v1/users/friends/friends.rs +++ b/src/routes/v1/users/friends/friends.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; diff --git a/src/routes/v1/users/stories/stories.rs b/src/routes/v1/users/stories/stories.rs index bce73f0..987cd72 100644 --- a/src/routes/v1/users/stories/stories.rs +++ b/src/routes/v1/users/stories/stories.rs @@ -1,12 +1,12 @@ use crate::{ + AppState, error::AppError, middlewares::identity::identity::Identity, - AppState, }; use actix_web::{ + HttpResponse, get, web, - HttpResponse, }; use actix_web_validator::QsQuery; use lazy_static::lazy_static; @@ -17,8 +17,8 @@ use serde::{ }; use sqlx::{ - types::Json, FromRow, + types::Json, }; use time::OffsetDateTime; use uuid::Uuid; diff --git a/src/snowflake_id.rs b/src/snowflake_id.rs index 35ba371..9cffb49 100644 --- a/src/snowflake_id.rs +++ b/src/snowflake_id.rs @@ -1,8 +1,8 @@ use serde::{ - de, Deserialize, Deserializer, Serializer, + de, }; use std::fmt; @@ -38,10 +38,10 @@ where pub mod option { use serde::{ - de, Deserialize, Deserializer, Serializer, + de, }; use std::fmt; diff --git a/src/telemetry.rs b/src/telemetry.rs index 0705707..55a491e 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -1,17 +1,17 @@ use actix_web::rt::task::JoinHandle; use tracing::{ - subscriber::set_global_default, Subscriber, + subscriber::set_global_default, }; use tracing_bunyan_formatter::{ BunyanFormattingLayer, JsonStorageLayer, }; use tracing_subscriber::{ - fmt::MakeWriter, - layer::SubscriberExt, EnvFilter, Registry, + fmt::MakeWriter, + layer::SubscriberExt, }; /// Composes multiple layers into a `tracing`'s subscriber. diff --git a/src/test_utils/get_cron_job_state_for_test.rs b/src/test_utils/get_cron_job_state_for_test.rs index 6f0f7b4..267ba71 100644 --- a/src/test_utils/get_cron_job_state_for_test.rs +++ b/src/test_utils/get_cron_job_state_for_test.rs @@ -1,8 +1,8 @@ use crate::{ + S3Client, config::get_app_config, cron::init::SharedCronJobState, test_utils::get_s3_client, - S3Client, }; use apalis::prelude::*; use sqlx::PgPool; diff --git a/src/test_utils/get_lapin_pool.rs b/src/test_utils/get_lapin_pool.rs index f3459a7..aa0ac6b 100644 --- a/src/test_utils/get_lapin_pool.rs +++ b/src/test_utils/get_lapin_pool.rs @@ -1,6 +1,6 @@ use crate::{ - config::get_app_config, LapinPool, + config::get_app_config, }; /// Initializes and returns a Lapin connection pool for tests diff --git a/src/test_utils/get_redis_pool.rs b/src/test_utils/get_redis_pool.rs index 2c049cd..08a0432 100644 --- a/src/test_utils/get_redis_pool.rs +++ b/src/test_utils/get_redis_pool.rs @@ -1,6 +1,6 @@ use crate::{ - config::get_app_config, RedisPool, + config::get_app_config, }; /// Initializes and returns a Redis connection pool for tests diff --git a/src/test_utils/get_resource_limit.rs b/src/test_utils/get_resource_limit.rs index 90292e9..c6b267b 100644 --- a/src/test_utils/get_resource_limit.rs +++ b/src/test_utils/get_resource_limit.rs @@ -1,9 +1,9 @@ use crate::{ + RedisPool, constants::{ redis_namespaces::RedisNamespace, resource_limit::ResourceLimit, }, - RedisPool, }; use redis::AsyncCommands; diff --git a/src/test_utils/get_resource_lock_attempts.rs b/src/test_utils/get_resource_lock_attempts.rs index 58df380..02f1fbd 100644 --- a/src/test_utils/get_resource_lock_attempts.rs +++ b/src/test_utils/get_resource_lock_attempts.rs @@ -1,6 +1,6 @@ use crate::{ - constants::resource_lock::ResourceLock, RedisPool, + constants::resource_lock::ResourceLock, }; use redis::AsyncCommands; diff --git a/src/test_utils/redis_test_context.rs b/src/test_utils/redis_test_context.rs index 4cc8842..b47e98f 100644 --- a/src/test_utils/redis_test_context.rs +++ b/src/test_utils/redis_test_context.rs @@ -1,9 +1,9 @@ use crate::{ + RedisPool, test_utils::{ - get_redis_pool, TestContext, + get_redis_pool, }, - RedisPool, }; /// The test context with Redis connection pool. Flushes the entire Redis database on teardown. diff --git a/src/test_utils/test_grpc_service.rs b/src/test_utils/test_grpc_service.rs index 468b6cf..f287ebc 100644 --- a/src/test_utils/test_grpc_service.rs +++ b/src/test_utils/test_grpc_service.rs @@ -1,4 +1,5 @@ use crate::{ + RedisPool, config::get_app_config, grpc::{ defs::grpc_service::v1::{ @@ -8,7 +9,6 @@ use crate::{ service::GrpcService, }, test_utils::get_redis_pool, - RedisPool, }; use sqlx::{ PgPool, diff --git a/src/utils/clear_user_sessions.rs b/src/utils/clear_user_sessions.rs index 5af6e4c..eb3815c 100644 --- a/src/utils/clear_user_sessions.rs +++ b/src/utils/clear_user_sessions.rs @@ -1,6 +1,6 @@ use crate::{ - utils::get_user_sessions::get_user_sessions, RedisPool, + utils::get_user_sessions::get_user_sessions, }; use anyhow::anyhow; @@ -57,11 +57,7 @@ mod tests { for _ in 0..5 { redis_conn .set::<_, _, ()>( - &format!( - "{}:{user_id}:{}", - RedisNamespace::Session, - Uuid::new_v4() - ), + &format!("{}:{user_id}:{}", RedisNamespace::Session, Uuid::new_v4()), &rmp_serde::to_vec_named(&UserSession { user_id, ..Default::default() diff --git a/src/utils/decode_uri_encoded_story_categories.rs b/src/utils/decode_uri_encoded_story_categories.rs index 732464b..db12034 100644 --- a/src/utils/decode_uri_encoded_story_categories.rs +++ b/src/utils/decode_uri_encoded_story_categories.rs @@ -41,11 +41,13 @@ mod tests { #[test] fn can_decode_uri_encoded_story_categories() { - let categories_str = [StoryCategory::Travel.to_string(), + let categories_str = [ + StoryCategory::Travel.to_string(), StoryCategory::Entertainment.to_string(), StoryCategory::DigitalGraphics.to_string(), StoryCategory::Gaming.to_string(), - StoryCategory::Lifestyle.to_string()] + StoryCategory::Lifestyle.to_string(), + ] .join("|"); let uri_encoded = lz_str::compress_to_encoded_uri_component(&categories_str); let decoded = decode_uri_encoded_story_categories(&uri_encoded); diff --git a/src/utils/encode_unsubscribe_fragment.rs b/src/utils/encode_unsubscribe_fragment.rs index 6e4f269..916844e 100644 --- a/src/utils/encode_unsubscribe_fragment.rs +++ b/src/utils/encode_unsubscribe_fragment.rs @@ -1,8 +1,8 @@ use crate::HmacSha1; use hex::encode as encode_hex; use hmac::{ - digest::InvalidLength, Mac, + digest::InvalidLength, }; /// Generates a personalized unsubscribe fragment for a newsletter email. diff --git a/src/utils/generate_hashed_token.rs b/src/utils/generate_hashed_token.rs index 9a705d5..f5b9930 100644 --- a/src/utils/generate_hashed_token.rs +++ b/src/utils/generate_hashed_token.rs @@ -1,9 +1,9 @@ use crate::constants::token::TOKEN_LENGTH; use anyhow::anyhow; use argon2::{ - password_hash::SaltString, Argon2, PasswordHasher, + password_hash::SaltString, }; use nanoid::nanoid; diff --git a/src/utils/get_client_country.rs b/src/utils/get_client_country.rs index fafaa7a..2dd1254 100644 --- a/src/utils/get_client_country.rs +++ b/src/utils/get_client_country.rs @@ -1,6 +1,6 @@ use maxminddb::{ - geoip2, Reader, + geoip2, }; use std::net::IpAddr; diff --git a/src/utils/get_client_location.rs b/src/utils/get_client_location.rs index 6e16582..805141d 100644 --- a/src/utils/get_client_location.rs +++ b/src/utils/get_client_location.rs @@ -1,6 +1,6 @@ use maxminddb::{ - geoip2, Reader, + geoip2, }; use serde::{ Deserialize, diff --git a/src/utils/get_user_sessions.rs b/src/utils/get_user_sessions.rs index 8e28e60..4a8de8b 100644 --- a/src/utils/get_user_sessions.rs +++ b/src/utils/get_user_sessions.rs @@ -1,10 +1,10 @@ use crate::{ + RedisPool, constants::redis_namespaces::RedisNamespace, utils::{ get_client_device::ClientDevice, get_client_location::ClientLocation, }, - RedisPool, }; use anyhow::anyhow; use futures::stream::StreamExt; diff --git a/src/utils/incr_report_limit.rs b/src/utils/incr_report_limit.rs index e4867dc..e6b6f75 100644 --- a/src/utils/incr_report_limit.rs +++ b/src/utils/incr_report_limit.rs @@ -1,9 +1,9 @@ use crate::{ + RedisPool, constants::{ redis_namespaces::RedisNamespace, resource_limit::ResourceLimit, }, - RedisPool, }; use anyhow::anyhow; diff --git a/src/utils/incr_resource_limit.rs b/src/utils/incr_resource_limit.rs index 6b1e0af..3b651fe 100644 --- a/src/utils/incr_resource_limit.rs +++ b/src/utils/incr_resource_limit.rs @@ -1,9 +1,9 @@ use crate::{ + RedisPool, constants::{ redis_namespaces::RedisNamespace, resource_limit::ResourceLimit, }, - RedisPool, }; use anyhow::anyhow; diff --git a/src/utils/incr_subscription_limit.rs b/src/utils/incr_subscription_limit.rs index 9a370a8..58bc0a1 100644 --- a/src/utils/incr_subscription_limit.rs +++ b/src/utils/incr_subscription_limit.rs @@ -1,9 +1,9 @@ use crate::{ + RedisPool, constants::{ redis_namespaces::RedisNamespace, resource_limit::ResourceLimit, }, - RedisPool, }; use anyhow::anyhow; diff --git a/src/utils/md_to_html.rs b/src/utils/md_to_html.rs index 76739ec..cc70f3a 100644 --- a/src/utils/md_to_html.rs +++ b/src/utils/md_to_html.rs @@ -1,10 +1,10 @@ use lazy_static::lazy_static; use markdown::{ - to_html_with_options, CompileOptions, Constructs, Options, ParseOptions, + to_html_with_options, }; use regex::{ Captures, diff --git a/src/utils/to_iso8601.rs b/src/utils/to_iso8601.rs index d8c65ed..553bf79 100644 --- a/src/utils/to_iso8601.rs +++ b/src/utils/to_iso8601.rs @@ -1,14 +1,14 @@ use std::num::NonZeroU8; use time::{ + OffsetDateTime, format_description::well_known::{ + Iso8601, iso8601::{ Config, EncodedConfig, TimePrecision, }, - Iso8601, }, - OffsetDateTime, }; /// The configuration of ISO 8601. diff --git a/tests/tables/blog_lsb_items/blog_lsb_items.rs b/tests/tables/blog_lsb_items/blog_lsb_items.rs index 36f4405..358c77e 100644 --- a/tests/tables/blog_lsb_items/blog_lsb_items.rs +++ b/tests/tables/blog_lsb_items/blog_lsb_items.rs @@ -1,12 +1,12 @@ #[cfg(test)] mod tests { use sqlx::{ - pool::PoolConnection, - postgres::PgRow, Error, PgPool, Postgres, Row, + pool::PoolConnection, + postgres::PgRow, }; use storiny::constants::sql_states::SqlState; use uuid::Uuid; diff --git a/tests/tables/blog_rsb_items/blog_rsb_items.rs b/tests/tables/blog_rsb_items/blog_rsb_items.rs index eea01dc..259568a 100644 --- a/tests/tables/blog_rsb_items/blog_rsb_items.rs +++ b/tests/tables/blog_rsb_items/blog_rsb_items.rs @@ -1,12 +1,12 @@ #[cfg(test)] mod tests { use sqlx::{ - pool::PoolConnection, - postgres::PgRow, Error, PgPool, Postgres, Row, + pool::PoolConnection, + postgres::PgRow, }; use storiny::constants::sql_states::SqlState; use uuid::Uuid; diff --git a/tests/tables/comments/comments.rs b/tests/tables/comments/comments.rs index eb663c0..4baa431 100644 --- a/tests/tables/comments/comments.rs +++ b/tests/tables/comments/comments.rs @@ -1,12 +1,12 @@ #[cfg(test)] mod tests { use sqlx::{ - pool::PoolConnection, - postgres::PgRow, Error, PgPool, Postgres, Row, + pool::PoolConnection, + postgres::PgRow, }; use storiny::constants::sql_states::SqlState; use time::OffsetDateTime; diff --git a/tests/tables/replies/replies.rs b/tests/tables/replies/replies.rs index 45e4db0..7c08062 100644 --- a/tests/tables/replies/replies.rs +++ b/tests/tables/replies/replies.rs @@ -1,12 +1,12 @@ #[cfg(test)] mod tests { use sqlx::{ - pool::PoolConnection, - postgres::PgRow, Error, PgPool, Postgres, Row, + pool::PoolConnection, + postgres::PgRow, }; use storiny::constants::sql_states::SqlState; use time::OffsetDateTime; diff --git a/tests/tables/stories/stories.rs b/tests/tables/stories/stories.rs index 3f5aeaf..eb23f53 100644 --- a/tests/tables/stories/stories.rs +++ b/tests/tables/stories/stories.rs @@ -1,12 +1,12 @@ #[cfg(test)] mod tests { use sqlx::{ - pool::PoolConnection, - postgres::PgRow, Error, PgPool, Postgres, Row, + pool::PoolConnection, + postgres::PgRow, }; use storiny::constants::sql_states::SqlState; use time::OffsetDateTime; From bbbbda201bef996e664eda06332c652c9e9d581a Mon Sep 17 00:00:00 2001 From: zignis Date: Wed, 23 Apr 2025 19:38:20 +0530 Subject: [PATCH 3/7] fix blog login --- session/src/config.rs | 1 - src/routes/v1/auth/login.rs | 4 +++- .../v1/blogs/verify_login/verify_login.rs | 18 +----------------- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/session/src/config.rs b/session/src/config.rs index 2197fa9..cc511d2 100644 --- a/session/src/config.rs +++ b/session/src/config.rs @@ -11,7 +11,6 @@ use serde::{ }; use crate::{ - Session, SessionMiddleware, storage::SessionStore, }; diff --git a/src/routes/v1/auth/login.rs b/src/routes/v1/auth/login.rs index 9e8fe4e..c80421e 100644 --- a/src/routes/v1/auth/login.rs +++ b/src/routes/v1/auth/login.rs @@ -108,7 +108,7 @@ struct QueryParams { fields( email = %payload.email, remember_me = %payload.remember_me, - blog_domain = %payload.blog_domain, + blog_domain = tracing::field::Empty, bypass = query.bypass ) )] @@ -405,6 +405,8 @@ WHERE id = $1 // Handle blog login if let Some(domain) = &payload.blog_domain { + tracing::Span::current().record("blog_domain", domain); + if let Some(login_token) = handle_blog_login( domain, user_id, diff --git a/src/routes/v1/blogs/verify_login/verify_login.rs b/src/routes/v1/blogs/verify_login/verify_login.rs index fbdef36..a922eb1 100644 --- a/src/routes/v1/blogs/verify_login/verify_login.rs +++ b/src/routes/v1/blogs/verify_login/verify_login.rs @@ -1,10 +1,6 @@ use crate::{ AppState, - constants::{ - notification_entity_type::NotificationEntityType, - resource_lock::ResourceLock, - user_flag::UserFlag, - }, + constants::notification_entity_type::NotificationEntityType, error::{ AppError, ToastErrorResponse, @@ -12,21 +8,15 @@ use crate::{ middlewares::identity::identity::Identity, utils::{ clear_user_sessions::clear_user_sessions, - flag::Flag, - generate_hashed_token::generate_hashed_token, - generate_totp::generate_totp, get_client_device::get_client_device, get_client_location::get_client_location, get_user_sessions::get_user_sessions, - incr_resource_lock_attempts::incr_resource_lock_attempts, - incr_subscription_limit::incr_subscription_limit, }, }; use actix_http::HttpMessage; use actix_web::{ HttpRequest, HttpResponse, - http::StatusCode, post, web, }; @@ -36,7 +26,6 @@ use argon2::{ PasswordHasher, password_hash::SaltString, }; -use chrono::Datelike; use serde::{ Deserialize, Serialize, @@ -47,11 +36,6 @@ use storiny_session::{ Session, config::SessionLifecycle, }; -use time::{ - Duration, - OffsetDateTime, -}; -use totp_rs::Secret; use tracing::debug; use url::Url; use validator::Validate; From fddf88f3c38aa42dfe2178ce28b19a3a75c6b764 Mon Sep 17 00:00:00 2001 From: zignis Date: Wed, 23 Apr 2025 21:22:59 +0530 Subject: [PATCH 4/7] fix tests --- .../cleanup_db/fixtures/blog_login_tokens.sql | 33 ++++++++++--------- .../v1/blogs/verify_login/verify_login.rs | 12 ++++--- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/cron/cleanup_db/fixtures/blog_login_tokens.sql b/src/cron/cleanup_db/fixtures/blog_login_tokens.sql index 1d3558d..c2c1410 100644 --- a/src/cron/cleanup_db/fixtures/blog_login_tokens.sql +++ b/src/cron/cleanup_db/fixtures/blog_login_tokens.sql @@ -1,18 +1,19 @@ WITH inserted_user AS ( - INSERT INTO users (name, username, email) VALUES ('Sample user', 'sample_user', 'sample@example.com') - RETURNING id - ), - inserted_blog AS ( - INSERT INTO blogs (name, slug, user_id) - VALUES ('Sample blog', 'sample-blog', (SELECT id FROM inserted_user)) - RETURNING id - ) -INSERT -INTO - blog_login_tokens (id, user_id, blog_id, expires_at) + INSERT + INTO users (name, username, email) + VALUES ('Sample user', 'sample_user', 'sample@example.com') + RETURNING id +), inserted_blogs AS ( + INSERT + INTO blogs (name, slug, user_id) + SELECT + 'Sample blog ' || i, 'sample-blog-' || i, (SELECT id FROM inserted_user) + FROM generate_series(1, 5) i + RETURNING id +) +INSERT INTO blog_login_tokens (id, user_id, blog_id, expires_at) SELECT uuid_generate_v4(), - (SELECT id FROM inserted_user), - (SELECT id FROM inserted_blog), - NOW() - INTERVAL '7 days' -FROM - GENERATE_SERIES(1, 5); + (SELECT id FROM inserted_user), + id, + NOW() - INTERVAL '7 days' +FROM inserted_blogs; \ No newline at end of file diff --git a/src/routes/v1/blogs/verify_login/verify_login.rs b/src/routes/v1/blogs/verify_login/verify_login.rs index a922eb1..3207cdf 100644 --- a/src/routes/v1/blogs/verify_login/verify_login.rs +++ b/src/routes/v1/blogs/verify_login/verify_login.rs @@ -310,17 +310,16 @@ mod tests { res_to_string, }, utils::{ + generate_hashed_token::generate_hashed_token, get_client_device::ClientDevice, get_client_location::ClientLocation, get_user_sessions::UserSession, }, }; use actix_web::{ - Responder, services, test, }; - use argon2::PasswordHasher; use redis::AsyncCommands; use sqlx::PgPool; use std::net::{ @@ -329,6 +328,10 @@ mod tests { SocketAddrV4, }; use storiny_macros::test_context; + use time::{ + Duration, + OffsetDateTime, + }; use uuid::Uuid; #[sqlx::test] @@ -491,7 +494,7 @@ RETURNING blog_id .fetch_one(&mut *conn) .await?; - let blog_id = blog.get::("id"); + let blog_id = blog.get::("blog_id"); // Try to verify login token. let req = test::TestRequest::post() @@ -536,7 +539,7 @@ RETURNING blog_id .fetch_one(&mut *conn) .await?; - let blog_id = blog.get::("id"); + let blog_id = blog.get::("blog_id"); // Try to verify login token. let req = test::TestRequest::post() @@ -648,6 +651,7 @@ RETURNING id // Verify login token. let req = test::TestRequest::post() + .append_header(("origin", "https://test.com")) .uri(&format!("/v1/blogs/{blog_id}/verify-login")) .set_json(Request { token: login_token }) .to_request(); From 6da7704d93f462fa77cd44f9ba2480062d5aeed7 Mon Sep 17 00:00:00 2001 From: zignis Date: Sat, 26 Apr 2025 20:22:59 +0530 Subject: [PATCH 5/7] add verify_blog_login grpc endpoint --- ...30_create_blog_login_tokens_table.down.sql | 1 - ...1930_create_blog_login_tokens_table.up.sql | 14 - redis_namespaces.md | 1 + session/src/config.rs | 4 +- session/src/lib.rs | 43 +- session/src/middleware.rs | 210 +- session/src/session.rs | 35 +- session/src/storage/mod.rs | 12 +- session/src/storage/session_key.rs | 11 +- session/src/storage/utils.rs | 2 +- src/constants/blog_login_token_data.rs | 23 + src/constants/blog_login_token_expiration.rs | 2 + src/constants/mod.rs | 2 + src/constants/redis_namespaces.rs | 2 + src/cron/cleanup_db/cleanup_db.rs | 104 +- .../cleanup_db/fixtures/blog_login_tokens.sql | 19 - .../get_blog_pending_story_count.rs | 1 - .../get_blog_published_story_count.rs | 1 - src/grpc/endpoints/get_login_activity.rs | 5 +- src/grpc/endpoints/get_user_id.rs | 6 +- src/grpc/endpoints/mod.rs | 1 + .../fixtures/verify_blog_login.sql | 7 + src/grpc/endpoints/verify_blog_login/mod.rs | 3 + .../verify_blog_login/verify_blog_login.rs | 841 ++++++ .../verify_newsletter_subscription.rs | 10 +- src/grpc/server.rs | 4 + src/grpc/service.rs | 12 + src/main.rs | 6 +- src/middlewares/identity/identity.rs | 27 +- src/proto/api_service.v1.tonic.rs | 2281 +++++++---------- src/proto/blog_def.v1.rs | 210 +- src/proto/blog_def.v1.serde.rs | 1063 ++++++-- src/routes/init.rs | 7 +- src/routes/mod.rs | 6 + src/routes/v1/auth/login.rs | 456 ++-- src/routes/v1/blogs/mod.rs | 1 - src/routes/v1/blogs/verify_login/mod.rs | 3 - .../v1/blogs/verify_login/verify_login.rs | 791 ------ src/test_utils/test_grpc_service.rs | 5 + src/utils/get_client_device.rs | 4 +- src/utils/get_client_location.rs | 2 +- src/utils/get_user_sessions.rs | 10 + tests/tables/blogs/blogs.rs | 50 - tests/tables/users/users.rs | 50 - 44 files changed, 3316 insertions(+), 3032 deletions(-) delete mode 100644 migrations/20250423071930_create_blog_login_tokens_table.down.sql delete mode 100644 migrations/20250423071930_create_blog_login_tokens_table.up.sql create mode 100644 src/constants/blog_login_token_data.rs create mode 100644 src/constants/blog_login_token_expiration.rs delete mode 100644 src/cron/cleanup_db/fixtures/blog_login_tokens.sql create mode 100644 src/grpc/endpoints/verify_blog_login/fixtures/verify_blog_login.sql create mode 100644 src/grpc/endpoints/verify_blog_login/mod.rs create mode 100644 src/grpc/endpoints/verify_blog_login/verify_blog_login.rs delete mode 100644 src/routes/v1/blogs/verify_login/mod.rs delete mode 100644 src/routes/v1/blogs/verify_login/verify_login.rs diff --git a/migrations/20250423071930_create_blog_login_tokens_table.down.sql b/migrations/20250423071930_create_blog_login_tokens_table.down.sql deleted file mode 100644 index e0e348f..0000000 --- a/migrations/20250423071930_create_blog_login_tokens_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS blog_login_tokens CASCADE; \ No newline at end of file diff --git a/migrations/20250423071930_create_blog_login_tokens_table.up.sql b/migrations/20250423071930_create_blog_login_tokens_table.up.sql deleted file mode 100644 index b02646b..0000000 --- a/migrations/20250423071930_create_blog_login_tokens_table.up.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE IF NOT EXISTS blog_login_tokens -( - -- Hashed token value - id TEXT PRIMARY KEY, - blog_id BIGINT NOT NULL - REFERENCES blogs (id) - ON DELETE CASCADE, - user_id BIGINT NOT NULL - REFERENCES users (id) - ON DELETE CASCADE, - is_persistent_session BOOL NOT NULL DEFAULT TRUE, - expires_at TIMESTAMPTZ NOT NULL, - UNIQUE (blog_id, user_id) -); diff --git a/redis_namespaces.md b/redis_namespaces.md index 4a2d18d..5d57b11 100644 --- a/redis_namespaces.md +++ b/redis_namespaces.md @@ -11,6 +11,7 @@ | `s` | `session` | `s:user_id:session_key` | Persistent cookie session | | `rl` | `resource_limit` | `rl:resource_type:user_id` | Daily resource limit for a user | | `r` | `reading_session` | `r:story_id:session_uuid` | Story reading session for a user. These sessions use a session UUID, sent when the user starts reading a public story. It is then returned to the API server upon leaving the page. This key is used to determine the total reading duration by computing the difference between its creation time and the current time. | +| `bl` | `blog_login` | `bl:hashed_token` | User login verification token for blogs hosted on external domain. | | `l:lgn` | `lock:login` | `l:lgn:email` | Login resource lock, enforced when the login process encounters too many failed attempts for a user. | | `l:sgn` | `lock:signup` | `l:sgn:client_up` | Signup resource lock, enforced when too many users register from the same IP address in a short duration of time. | | `l:rcv` | `lock:recovery` | `l:rcv:email` | Account recovery resource lock, enforced when there are too many recovery requests for an account associated with a specific email address. | diff --git a/session/src/config.rs b/session/src/config.rs index cc511d2..b44a838 100644 --- a/session/src/config.rs +++ b/session/src/config.rs @@ -108,8 +108,6 @@ impl SessionMiddlewareBuilder { /// /// Use `None` to leave the attribute unspecified. If unspecified, the attribute defaults /// to the same host that set the cookie, excluding subdomains. - /// - /// Note: Value set by [Session::set_cookie_domain] will override this value. pub fn cookie_domain(mut self, domain: Option) -> Self { self.configuration.cookie.domain = domain; self @@ -162,7 +160,7 @@ pub(crate) fn default_configuration(key: Key) -> Configuration { secure: true, http_only: true, name: "id".into(), - same_site: SameSite::Lax, + same_site: SameSite::Strict, path: "/".into(), domain: None, max_age: None, diff --git a/session/src/lib.rs b/session/src/lib.rs index 8c83ff6..7a2d4f7 100644 --- a/session/src/lib.rs +++ b/session/src/lib.rs @@ -21,7 +21,11 @@ mod session_ext; pub mod storage; pub use self::{ - middleware::SessionMiddleware, + middleware::{ + EXTERNAL_BLOG_KEY, + LIFECYCLE_KEY, + SessionMiddleware, + }, session::{ Session, SessionGetError, @@ -53,6 +57,7 @@ pub mod test_helpers { F: Fn() -> Store + Clone + Send + 'static, { acceptance_tests::basic_workflow(store_builder.clone()).await; + acceptance_tests::authorization_header(store_builder.clone()).await; acceptance_tests::expiration_is_refreshed_on_changes(store_builder.clone()).await; acceptance_tests::complex_workflow(store_builder.clone(), is_invalidation_supported).await; acceptance_tests::lifecycle(store_builder.clone()).await; @@ -70,6 +75,7 @@ pub mod test_helpers { ServiceResponse, }, guard, + http::header::AUTHORIZATION, middleware, test, web::{ @@ -137,6 +143,41 @@ pub mod test_helpers { assert_eq!(body, Bytes::from_static(b"counter: 100")); } + pub(super) async fn authorization_header(store_builder: F) + where + Store: SessionStore + 'static, + F: Fn() -> Store + Clone + Send + 'static, + { + let app = test::init_service( + App::new() + .wrap( + SessionMiddleware::builder(store_builder(), key()) + .session_ttl(time::Duration::seconds(100)) + .build(), + ) + .service(web::resource("/").to(|ses: Session| async move { + ses.insert("counter", Value::from(10)); + "test" + })) + .service(web::resource("/test/").to(|ses: Session| async move { + let val: usize = ses.get("counter").unwrap().unwrap(); + format!("counter: {val}") + })), + ) + .await; + + let request = test::TestRequest::get().to_request(); + let response = app.call(request).await.unwrap(); + let cookie = response.get_cookie("id").expect("Cookie is set"); + let token = cookie.value(); + + let request = test::TestRequest::with_uri("/test/") + .insert_header((AUTHORIZATION, format!("Bearer {token}"))) + .to_request(); + let body = test::call_and_read_body(&app, request).await; + assert_eq!(body, Bytes::from_static(b"counter: 10")); + } + pub(super) async fn expiration_is_refreshed_on_changes(store_builder: F) where Store: SessionStore + 'static, diff --git a/session/src/middleware.rs b/session/src/middleware.rs index 582bf28..a94037c 100644 --- a/session/src/middleware.rs +++ b/session/src/middleware.rs @@ -11,6 +11,7 @@ use crate::{ storage::{ LoadError, SessionKey, + SessionKeySource, SessionStore, }, }; @@ -35,11 +36,15 @@ use actix_web::{ forward_ready, }, http::header::{ + AUTHORIZATION, HeaderValue, SET_COOKIE, }, }; -use anyhow::Context; +use anyhow::{ + Context, + anyhow, +}; use serde_json::{ Map, Value, @@ -51,6 +56,7 @@ use std::{ pin::Pin, rc::Rc, }; +use tracing::debug; /// A middleware for session management. /// @@ -134,8 +140,8 @@ fn e500(err: E) -> actix_web::Error { .into() } -static LIFECYCLE_KEY: &str = "lifecycle"; -static DOMAIN_KEY: &str = "cookie_domain"; +pub static LIFECYCLE_KEY: &str = "lifecycle"; +pub static EXTERNAL_BLOG_KEY: &str = "ext_blog"; #[doc(hidden)] #[non_exhaustive] @@ -164,11 +170,12 @@ where let configuration = Rc::clone(&self.configuration); Box::pin(async move { - let session_key = extract_session_key(&req, &configuration.cookie); + let (session_key, key_source) = + extract_session_key(&req, &configuration.cookie).unzip(); let (session_key, session_state) = load_session_state(session_key, storage_backend.as_ref()).await?; + let mut session_lifecycle = SessionLifecycle::PersistentSession; - let mut cookie_domain: Option = None; if let Some(lifecycle) = session_state.get(LIFECYCLE_KEY) { let lifecycle = lifecycle @@ -178,16 +185,16 @@ where session_lifecycle = SessionLifecycle::from_i32(lifecycle as i32); } - if let Some(domain) = session_state.get(DOMAIN_KEY) { - let domain = domain.as_str(); - cookie_domain = domain.map(|val| val.to_string()); - } + let is_external_blog = if let Some(ext_blog) = session_state.get(EXTERNAL_BLOG_KEY) { + ext_blog.as_bool().unwrap_or_default() + } else { + false + }; - Session::set_session(&mut req, session_state, session_lifecycle, cookie_domain); + Session::set_session(&mut req, session_state, session_lifecycle); let mut res = service.call(req).await?; - let (lifecycle, cookie_domain, status, mut session_state) = - Session::get_changes(&mut res); + let (lifecycle, status, mut session_state) = Session::get_changes(&mut res); // We only insert the dynamic properties into the session if they already exist or are // non-empty to avoid creating sessions on every request. @@ -196,30 +203,30 @@ where LIFECYCLE_KEY.to_string(), Value::from(lifecycle.clone() as i32), ); - - if let Some(domain) = cookie_domain.clone() { - session_state.insert(DOMAIN_KEY.to_string(), Value::from(domain)); - } } + let can_set_cookie = + !is_external_blog && key_source != Some(SessionKeySource::AuthorizationHeader); + match session_key { None => { // We do not create an entry in the session store if there is no state attached - // to a fresh session + // to a fresh session. if !session_state.is_empty() { let session_key = storage_backend .save(session_state, &configuration.session.state_ttl) .await .map_err(e500)?; - set_session_cookie( - res.response_mut().head_mut(), - session_key, - &configuration.cookie, - lifecycle, - cookie_domain, - ) - .map_err(e500)?; + if can_set_cookie { + set_session_cookie( + res.response_mut().head_mut(), + session_key, + &configuration.cookie, + lifecycle, + ) + .map_err(e500)?; + } } } @@ -235,25 +242,27 @@ where .await .map_err(e500)?; - set_session_cookie( - res.response_mut().head_mut(), - session_key, - &configuration.cookie, - lifecycle, - cookie_domain, - ) - .map_err(e500)?; + if can_set_cookie { + set_session_cookie( + res.response_mut().head_mut(), + session_key, + &configuration.cookie, + lifecycle, + ) + .map_err(e500)?; + } } SessionStatus::Purged => { storage_backend.delete(&session_key).await.map_err(e500)?; - delete_session_cookie( - res.response_mut().head_mut(), - cookie_domain, - &configuration.cookie, - ) - .map_err(e500)?; + if can_set_cookie { + delete_session_cookie( + res.response_mut().head_mut(), + &configuration.cookie, + ) + .map_err(e500)?; + } } SessionStatus::Renewed => { @@ -264,14 +273,15 @@ where .await .map_err(e500)?; - set_session_cookie( - res.response_mut().head_mut(), - session_key, - &configuration.cookie, - lifecycle, - cookie_domain, - ) - .map_err(e500)?; + if can_set_cookie { + set_session_cookie( + res.response_mut().head_mut(), + session_key, + &configuration.cookie, + lifecycle, + ) + .map_err(e500)?; + } } SessionStatus::Unchanged => {} @@ -284,31 +294,83 @@ where } } -/// Examines the session cookie attached to the incoming request, if there is one, and tries -/// to extract the session key. +/// Extracts session token from an `Authorization` header containing a Bearer token. Used for blogs +/// hosted on external domains. +/// +/// Returns `Some(String)` if the header is a valid Bearer token and successfully parsed, `None` +/// otherwise. +/// +/// * `header` - The `Authorization` header value expected in the form `Bearer `. +fn extract_token_from_auth_header(header: &HeaderValue) -> Option { + // "Bearer *" length + if header.len() < 8 { + debug!("header is too short"); + return None; + } + + let mut parts = header.to_str().ok()?.splitn(2, ' '); + + match parts.next() { + Some("Bearer") => {} + _ => { + debug!("no bearer prefix"); + return None; + } + } + + let token = parts.next()?.to_string(); + + Some(token) +} + +/// Examines the authorization header and session cookie attached to the incoming request, if +/// present, and attempts to extract the session key. The order of preference is the authorization +/// header first, followed by the cookie as a fallback if the authorization header is not present. +/// +/// It returns `None` if no session proof is found or if the session proof is considered invalid +/// (e.g., due to a failed signature check). /// -/// It returns `None` if there is no session cookie or if the session cookie is considered invalid -/// (e.g., when failing a signature check). -fn extract_session_key(req: &ServiceRequest, config: &CookieConfiguration) -> Option { - let cookies = req.cookies().ok()?; - let session_cookie = cookies - .iter() - .find(|&cookie| cookie.name() == config.name)?; +/// * `req` - The incoming [ServiceRequest] instance. +/// * `config` - The [CookieConfiguration] instance. +fn extract_session_key( + req: &ServiceRequest, + config: &CookieConfiguration, +) -> Option<(SessionKey, SessionKeySource)> { + let (session_cookie, key_source) = if let Some(auth_header) = req.headers().get(AUTHORIZATION) { + debug!("using authorization header"); + + let token = extract_token_from_auth_header(auth_header)?; + + ( + Cookie::new(config.name.clone(), token), + SessionKeySource::AuthorizationHeader, + ) + } else { + debug!("using cookie"); + + let cookies = req.cookies().ok()?; + let session_cookie = cookies + .iter() + .find(|&cookie| cookie.name() == config.name) + .map(|cookie| cookie.clone())?; + + (session_cookie, SessionKeySource::Cookie) + }; let mut jar = CookieJar::new(); - jar.add_original(session_cookie.clone()); + jar.add_original(session_cookie); let verification_result = jar.signed(&config.key).get(&config.name); if verification_result.is_none() { tracing::warn!( - "The session cookie attached to the incoming request failed to pass cryptographic \ + "The session proof attached to the incoming request failed to pass cryptographic \ checks (signature verification/decryption)." ); } match verification_result?.value().to_owned().try_into() { - Ok(session_key) => Some(session_key), + Ok(session_key) => Some((session_key, key_source)), Err(err) => { tracing::warn!( error.message = %err, @@ -321,6 +383,10 @@ fn extract_session_key(req: &ServiceRequest, config: &CookieConfiguration) -> Op } } +/// Loads the session state from session storage using the provided `session_key`. +/// +/// * `session_key` - The session key for item. +/// * `storage_backend` - The session storage backend. async fn load_session_state( session_key: Option, storage_backend: &Store, @@ -365,12 +431,19 @@ async fn load_session_state( } } +/// Attaches a signed session cookie to the outgoing response based on the provided session key +/// and cookie configuration. +/// +/// * `response` - A mutable reference to the response head to which the `Set-Cookie` header will be +/// added. +/// * `session_key` - The session key to be stored in the cookie. +/// * `config` - The [CookieConfiguration] instance. +/// * `session_lifecycle` - Indicates whether the session should be persistent or session-based. fn set_session_cookie( response: &mut ResponseHead, session_key: SessionKey, config: &CookieConfiguration, session_lifecycle: SessionLifecycle, - domain: Option, ) -> Result<(), anyhow::Error> { let value: String = session_key.into(); let mut cookie = Cookie::new(config.name.clone(), value); @@ -387,7 +460,7 @@ fn set_session_cookie( } } - if let Some(domain) = domain.clone().or_else(|| config.domain.clone()) { + if let Some(domain) = config.domain.clone() { cookie.set_domain(domain); } @@ -395,7 +468,10 @@ fn set_session_cookie( jar.signed_mut(&config.key).add(cookie); // Set cookie - let cookie = jar.delta().next().unwrap(); + let cookie = jar + .delta() + .next() + .ok_or(anyhow!("unable to sign the cookie"))?; let val = HeaderValue::from_str(&cookie.encoded().to_string()) .context("Failed to attach a session cookie to the outgoing response")?; @@ -404,9 +480,14 @@ fn set_session_cookie( Ok(()) } +/// Attaches a removal cookie to the outgoing response, effectively instructing the client +/// to delete the existing session cookie. +/// +/// * `response` - A mutable reference to the response head to which the removal `Set-Cookie` header +/// will be added. +/// * `config` - The [CookieConfiguration] instance. fn delete_session_cookie( response: &mut ResponseHead, - domain: Option, config: &CookieConfiguration, ) -> Result<(), anyhow::Error> { let removal_cookie = Cookie::build(config.name.clone(), "") @@ -415,8 +496,7 @@ fn delete_session_cookie( .http_only(config.http_only) .same_site(config.same_site); - let mut removal_cookie = if let Some(domain) = domain.clone().or_else(|| config.domain.clone()) - { + let mut removal_cookie = if let Some(domain) = config.domain.clone() { removal_cookie.domain(domain) } else { removal_cookie diff --git a/session/src/session.rs b/session/src/session.rs index 2a9e6f2..e6181c5 100644 --- a/session/src/session.rs +++ b/session/src/session.rs @@ -95,7 +95,6 @@ struct SessionInner { state: Map, status: SessionStatus, lifecycle: SessionLifecycle, - cookie_domain: Option, } impl Session { @@ -174,7 +173,7 @@ impl Session { } } - /// Removes session both client and server side. + /// Removes session from both client and server side. pub fn purge(&self) { let mut inner = self.0.borrow_mut(); inner.status = SessionStatus::Purged; @@ -209,25 +208,6 @@ impl Session { inner.lifecycle.to_owned() } - /// Sets the domain of the session cookie. - pub fn set_cookie_domain(&self, domain: Option) { - let mut inner = self.0.borrow_mut(); - - if inner.status != SessionStatus::Purged { - if inner.status != SessionStatus::Renewed { - inner.status = SessionStatus::Changed; - } - - inner.cookie_domain = domain; - } - } - - /// Returns the domain of the session cookie. - pub fn get_cookie_domain(&self) -> Option { - let inner = self.0.borrow_mut(); - inner.cookie_domain.to_owned() - } - /// Adds the given key-value pairs to the session on the request. /// /// Values that match keys already existing on the session will be overwritten. @@ -236,16 +216,14 @@ impl Session { req: &mut ServiceRequest, data: impl IntoIterator, lifecycle: SessionLifecycle, - cookie_domain: Option, ) { let session = Session::get_session(&mut req.extensions_mut()); let mut inner = session.0.borrow_mut(); inner.state.extend(data); inner.lifecycle = lifecycle; - inner.cookie_domain = cookie_domain; } - /// Returns session lifecycle, session cookie domain, session status, and iterator of key-value + /// Returns session lifecycle, session status, and iterator of key-value /// pairs of changes. /// /// This is a destructive operation - the session state is removed from the request extensions @@ -254,12 +232,7 @@ impl Session { #[allow(clippy::needless_pass_by_ref_mut)] pub(crate) fn get_changes( res: &mut ServiceResponse, - ) -> ( - SessionLifecycle, - Option, - SessionStatus, - Map, - ) { + ) -> (SessionLifecycle, SessionStatus, Map) { if let Some(s_impl) = res .request() .extensions() @@ -268,14 +241,12 @@ impl Session { let state = mem::take(&mut s_impl.borrow_mut().state); ( s_impl.borrow().lifecycle.clone(), - s_impl.borrow().cookie_domain.clone(), s_impl.borrow().status.clone(), state, ) } else { ( SessionLifecycle::PersistentSession, - None, SessionStatus::Unchanged, Map::new(), ) diff --git a/session/src/storage/mod.rs b/session/src/storage/mod.rs index ad76712..673bbcf 100644 --- a/session/src/storage/mod.rs +++ b/session/src/storage/mod.rs @@ -1,7 +1,9 @@ //! Storage backend for session state. mod interface; +mod redis_rs; mod session_key; +mod utils; pub use self::{ interface::{ @@ -10,12 +12,12 @@ pub use self::{ SessionStore, UpdateError, }, - session_key::SessionKey, + session_key::{ + SessionKey, + SessionKeySource, + }, + utils::generate_session_key, }; - -mod redis_rs; -mod utils; - pub use redis_rs::{ RedisSessionStore, RedisSessionStoreBuilder, diff --git a/session/src/storage/session_key.rs b/session/src/storage/session_key.rs index eab139a..6905c83 100644 --- a/session/src/storage/session_key.rs +++ b/session/src/storage/session_key.rs @@ -5,6 +5,15 @@ use derive_more::{ From, }; +/// The source of the client session key. +#[derive(Debug, Eq, PartialEq)] +pub enum SessionKeySource { + /// Authorization header (for blogs hosted on external domains). + AuthorizationHeader, + /// Session cookie. + Cookie, +} + /// A session key, the string stored in a client-side cookie to associate a user with its session /// state on the backend. /// @@ -20,7 +29,7 @@ use derive_more::{ /// let session_key: Result = key.try_into(); /// assert!(session_key.is_err()); /// ``` -#[derive(Debug, Display, PartialEq, Eq)] +#[derive(Debug, Clone, Display, PartialEq, Eq)] pub struct SessionKey(String); impl TryFrom for SessionKey { diff --git a/session/src/storage/utils.rs b/session/src/storage/utils.rs index 46386d1..4fe4dbe 100644 --- a/session/src/storage/utils.rs +++ b/session/src/storage/utils.rs @@ -9,7 +9,7 @@ use std::convert::TryInto; /// Session key generation routine that follows [OWASP recommendations]. /// /// [OWASP recommendations]: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#session-id-entropy -pub(crate) fn generate_session_key(user_id: Option) -> SessionKey { +pub fn generate_session_key(user_id: Option) -> SessionKey { let value = std::iter::repeat(()) .map(|()| OsRng.sample(Alphanumeric)) .take(64) diff --git a/src/constants/blog_login_token_data.rs b/src/constants/blog_login_token_data.rs new file mode 100644 index 0000000..3eb2031 --- /dev/null +++ b/src/constants/blog_login_token_data.rs @@ -0,0 +1,23 @@ +use crate::utils::{ + get_client_device::ClientDevice, + get_client_location::ClientLocation, +}; +use serde::{ + Deserialize, + Serialize, +}; + +/// The struct for storing a user's temporary blog login data. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BlogLoginTokenData { + /// The user ID. + pub uid: i64, + /// The blog ID. + pub bid: i64, + /// Whether the session is persistent. + pub persistent: bool, + /// The user's [ClientLocation] value. + pub loc: Option, + /// The user's [ClientDevice] value. + pub device: Option, +} diff --git a/src/constants/blog_login_token_expiration.rs b/src/constants/blog_login_token_expiration.rs new file mode 100644 index 0000000..52f4b10 --- /dev/null +++ b/src/constants/blog_login_token_expiration.rs @@ -0,0 +1,2 @@ +/// The expiration time for a blog login token (in seconds). +pub const BLOG_LOGIN_TOKEN_EXPIRATION: usize = 120; diff --git a/src/constants/mod.rs b/src/constants/mod.rs index 19ffeb0..15e2a41 100644 --- a/src/constants/mod.rs +++ b/src/constants/mod.rs @@ -1,5 +1,7 @@ pub mod account_activity_type; pub mod blog_domain_regex; +pub mod blog_login_token_data; +pub mod blog_login_token_expiration; pub mod blog_slug_regex; pub mod buckets; pub mod connection_provider; diff --git a/src/constants/redis_namespaces.rs b/src/constants/redis_namespaces.rs index 5535a02..5de46ca 100644 --- a/src/constants/redis_namespaces.rs +++ b/src/constants/redis_namespaces.rs @@ -15,4 +15,6 @@ pub enum RedisNamespace { BackgroundJob, #[strum(serialize = "rl")] ResourceLimit, + #[strum(serialize = "bl")] + BlogLogin, } diff --git a/src/cron/cleanup_db/cleanup_db.rs b/src/cron/cleanup_db/cleanup_db.rs index 384eac2..d652be0 100644 --- a/src/cron/cleanup_db/cleanup_db.rs +++ b/src/cron/cleanup_db/cleanup_db.rs @@ -147,32 +147,6 @@ WHERE expires_at < NOW() Ok(()) } -/// Cleans the `blog_login_tokens` table based on the conditions specified in [cleanup_db]. -/// -/// * `db_pool` - The Postgres connection pool. -#[tracing::instrument(skip_all, err)] -async fn clean_blog_login_tokens(db_pool: &Pool) -> Result<(), Error> { - trace!("attempting to clean the `blog_login_tokens` table..."); - - let delete_tokens_result = sqlx::query( - r#" -DELETE FROM blog_login_tokens -WHERE expires_at < NOW() -"#, - ) - .execute(db_pool) - .await - .map_err(|err| Box::from(err.to_string())) - .map_err(Error::Failed)?; - - debug!( - "deleted {} rows from the `blog_login_tokens` table", - delete_tokens_result.rows_affected() - ); - - Ok(()) -} - /// Cleans the `user_statuses` table based on the conditions specified in [cleanup_db]. /// /// * `db_pool` - The Postgres connection pool. @@ -241,8 +215,8 @@ WHERE NOT EXISTS ( /// permanently deleted. This would also permanently delete the relations that the story holds; /// such as likes, comments, and replies. /// -/// - Expired tokens, newsletter tokens, blog login tokens, and user statuses (based on the -/// `expires_at` column) will be permanently deleted. +/// - Expired tokens, newsletter tokens, and user statuses (based on the `expires_at` column) will +/// be permanently deleted. /// /// - Notifications that do not have any related row in `notification_outs` table will be /// permanently deleted. @@ -259,11 +233,10 @@ pub async fn cleanup_db( clean_users(db_pool).await?; // These can be run in parallel as they do not depend on each other. - future::try_join5( + future::try_join4( clean_stories(db_pool), clean_tokens(db_pool), clean_newsletter_tokens(db_pool), - clean_blog_login_tokens(db_pool), clean_user_statuses(db_pool), ) .await?; @@ -736,77 +709,6 @@ WHERE id = (SELECT id FROM selected_token) Ok(()) } - // Blog login tokens - - #[sqlx::test(fixtures("blog_login_tokens"))] - async fn can_clean_blog_login_tokens_table(pool: PgPool) -> sqlx::Result<()> { - let mut conn = pool.acquire().await?; - - // Rows should be present initially. - let result = sqlx::query(r#"SELECT 1 FROM blog_login_tokens"#) - .fetch_all(&mut *conn) - .await?; - - assert!(!result.is_empty()); - - let state = get_cron_job_state_for_test(pool, None).await; - let result = cleanup_db(DatabaseCleanupJob(Utc::now()), state).await; - - assert!(result.is_ok()); - - // Rows should get deleted from the database. - let result = sqlx::query(r#"SELECT 1 FROM blog_login_tokens"#) - .fetch_all(&mut *conn) - .await?; - - assert!(result.is_empty()); - - Ok(()) - } - - #[sqlx::test(fixtures("blog_login_tokens"))] - async fn should_not_delete_non_expired_blog_login_tokens(pool: PgPool) -> sqlx::Result<()> { - let mut conn = pool.acquire().await?; - - // Rows should be present initially. - let result = sqlx::query(r#"SELECT 1 FROM blog_login_tokens"#) - .fetch_all(&mut *conn) - .await?; - - assert!(!result.is_empty()); - - // Update `expires_at` for a single token. - let result = sqlx::query( - r#" -WITH selected_token AS ( - SELECT id FROM blog_login_tokens - LIMIT 1 -) -UPDATE blog_login_tokens -SET expires_at = NOW() + INTERVAL '7 days' -WHERE id = (SELECT id FROM selected_token) -"#, - ) - .execute(&mut *conn) - .await?; - - assert_eq!(result.rows_affected(), 1); - - let state = get_cron_job_state_for_test(pool, None).await; - let result = cleanup_db(DatabaseCleanupJob(Utc::now()), state).await; - - assert!(result.is_ok()); - - // Exactly one row should be present in the database. - let result = sqlx::query(r#"SELECT 1 FROM blog_login_tokens"#) - .fetch_all(&mut *conn) - .await?; - - assert_eq!(result.len(), 1); - - Ok(()) - } - // User statuses #[sqlx::test(fixtures("user_statuses"))] diff --git a/src/cron/cleanup_db/fixtures/blog_login_tokens.sql b/src/cron/cleanup_db/fixtures/blog_login_tokens.sql deleted file mode 100644 index c2c1410..0000000 --- a/src/cron/cleanup_db/fixtures/blog_login_tokens.sql +++ /dev/null @@ -1,19 +0,0 @@ -WITH inserted_user AS ( - INSERT - INTO users (name, username, email) - VALUES ('Sample user', 'sample_user', 'sample@example.com') - RETURNING id -), inserted_blogs AS ( - INSERT - INTO blogs (name, slug, user_id) - SELECT - 'Sample blog ' || i, 'sample-blog-' || i, (SELECT id FROM inserted_user) - FROM generate_series(1, 5) i - RETURNING id -) -INSERT INTO blog_login_tokens (id, user_id, blog_id, expires_at) -SELECT uuid_generate_v4(), - (SELECT id FROM inserted_user), - id, - NOW() - INTERVAL '7 days' -FROM inserted_blogs; \ No newline at end of file diff --git a/src/grpc/endpoints/get_blog_pending_story_count/get_blog_pending_story_count.rs b/src/grpc/endpoints/get_blog_pending_story_count/get_blog_pending_story_count.rs index 27a63f5..2d662fd 100644 --- a/src/grpc/endpoints/get_blog_pending_story_count/get_blog_pending_story_count.rs +++ b/src/grpc/endpoints/get_blog_pending_story_count/get_blog_pending_story_count.rs @@ -78,7 +78,6 @@ WHERE .await .map_err(|error| { error!("database error: {error:?}"); - Status::internal("Database error") })?; diff --git a/src/grpc/endpoints/get_blog_published_story_count/get_blog_published_story_count.rs b/src/grpc/endpoints/get_blog_published_story_count/get_blog_published_story_count.rs index 80c9417..f5e010c 100644 --- a/src/grpc/endpoints/get_blog_published_story_count/get_blog_published_story_count.rs +++ b/src/grpc/endpoints/get_blog_published_story_count/get_blog_published_story_count.rs @@ -78,7 +78,6 @@ WHERE .await .map_err(|error| { error!("database error: {error:?}"); - Status::internal("Database error") })?; diff --git a/src/grpc/endpoints/get_login_activity.rs b/src/grpc/endpoints/get_login_activity.rs index 2d86ef4..86da9ec 100644 --- a/src/grpc/endpoints/get_login_activity.rs +++ b/src/grpc/endpoints/get_login_activity.rs @@ -168,9 +168,9 @@ mod tests { #[test_context(RedisTestContext)] #[sqlx::test] - async fn can_return_login_activity(_ctx: &mut RedisTestContext, pool: PgPool) { + async fn can_return_login_activity(_ctx: &mut RedisTestContext, _pool: PgPool) { test_grpc_service( - pool, + _pool, true, Box::new(|mut client, _, redis_pool, user_id| async move { let config = get_app_config().unwrap(); @@ -203,6 +203,7 @@ mod tests { }), domain: Some("example.com".to_string()), ack: false, + ext_blog: Some(false), }) .unwrap(), ) diff --git a/src/grpc/endpoints/get_user_id.rs b/src/grpc/endpoints/get_user_id.rs index 9945bd5..3461d7e 100644 --- a/src/grpc/endpoints/get_user_id.rs +++ b/src/grpc/endpoints/get_user_id.rs @@ -38,11 +38,10 @@ pub async fn get_user_id( // Session key is in the format `user_id:token`. let session_key = extract_session_key_from_cookie(&request.into_inner().token, &secret_key) .ok_or(Status::not_found("Invalid token"))?; - let cache_key = format!("{}:{}", RedisNamespace::Session, session_key); + let cache_key = format!("{}:{session_key}", RedisNamespace::Session); let mut conn = client.redis_pool.get().await.map_err(|error| { error!("unable to acquire a connection from the Redis pool: {error:?}"); - Status::internal("Redis error") })?; @@ -52,14 +51,12 @@ pub async fn get_user_id( .await .map_err(|error| { error!("unable to fetch the session data: {error:?}"); - Status::internal("Redis error") })?; // Missing session. if result.is_none() { debug!("no session found in the cache"); - return Err(Status::not_found("Session not found")); } @@ -68,7 +65,6 @@ pub async fn get_user_id( // This can happen when we manually insert a key value pair into the session while the // user has not logged-in. warn!("`user_id` is not present in the session data: {error:?}"); - Status::not_found("Valid session not found") })? .user_id; diff --git a/src/grpc/endpoints/mod.rs b/src/grpc/endpoints/mod.rs index 9326234..d805a5c 100644 --- a/src/grpc/endpoints/mod.rs +++ b/src/grpc/endpoints/mod.rs @@ -34,5 +34,6 @@ pub mod get_user_mute_count; pub mod get_user_relations_info; pub mod get_username; pub mod validate_story; +pub mod verify_blog_login; pub mod verify_email; pub mod verify_newsletter_subscription; diff --git a/src/grpc/endpoints/verify_blog_login/fixtures/verify_blog_login.sql b/src/grpc/endpoints/verify_blog_login/fixtures/verify_blog_login.sql new file mode 100644 index 0000000..a07014f --- /dev/null +++ b/src/grpc/endpoints/verify_blog_login/fixtures/verify_blog_login.sql @@ -0,0 +1,7 @@ +WITH inserted_user AS ( + INSERT INTO users (name, username, email) + VALUES ('Sample user', 'sample_user', 'sample@example.com') + RETURNING id) +INSERT +INTO blogs (id, name, slug, domain, user_id) +VALUES (1, 'Sample blog', 'test-blog', 'test.com', (SELECT id FROM inserted_user)); diff --git a/src/grpc/endpoints/verify_blog_login/mod.rs b/src/grpc/endpoints/verify_blog_login/mod.rs new file mode 100644 index 0000000..0dca71c --- /dev/null +++ b/src/grpc/endpoints/verify_blog_login/mod.rs @@ -0,0 +1,3 @@ +mod verify_blog_login; + +pub use verify_blog_login::*; diff --git a/src/grpc/endpoints/verify_blog_login/verify_blog_login.rs b/src/grpc/endpoints/verify_blog_login/verify_blog_login.rs new file mode 100644 index 0000000..ddd93ec --- /dev/null +++ b/src/grpc/endpoints/verify_blog_login/verify_blog_login.rs @@ -0,0 +1,841 @@ +use crate::{ + constants::{ + blog_login_token_data::BlogLoginTokenData, + notification_entity_type::NotificationEntityType, + redis_namespaces::RedisNamespace, + session_cookie::SESSION_COOKIE_NAME, + token::TOKEN_LENGTH, + }, + grpc::{ + defs::blog_def::v1::{ + VerifyBlogLoginRequest, + VerifyBlogLoginResponse, + }, + service::GrpcService, + }, + middlewares::identity::identity::Identity, + utils::{ + clear_user_sessions::clear_user_sessions, + get_user_sessions::get_user_sessions, + }, +}; +use actix_web::cookie::{ + Cookie, + CookieJar, +}; +use argon2::{ + Argon2, + PasswordHasher, + password_hash::SaltString, +}; +use redis::cmd; +use serde_json::Value; +use sqlx::{ + Postgres, + QueryBuilder, + Row, +}; +use storiny_session::{ + EXTERNAL_BLOG_KEY, + LIFECYCLE_KEY, + config::SessionLifecycle, + storage::generate_session_key, +}; +use time::Duration; +use tonic::{ + Request, + Response, + Status, +}; +use tracing::{ + debug, + error, + warn, +}; +// TODO: Revert migration `blog_login_tokens` + +/// The TTL value (in seconds) for a user's session. +static SESSION_TTL: i64 = Duration::weeks(1).whole_seconds(); + +/// Verifies blog login token for a user. +#[tracing::instrument( + name = "GRPC verify_blog_login", + skip_all, + fields( + identifier = tracing::field::Empty, + hostname = tracing::field::Empty + ), + err +)] +pub async fn verify_blog_login( + client: &GrpcService, + request: Request, +) -> Result, Status> { + let request = request.into_inner(); + let pg_pool = &client.db_pool; + let identifier = request.blog_identifier; + let token_id = request.token; + let hostname = request.host; + + // Identifier can be slug, domain or the ID + let is_identifier_number = identifier.parse::().is_ok(); + + tracing::Span::current().record("identifier", &identifier); + tracing::Span::current().record("hostname", &hostname); + + // Validate token length. + if token_id.chars().count() != TOKEN_LENGTH { + warn!("token length does not match"); + return Ok(Response::new(VerifyBlogLoginResponse { + is_token_valid: false, + is_persistent_cookie: None, + cookie_value: None, + })); + } + + let salt = SaltString::from_b64(&client.config.token_salt).map_err(|error| { + error!("unable to parse the salt string: {error:?}"); + Status::internal("unable to verify the token") + })?; + + let hashed_token = Argon2::default() + .hash_password(token_id.as_bytes(), &salt) + .map_err(|error| { + error!("unable to generate token hash: {error:?}"); + Status::internal("unable to verify the token") + })?; + let cache_key = format!("{}:{hashed_token}", RedisNamespace::BlogLogin); + + let mut redis_conn = client.redis_pool.get().await.map_err(|error| { + error!("unable to acquire a connection from the Redis pool: {error:?}"); + Status::internal("Redis error") + })?; + + // Fetch login data from cache. + let result: Option> = cmd("GET") + .arg(&[&cache_key]) + .query_async(&mut redis_conn) + .await + .map_err(|error| { + error!("unable to fetch the login data: {error:?}"); + Status::internal("Redis error") + })?; + + // Missing or expired login token. + if result.is_none() { + warn!("no login token found in the cache"); + + return Ok(Response::new(VerifyBlogLoginResponse { + is_token_valid: false, + is_persistent_cookie: None, + cookie_value: None, + })); + } + + let login_data = rmp_serde::from_slice::(&result.unwrap_or_default()) + .map_err(|error| { + error!("unable to parse login data: {error:?}"); + Status::internal("parsing error") + })?; + let user_id = login_data.uid; + + let mut txn = pg_pool.begin().await.map_err(|error| { + error!("unable to begin the transaction: {error:?}"); + Status::internal("Database error") + })?; + + // Fetch blog using identifier. + let mut query_builder: QueryBuilder = QueryBuilder::new( + r#" +SELECT id, domain FROM blogs b +WHERE +"#, + ); + + query_builder.push(if is_identifier_number { + r#" +(b.id = $1::BIGINT OR b.slug = $1) +"# + } else { + // The identifier is definitely not an ID + r#" +(b.domain = $1 OR b.slug = $1) +"# + }); + + query_builder.push(r#" AND b.deleted_at IS NULL "#); + + let blog = query_builder + .build() + .bind(identifier) + .fetch_one(&mut *txn) + .await + .map_err(|error| { + if matches!(error, sqlx::Error::RowNotFound) { + Status::not_found("Blog not found") + } else { + error!("database error: {error:?}"); + Status::internal("Database error") + } + })?; + let blog_id = blog.get::("id"); + + // Assert blog ID. + if blog_id != login_data.bid { + warn!("blog id from database does not match the one from cache"); + + return Ok(Response::new(VerifyBlogLoginResponse { + is_token_valid: false, + is_persistent_cookie: None, + cookie_value: None, + })); + } + + // Assert blog domain. + if blog.get::, _>("domain") != Some(hostname.clone()) { + warn!("blog domain from database does not match the one from cache"); + + return Ok(Response::new(VerifyBlogLoginResponse { + is_token_valid: false, + is_persistent_cookie: None, + cookie_value: None, + })); + } + + // Insert session into cache. + let client_location = login_data.loc; + let client_device = login_data.device; + let mut login_data_map = Identity::get_login_data_map(user_id); + + login_data_map.insert(EXTERNAL_BLOG_KEY.to_string(), Value::Bool(true)); + login_data_map.insert( + LIFECYCLE_KEY.to_string(), + Value::from(if login_data.persistent { + SessionLifecycle::PersistentSession + } else { + SessionLifecycle::BrowserSession + } as i32), + ); + + if let Ok(domain) = serde_json::to_value(hostname) { + login_data_map.insert("domain".to_string(), domain); + } + + if let Some(ref device) = client_device { + if let Ok(device) = serde_json::to_value(device) { + login_data_map.insert("device".to_string(), device); + } + } + + if let Some(ref location) = client_location { + if let Ok(location) = serde_json::to_value(location) { + login_data_map.insert("location".to_string(), location); + } + } + + let session_key = generate_session_key(Some(user_id.to_string())); + let session_cache_key = format!("{}:{}", RedisNamespace::Session, session_key.as_ref()); + let body = rmp_serde::to_vec_named(&login_data_map).map_err(|error| { + error!("unable to serialize login data: {error:?}"); + Status::internal("serialization error") + })?; + + let mut redis_pipe = redis::pipe(); + redis_pipe + .atomic() + .cmd("SET") // Insert session + .arg(&session_cache_key) + .arg(&body) + .arg("NX") // NX: only set the key if it does not already exist + .arg("EX") // EX: set expiry + .arg(SESSION_TTL) + .ignore() + .cmd("DEL") // Delete login token + .arg(&[&cache_key]); + + // Generate signed cookie value. + let encoded_cookie_value = { + let mut jar = CookieJar::new(); + jar.signed_mut(&client.cookie_secret_key) + .add(Cookie::new(SESSION_COOKIE_NAME, session_key.to_string())); + jar.delta() + .next() + .and_then(|cookie| { + cookie + .encoded() // Percent-encode cookie + .stripped() + .to_string() + .splitn(2, '=') // Extract value from the encoded string + .nth(1) + .map(|value| value.to_string()) + }) + .ok_or(Status::internal("unable to sign the cookie"))? + }; + + let client_device_str = client_device + .map(|device| device.display_name.to_string()) + .unwrap_or("Unknown device".to_string()); + + // Update the `last_login_at` and insert a login notification for the user. + sqlx::query( + r#" +WITH updated_user AS ( + UPDATE users + SET last_login_at = NOW() + WHERE id = $2 +), +inserted_notification AS ( + INSERT INTO notifications (entity_type) + VALUES ($1) + RETURNING id +) +INSERT +INTO + notification_outs ( + notified_id, + notification_id, + rendered_content + ) +SELECT $2, (SELECT id FROM inserted_notification), $3 +"#, + ) + .bind(NotificationEntityType::LoginAttempt as i16) + .bind(user_id) + .bind(if let Some(location) = client_location { + format!("{client_device_str}:{}", location.display_name.to_string()) + } else { + client_device_str + }) + .execute(&mut *txn) + .await + .map_err(|_| Status::internal("Database error"))?; + + // Check if the user maintains more than or equal to 10 sessions, and + // delete all the previous sessions if the current number of active + // sessions for the user exceeds the per user session limit (10). + match get_user_sessions(&client.redis_pool, user_id).await { + Ok(sessions) => { + if sessions.len() >= 10 { + match clear_user_sessions(&client.redis_pool, user_id).await { + Ok(_) => { + debug!( + "cleared {} overflowing sessions for the user", + sessions.len() + ); + } + Err(error) => { + return Err(Status::internal(format!( + "unable to clear the overflowing sessions for the user: {:?}", + error + ))); + } + }; + } + } + Err(error) => { + return Err(Status::internal(format!( + "unable to fetch the sessions for the user: {:?}", + error + ))); + } + }; + + redis_pipe + .query_async::<_, ()>(&mut redis_conn) + .await + .map_err(|error| { + error!("Redis error: {error:?}"); + Status::internal("Redis error") + })?; + + txn.commit().await.map_err(|error| { + error!("unable to commit the transaction: {error:?}"); + Status::internal("Database error") + })?; + + Ok(Response::new(VerifyBlogLoginResponse { + is_token_valid: true, + is_persistent_cookie: Some(login_data.persistent), + cookie_value: Some(encoded_cookie_value), + })) +} + +#[cfg(test)] +mod tests { + use crate::{ + config::get_app_config, + constants::{ + blog_login_token_data::BlogLoginTokenData, + blog_login_token_expiration::BLOG_LOGIN_TOKEN_EXPIRATION, + notification_entity_type::NotificationEntityType, + redis_namespaces::RedisNamespace, + session_cookie::SESSION_COOKIE_NAME, + }, + grpc::defs::{ + blog_def::v1::VerifyBlogLoginRequest, + login_activity_def::v1::DeviceType, + }, + routes::{ + GetLoginDetailsResponse, + get_login_details, + }, + test_utils::{ + RedisTestContext, + init_app_for_test, + test_grpc_service, + }, + utils::{ + clear_user_sessions::clear_user_sessions, + generate_hashed_token::generate_hashed_token, + get_client_device::ClientDevice, + get_client_location::ClientLocation, + get_user_sessions::{ + UserSession, + get_user_sessions, + }, + }, + }; + use actix_web::{ + cookie::Cookie, + test, + }; + use redis::{ + AsyncCommands, + RedisResult, + aio::ConnectionLike, + }; + use sqlx::{ + PgPool, + Row, + }; + use storiny_macros::test_context; + use time::OffsetDateTime; + use tokio::time::{ + Duration, + sleep, + }; + use tonic::Request; + use urlencoding::decode; + use uuid::Uuid; + + /// Generates a random hashed token based on the provided `salt` and returns the (cache_key, + /// token_id, hashed_token) tuple. + /// + /// * `salt` - The salt string used to hash the token. + fn get_token(salt: &str) -> (String, String, String) { + let (token_id, hashed_token) = generate_hashed_token(salt).unwrap(); + let cache_key = format!("{}:{hashed_token}", RedisNamespace::BlogLogin); + (cache_key, token_id, hashed_token) + } + + /// Inserts the provided `token_data` into the cache. + /// + /// * `cache_key` - The key for this token data. + /// * `token_data` - The [BlogLoginTokenData] instance. + /// * `conn` - The Redis connection instance. + async fn insert_login_token( + cache_key: &str, + token_data: &BlogLoginTokenData, + conn: &mut C, + ) -> RedisResult<()> + where + C: ConnectionLike, + { + let serialized_token_data = + rmp_serde::to_vec_named(token_data).expect("unable to serialize"); + + redis::cmd("SET") + .arg(&cache_key) + .arg(&serialized_token_data) + .arg("EX") // EX: set expiry + .arg(BLOG_LOGIN_TOKEN_EXPIRATION) + .query_async::<_, ()>(&mut *conn) + .await + } + + mod serial { + use super::*; + + #[test_context(RedisTestContext)] + #[sqlx::test(fixtures("verify_blog_login"))] + async fn can_verify_blog_login(_ctx: &mut RedisTestContext, pool: PgPool) { + test_grpc_service( + pool, + true, + Box::new(|mut client, pool, redis_pool, user_id| async move { + let config = get_app_config().unwrap(); + + let mut db_conn = pool.acquire().await.unwrap(); + let mut redis_conn = redis_pool.get().await.unwrap(); + let web_app = init_app_for_test(get_login_details, pool, false, true, None) + .await + .0; + + let user_id = user_id.unwrap(); + let blog_id = 1_i64; + let domain = "test.com"; + let (cache_key, token_id, _) = get_token(&config.token_salt); + + let mut token_data = BlogLoginTokenData { + uid: user_id, + bid: blog_id, + persistent: true, + loc: None, + device: None, + }; + + // With `persistent` = true and no other properties. + insert_login_token(&cache_key, &token_data, &mut *redis_conn) + .await + .unwrap(); + + let response = client + .verify_blog_login(Request::new(VerifyBlogLoginRequest { + blog_identifier: blog_id.to_string(), + token: token_id.to_string(), + host: domain.to_string(), + })) + .await; + + assert!(response.is_ok()); + + let response = response.unwrap().into_inner(); + + assert!(response.cookie_value.is_some()); + assert_eq!(response.is_persistent_cookie.unwrap(), true); + assert_eq!(response.is_token_valid, true); + + // Should insert a login session into the cache. + let sessions = get_user_sessions(&redis_pool, user_id) + .await + .expect("unable to get user sessions"); + + assert_eq!(sessions.len(), 1); + + let session_data = &sessions[0].1; + + assert_eq!(session_data.user_id, user_id); + assert_eq!(session_data.domain.clone().unwrap(), "test.com"); + assert_eq!(session_data.ext_blog, Some(true)); + assert_eq!(session_data.ack, false); + assert!(session_data.device.is_none()); + assert!(session_data.location.is_none()); + + // Should also insert a notification. + let result = sqlx::query( + r#" +SELECT EXISTS ( + SELECT + 1 + FROM + notification_outs + WHERE + notification_id = ( + SELECT id FROM notifications + WHERE entity_type = $1 + ) + ) +"#, + ) + .bind(NotificationEntityType::LoginAttempt as i16) + .fetch_one(&mut *db_conn) + .await + .unwrap(); + + assert!(result.get::("exists")); + + // Should also update the `last_login_at` column. + let result = sqlx::query( + r#" +SELECT last_login_at FROM users +WHERE username = $1 +"#, + ) + .bind("sample_user") + .fetch_one(&mut *db_conn) + .await + .unwrap(); + + assert!( + result + .get::, _>("last_login_at") + .is_some() + ); + + // Should delete the login token. + let result: Option> = redis::cmd("GET") + .arg(&[&cache_key]) + .query_async(&mut *redis_conn) + .await + .unwrap(); + + assert!(result.is_none()); + + clear_user_sessions(&redis_pool, user_id) + .await + .expect("unable to clear user sessions"); + + // With `persistent` = false and other client properties set. + token_data.persistent = false; + token_data.loc = Some(ClientLocation { + display_name: "test_location".to_string(), + lat: Some(25.0), + lng: Some(25.0), + }); + token_data.device = Some(ClientDevice { + display_name: "test_device".to_string(), + r#type: DeviceType::Computer as i32, + }); + + insert_login_token(&cache_key, &token_data, &mut *redis_conn) + .await + .unwrap(); + + let response = client + .verify_blog_login(Request::new(VerifyBlogLoginRequest { + blog_identifier: blog_id.to_string(), + token: token_id.to_string(), + host: domain.to_string(), + })) + .await; + + assert!(response.is_ok()); + + let response = response.unwrap().into_inner(); + + assert!(response.cookie_value.is_some()); + assert_eq!(response.is_persistent_cookie.unwrap(), false); + assert_eq!(response.is_token_valid, true); + + // Should insert a login session into the cache. + let sessions = get_user_sessions(&redis_pool, user_id) + .await + .expect("unable to get user sessions"); + + assert_eq!(sessions.len(), 1); + + let session_data = &sessions[0].1; + + assert_eq!(session_data.user_id, user_id); + assert_eq!(session_data.domain.clone().unwrap(), "test.com"); + assert_eq!(session_data.ext_blog, Some(true)); + assert_eq!(session_data.ack, false); + assert_eq!( + session_data.device.clone().unwrap(), + token_data.device.unwrap() + ); + assert_eq!( + session_data.location.clone().unwrap(), + token_data.loc.unwrap() + ); + + let cookie_value = response.cookie_value.unwrap(); + let decoded_value = + decode(&cookie_value).expect("unable to decode cookie value"); + + // Should be a valid cookie value to be used as a session token. + let req = test::TestRequest::get() + .cookie(Cookie::new(SESSION_COOKIE_NAME, decoded_value.to_string())) + .uri("/get-login-details") + .to_request(); + let res = test::call_service(&web_app, req).await; + let client_session = + test::read_body_json::(res).await; + + assert!(client_session.device.is_some()); + assert!(client_session.location.is_some()); + assert_eq!(client_session.domain, Some("test.com".to_string())); + + // Should delete the login token. + let result: Option> = redis::cmd("GET") + .arg(&[&cache_key]) + .query_async(&mut *redis_conn) + .await + .unwrap(); + + assert!(result.is_none()); + }), + ) + .await; + } + + #[test_context(RedisTestContext)] + #[sqlx::test(fixtures("verify_blog_login"))] + async fn can_handle_an_expired_token(_ctx: &mut RedisTestContext, _pool: PgPool) { + test_grpc_service( + _pool, + true, + Box::new(|mut client, _, redis_pool, user_id| async move { + let config = get_app_config().unwrap(); + let mut redis_conn = redis_pool.get().await.unwrap(); + let user_id = user_id.unwrap(); + let blog_id = 1_i64; + let domain = "test.com"; + let (cache_key, token_id, _) = get_token(&config.token_salt); + + let serialized_token_data = rmp_serde::to_vec_named(&BlogLoginTokenData { + uid: user_id, + bid: blog_id, + persistent: true, + loc: None, + device: None, + }) + .expect("unable to serialize"); + + redis::cmd("SET") + .arg(&cache_key) + .arg(&serialized_token_data) + .arg("EX") // EX: set expiry + .arg(3) // 3 seconds + .query_async::<_, ()>(&mut *redis_conn) + .await + .unwrap(); + + sleep(Duration::from_secs(5)).await; // Wait for token to expire + + let response = client + .verify_blog_login(Request::new(VerifyBlogLoginRequest { + blog_identifier: blog_id.to_string(), + token: token_id.to_string(), + host: domain.to_string(), + })) + .await; + + assert!(response.is_ok()); + + let response = response.unwrap().into_inner(); + + assert_eq!(response.is_token_valid, false); + }), + ) + .await; + } + + #[test_context(RedisTestContext)] + #[sqlx::test(fixtures("verify_blog_login"))] + async fn can_reject_verification_for_unmatched_host( + _ctx: &mut RedisTestContext, + _pool: PgPool, + ) { + test_grpc_service( + _pool, + true, + Box::new(|mut client, _, redis_pool, user_id| async move { + let config = get_app_config().unwrap(); + let mut redis_conn = redis_pool.get().await.unwrap(); + let user_id = user_id.unwrap(); + let blog_id = 1_i64; + let (cache_key, token_id, _) = get_token(&config.token_salt); + + insert_login_token( + &cache_key, + &BlogLoginTokenData { + uid: user_id, + bid: blog_id, + persistent: true, + loc: None, + device: None, + }, + &mut *redis_conn, + ) + .await + .unwrap(); + + let response = client + .verify_blog_login(Request::new(VerifyBlogLoginRequest { + blog_identifier: blog_id.to_string(), + token: token_id.to_string(), + host: "invalid.com".to_string(), + })) + .await; + + assert!(response.is_ok()); + + let response = response.unwrap().into_inner(); + + assert_eq!(response.is_token_valid, false); + }), + ) + .await; + } + + #[test_context(RedisTestContext)] + #[sqlx::test(fixtures("verify_blog_login"))] + async fn can_clear_overflowing_sessions_on_verification( + _ctx: &mut RedisTestContext, + _pool: PgPool, + ) { + test_grpc_service( + _pool, + true, + Box::new(|mut client, _, redis_pool, user_id| async move { + let config = get_app_config().unwrap(); + let mut redis_conn = redis_pool.get().await.unwrap(); + let user_id = user_id.unwrap(); + let blog_id = 1_i64; + let domain = "test.com"; + let (cache_key, token_id, _) = get_token(&config.token_salt); + + insert_login_token( + &cache_key, + &BlogLoginTokenData { + uid: user_id, + bid: blog_id, + persistent: true, + loc: None, + device: None, + }, + &mut *redis_conn, + ) + .await + .unwrap(); + + // Create 10 sessions. + for _ in 0..10 { + let _: () = redis_conn + .set( + &format!( + "{}:{}:{}", + RedisNamespace::Session, + user_id, + Uuid::new_v4() + ), + &rmp_serde::to_vec_named(&UserSession { + user_id, + ..Default::default() + }) + .unwrap(), + ) + .await + .unwrap(); + } + + let sessions = get_user_sessions(&redis_pool, user_id).await.unwrap(); + + assert_eq!(sessions.len(), 10); + + // Send verification request. + let response = client + .verify_blog_login(Request::new(VerifyBlogLoginRequest { + blog_identifier: blog_id.to_string(), + token: token_id.to_string(), + host: domain.to_string(), + })) + .await; + + assert!(response.is_ok()); + + let response = response.unwrap().into_inner(); + + assert_eq!(response.is_token_valid, true); + + // Should remove previous sessions. + let sessions = get_user_sessions(&redis_pool, user_id).await.unwrap(); + + assert_eq!(sessions.len(), 1); + }), + ) + .await; + } + } +} diff --git a/src/grpc/endpoints/verify_newsletter_subscription/verify_newsletter_subscription.rs b/src/grpc/endpoints/verify_newsletter_subscription/verify_newsletter_subscription.rs index 6d329de..393b0df 100644 --- a/src/grpc/endpoints/verify_newsletter_subscription/verify_newsletter_subscription.rs +++ b/src/grpc/endpoints/verify_newsletter_subscription/verify_newsletter_subscription.rs @@ -31,11 +31,6 @@ pub async fn verify_newsletter_subscription( request: Request, ) -> Result, Status> { let pg_pool = &client.db_pool; - let mut txn = pg_pool.begin().await.map_err(|error| { - error!("unable to begin the transaction: {error:?}"); - Status::internal("Database error") - })?; - let token_id = request.into_inner().identifier; // Validate token length. @@ -58,6 +53,11 @@ pub async fn verify_newsletter_subscription( Status::internal("unable to verify the token") })?; + let mut txn = pg_pool.begin().await.map_err(|error| { + error!("unable to begin the transaction: {error:?}"); + Status::internal("Database error") + })?; + let token_result = sqlx::query( r#" SELECT blog_id, email diff --git a/src/grpc/server.rs b/src/grpc/server.rs index 838d996..8ce0b0a 100644 --- a/src/grpc/server.rs +++ b/src/grpc/server.rs @@ -6,6 +6,7 @@ use crate::{ service::GrpcService, }, }; +use actix_web::cookie::Key; use sqlx::{ Pool, Postgres, @@ -49,6 +50,7 @@ pub async fn start_grpc_server( ) -> io::Result<()> { let endpoint = config.grpc_endpoint.clone(); let secret_token = config.grpc_secret_token.clone(); + let cookie_secret_key = Key::from(config.session_secret_key.as_bytes()); let is_dev = config.is_dev; tokio::spawn(async move { @@ -75,6 +77,7 @@ pub async fn start_grpc_server( redis_pool, config, db_pool, + cookie_secret_key, }) .send_compressed(CompressionEncoding::Gzip) .accept_compressed(CompressionEncoding::Gzip), @@ -85,6 +88,7 @@ pub async fn start_grpc_server( redis_pool, config, db_pool, + cookie_secret_key, }) .accept_compressed(CompressionEncoding::Gzip) .send_compressed(CompressionEncoding::Gzip), diff --git a/src/grpc/service.rs b/src/grpc/service.rs index f471654..1fa317d 100644 --- a/src/grpc/service.rs +++ b/src/grpc/service.rs @@ -24,6 +24,8 @@ use crate::{ GetBlogWritersInfoResponse, GetUserBlogsInfoRequest, GetUserBlogsInfoResponse, + VerifyBlogLoginRequest, + VerifyBlogLoginResponse, }, comment_def::v1::{ GetCommentRequest, @@ -112,6 +114,7 @@ use crate::{ endpoints, }, }; +use actix_web::cookie::Key; use sqlx::{ Pool, Postgres, @@ -131,6 +134,8 @@ pub struct GrpcService { pub db_pool: Pool, /// The Redis connection instance. pub redis_pool: RedisPool, + /// The secret key used to sign authentication cookies. + pub cookie_secret_key: Key, } #[tonic::async_trait] @@ -360,6 +365,13 @@ impl ApiService for GrpcService { endpoints::get_blog_writers_info::get_blog_writers_info(self, request).await } + async fn verify_blog_login( + &self, + request: Request, + ) -> Result, Status> { + endpoints::verify_blog_login::verify_blog_login(self, request).await + } + async fn get_user_blogs_info( &self, request: Request, diff --git a/src/main.rs b/src/main.rs index 2815d02..6e6bb0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -355,7 +355,11 @@ fn main() -> io::Result<()> { SessionMiddleware::builder(redis_store.clone(), secret_key.clone()) .session_ttl(time::Duration::weeks(1)) .cookie_name(SESSION_COOKIE_NAME.into()) - .cookie_same_site(SameSite::None) + .cookie_same_site(if config.is_dev { + SameSite::None + } else { + SameSite::Strict + }) .cookie_domain(if config.is_dev { None } else { diff --git a/src/middlewares/identity/identity.rs b/src/middlewares/identity/identity.rs index a7fc159..17f1f60 100644 --- a/src/middlewares/identity/identity.rs +++ b/src/middlewares/identity/identity.rs @@ -21,7 +21,10 @@ use actix_web::{ Payload, }, }; -use serde_json::Value; +use serde_json::{ + Map, + Value, +}; use storiny_session::Session; /// A verified user identity. It can be used as a request extractor. @@ -84,8 +87,9 @@ impl Identity { /// * `id` - ID of the user. pub fn login(ext: &Extensions, id: i64) -> Result { let inner = IdentityInner::extract(ext); - inner.session.insert(ID_KEY, Value::from(id)); let now = OffsetDateTime::now_utc().unix_timestamp(); + + inner.session.insert(ID_KEY, Value::from(id)); inner .session .insert(LOGIN_UNIX_TIMESTAMP_KEY, Value::from(now)); @@ -95,6 +99,25 @@ impl Identity { Ok(Self(inner)) } + /// Returns a map containing login-related session data for a given user ID. + /// + /// Returns a [`Map`] with the following keys: + /// - `ID_KEY`: the user's ID + /// - `LOGIN_UNIX_TIMESTAMP_KEY`: current UTC timestamp (login time) + /// - `"ack"`: the boolean acknowledgment flag + /// + /// * `id` - ID of the user. + pub fn get_login_data_map(id: i64) -> Map { + let mut map: Map = Map::new(); + let now = OffsetDateTime::now_utc().unix_timestamp(); + + map.insert(ID_KEY.to_string(), Value::from(id)); + map.insert(LOGIN_UNIX_TIMESTAMP_KEY.to_string(), Value::from(now)); + map.insert("ack".to_string(), Value::from(false)); // Acknowledged flag + + map + } + /// Removes the user identity from the current session. /// /// After `logout` has been called, the user will no longer be able to access routes that diff --git a/src/proto/api_service.v1.tonic.rs b/src/proto/api_service.v1.tonic.rs index 0006597..2c8cc3b 100644 --- a/src/proto/api_service.v1.tonic.rs +++ b/src/proto/api_service.v1.tonic.rs @@ -2,10 +2,12 @@ /// Generated client implementations. pub mod api_service_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] - use tonic::codegen::*; - use tonic::codegen::http::Uri; + use tonic::codegen::{ + http::Uri, + *, + }; /** Service definition -*/ + */ #[derive(Debug, Clone)] pub struct ApiServiceClient { inner: tonic::client::Grpc, @@ -44,14 +46,13 @@ pub mod api_service_client { F: tonic::service::Interceptor, T::ResponseBody: Default, T: tonic::codegen::Service< - http::Request, - Response = http::Response< - >::ResponseBody, + http::Request, + Response = http::Response< + >::ResponseBody, + >, >, - >, - , - >>::Error: Into + Send + Sync, + >>::Error: + Into + Send + Sync, { ApiServiceClient::new(InterceptedService::new(inner, interceptor)) } @@ -87,188 +88,149 @@ pub mod api_service_client { self } /** * - Checks whether the user is authenticated using the token from the session cookie -*/ + Checks whether the user is authenticated using the token from the session cookie + */ pub async fn get_user_id( &mut self, - request: impl tonic::IntoRequest< - super::super::super::user_def::v1::GetUserIdRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetUserId", - ); + let path = http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetUserId"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("api_service.v1.ApiService", "GetUserId")); self.inner.unary(req, path, codec).await } /** * - Returns the username for a user by its ID -*/ + Returns the username for a user by its ID + */ pub async fn get_username( &mut self, - request: impl tonic::IntoRequest< - super::super::super::user_def::v1::GetUsernameRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetUsername", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetUsername"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("api_service.v1.ApiService", "GetUsername")); self.inner.unary(req, path, codec).await } /** * - Returns the profile page data for a user -*/ + Returns the profile page data for a user + */ pub async fn get_profile( &mut self, - request: impl tonic::IntoRequest< - super::super::super::profile_def::v1::GetProfileRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetProfile", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetProfile"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("api_service.v1.ApiService", "GetProfile")); self.inner.unary(req, path, codec).await } /** * - Returns the tag page data for a tag -*/ + Returns the tag page data for a tag + */ pub async fn get_tag( &mut self, - request: impl tonic::IntoRequest< - super::super::super::tag_def::v1::GetTagRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetTag", - ); + let path = http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetTag"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("api_service.v1.ApiService", "GetTag")); self.inner.unary(req, path, codec).await } /** * - Returns the token using its identifier -*/ + Returns the token using its identifier + */ pub async fn get_token( &mut self, - request: impl tonic::IntoRequest< - super::super::super::token_def::v1::GetTokenRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetToken", - ); + let path = http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetToken"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("api_service.v1.ApiService", "GetToken")); self.inner.unary(req, path, codec).await } /** * - Verifies a user's email using the provided token identifier -*/ + Verifies a user's email using the provided token identifier + */ pub async fn verify_email( &mut self, - request: impl tonic::IntoRequest< - super::super::super::token_def::v1::VerifyEmailRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/VerifyEmail", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/VerifyEmail"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("api_service.v1.ApiService", "VerifyEmail")); self.inner.unary(req, path, codec).await } /** * - Verifies a visitor's newsletter subscription request using the provided token identifier -*/ + Verifies a visitor's newsletter subscription request using the provided token identifier + */ pub async fn verify_newsletter_subscription( &mut self, request: impl tonic::IntoRequest< @@ -280,32 +242,52 @@ pub mod api_service_client { >, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/VerifyNewsletterSubscription", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new( - "api_service.v1.ApiService", - "VerifyNewsletterSubscription", - ), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "VerifyNewsletterSubscription", + )); + self.inner.unary(req, path, codec).await + } + /** * + Verifies a user's blog login request using the provided token identifier + */ + pub async fn verify_blog_login( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/VerifyBlogLogin"); + let mut req = request.into_request(); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "VerifyBlogLogin", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's credentials settings -*/ + Returns the user's credentials settings + */ pub async fn get_credential_settings( &mut self, request: impl tonic::IntoRequest< @@ -317,29 +299,26 @@ pub mod api_service_client { >, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetCredentialSettings", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetCredentialSettings"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetCredentialSettings", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's privacy settings -*/ + Returns the user's privacy settings + */ pub async fn get_privacy_settings( &mut self, request: impl tonic::IntoRequest< @@ -351,29 +330,26 @@ pub mod api_service_client { >, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetPrivacySettings", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetPrivacySettings"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetPrivacySettings", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's notification settings -*/ + Returns the user's notification settings + */ pub async fn get_notification_settings( &mut self, request: impl tonic::IntoRequest< @@ -385,32 +361,26 @@ pub mod api_service_client { >, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetNotificationSettings", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new( - "api_service.v1.ApiService", - "GetNotificationSettings", - ), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetNotificationSettings", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's connection settings -*/ + Returns the user's connection settings + */ pub async fn get_connection_settings( &mut self, request: impl tonic::IntoRequest< @@ -422,908 +392,758 @@ pub mod api_service_client { >, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetConnectionSettings", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetConnectionSettings"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetConnectionSettings", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's login activity -*/ + Returns the user's login activity + */ pub async fn get_login_activity( &mut self, request: impl tonic::IntoRequest< super::super::super::login_activity_def::v1::GetLoginActivityRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::login_activity_def::v1::GetLoginActivityResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetLoginActivity", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetLoginActivity"); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetLoginActivity"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetLoginActivity", + )); self.inner.unary(req, path, codec).await } /** * - Validates a story -*/ + Validates a story + */ pub async fn validate_story( &mut self, - request: impl tonic::IntoRequest< - super::super::super::story_def::v1::ValidateStoryRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/ValidateStory", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/ValidateStory"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("api_service.v1.ApiService", "ValidateStory")); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "ValidateStory", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's drafts details -*/ + Returns the user's drafts details + */ pub async fn get_drafts_info( &mut self, - request: impl tonic::IntoRequest< - super::super::super::story_def::v1::GetDraftsInfoRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetDraftsInfo", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetDraftsInfo"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("api_service.v1.ApiService", "GetDraftsInfo")); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetDraftsInfo", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's stories details -*/ + Returns the user's stories details + */ pub async fn get_stories_info( &mut self, - request: impl tonic::IntoRequest< - super::super::super::story_def::v1::GetStoriesInfoRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetStoriesInfo", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetStoriesInfo"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("api_service.v1.ApiService", "GetStoriesInfo")); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetStoriesInfo", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's contributions details -*/ + Returns the user's contributions details + */ pub async fn get_contributions_info( &mut self, request: impl tonic::IntoRequest< super::super::super::story_def::v1::GetContributionsInfoRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::story_def::v1::GetContributionsInfoResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetContributionsInfo", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetContributionsInfo"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetContributionsInfo", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's responses details -*/ + Returns the user's responses details + */ pub async fn get_responses_info( &mut self, request: impl tonic::IntoRequest< super::super::super::response_def::v1::GetResponsesInfoRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::response_def::v1::GetResponsesInfoResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetResponsesInfo", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetResponsesInfo"); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetResponsesInfo"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetResponsesInfo", + )); self.inner.unary(req, path, codec).await } /** * - Returns the story's responses details -*/ + Returns the story's responses details + */ pub async fn get_story_responses_info( &mut self, request: impl tonic::IntoRequest< super::super::super::response_def::v1::GetStoryResponsesInfoRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::response_def::v1::GetStoryResponsesInfoResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetStoryResponsesInfo", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetStoryResponsesInfo"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetStoryResponsesInfo", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's followed tag count -*/ + Returns the user's followed tag count + */ pub async fn get_followed_tag_count( &mut self, request: impl tonic::IntoRequest< super::super::super::tag_def::v1::GetFollowedTagCountRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::tag_def::v1::GetFollowedTagCountResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetFollowedTagCount", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetFollowedTagCount"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetFollowedTagCount", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's relations details -*/ + Returns the user's relations details + */ pub async fn get_user_relations_info( &mut self, request: impl tonic::IntoRequest< super::super::super::user_def::v1::GetUserRelationsInfoRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::user_def::v1::GetUserRelationsInfoResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetUserRelationsInfo", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetUserRelationsInfo"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetUserRelationsInfo", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's blogs details -*/ + Returns the user's blogs details + */ pub async fn get_user_blogs_info( &mut self, - request: impl tonic::IntoRequest< - super::super::super::blog_def::v1::GetUserBlogsInfoRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetUserBlogsInfo", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetUserBlogsInfo"); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetUserBlogsInfo"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetUserBlogsInfo", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's block count -*/ + Returns the user's block count + */ pub async fn get_user_block_count( &mut self, request: impl tonic::IntoRequest< super::super::super::user_def::v1::GetUserBlockCountRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::user_def::v1::GetUserBlockCountResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetUserBlockCount", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetUserBlockCount"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetUserBlockCount", + )); self.inner.unary(req, path, codec).await } /** * - Returns the user's mute count -*/ + Returns the user's mute count + */ pub async fn get_user_mute_count( &mut self, - request: impl tonic::IntoRequest< - super::super::super::user_def::v1::GetUserMuteCountRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetUserMuteCount", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetUserMuteCount"); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetUserMuteCount"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetUserMuteCount", + )); self.inner.unary(req, path, codec).await } /** * - Returns the story's data -*/ + Returns the story's data + */ pub async fn get_story( &mut self, - request: impl tonic::IntoRequest< - super::super::super::story_def::v1::GetStoryRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetStory", - ); + let path = http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetStory"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("api_service.v1.ApiService", "GetStory")); self.inner.unary(req, path, codec).await } /** * - Returns the story's metadata -*/ + Returns the story's metadata + */ pub async fn get_story_metadata( &mut self, request: impl tonic::IntoRequest< super::super::super::story_def::v1::GetStoryMetadataRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::story_def::v1::GetStoryMetadataResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetStoryMetadata", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetStoryMetadata"); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetStoryMetadata"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetStoryMetadata", + )); self.inner.unary(req, path, codec).await } /** * - Returns the comment's data -*/ + Returns the comment's data + */ pub async fn get_comment( &mut self, - request: impl tonic::IntoRequest< - super::super::super::comment_def::v1::GetCommentRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetComment", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetComment"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("api_service.v1.ApiService", "GetComment")); self.inner.unary(req, path, codec).await } /** * - Creates a new draft -*/ + Creates a new draft + */ pub async fn create_draft( &mut self, - request: impl tonic::IntoRequest< - super::super::super::story_def::v1::CreateDraftRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/CreateDraft", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/CreateDraft"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("api_service.v1.ApiService", "CreateDraft")); self.inner.unary(req, path, codec).await } /** * - Returns the blog's data -*/ + Returns the blog's data + */ pub async fn get_blog( &mut self, - request: impl tonic::IntoRequest< - super::super::super::blog_def::v1::GetBlogRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetBlog", - ); + let path = http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetBlog"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("api_service.v1.ApiService", "GetBlog")); self.inner.unary(req, path, codec).await } /** * - Returns the blog's archive data -*/ + Returns the blog's archive data + */ pub async fn get_blog_archive( &mut self, - request: impl tonic::IntoRequest< - super::super::super::blog_def::v1::GetBlogArchiveRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetBlogArchive", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetBlogArchive"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("api_service.v1.ApiService", "GetBlogArchive")); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetBlogArchive", + )); self.inner.unary(req, path, codec).await } /** * - Returns the blog's pending story count -*/ + Returns the blog's pending story count + */ pub async fn get_blog_pending_story_count( &mut self, request: impl tonic::IntoRequest< super::super::super::blog_def::v1::GetBlogPendingStoryCountRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogPendingStoryCountResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetBlogPendingStoryCount", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new( - "api_service.v1.ApiService", - "GetBlogPendingStoryCount", - ), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetBlogPendingStoryCount", + )); self.inner.unary(req, path, codec).await } /** * - Returns the blog's published story count -*/ + Returns the blog's published story count + */ pub async fn get_blog_published_story_count( &mut self, request: impl tonic::IntoRequest< super::super::super::blog_def::v1::GetBlogPublishedStoryCountRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogPublishedStoryCountResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetBlogPublishedStoryCount", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new( - "api_service.v1.ApiService", - "GetBlogPublishedStoryCount", - ), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetBlogPublishedStoryCount", + )); self.inner.unary(req, path, codec).await } /** * - Returns the blog's editors details -*/ + Returns the blog's editors details + */ pub async fn get_blog_editors_info( &mut self, request: impl tonic::IntoRequest< super::super::super::blog_def::v1::GetBlogEditorsInfoRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogEditorsInfoResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetBlogEditorsInfo", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetBlogEditorsInfo"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetBlogEditorsInfo", + )); self.inner.unary(req, path, codec).await } /** * - Returns the blog's writers details -*/ + Returns the blog's writers details + */ pub async fn get_blog_writers_info( &mut self, request: impl tonic::IntoRequest< super::super::super::blog_def::v1::GetBlogWritersInfoRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogWritersInfoResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetBlogWritersInfo", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetBlogWritersInfo"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetBlogWritersInfo", + )); self.inner.unary(req, path, codec).await } /** * - Returns the blog's sitemap -*/ + Returns the blog's sitemap + */ pub async fn get_blog_sitemap( &mut self, - request: impl tonic::IntoRequest< - super::super::super::blog_def::v1::GetBlogSitemapRequest, - >, + request: impl tonic::IntoRequest, ) -> std::result::Result< tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/api_service.v1.ApiService/GetBlogSitemap", - ); + let path = + http::uri::PathAndQuery::from_static("/api_service.v1.ApiService/GetBlogSitemap"); let mut req = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("api_service.v1.ApiService", "GetBlogSitemap")); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetBlogSitemap", + )); self.inner.unary(req, path, codec).await } /** * - Returns the blog's newsletter -*/ + Returns the blog's newsletter + */ pub async fn get_blog_newsletter( &mut self, request: impl tonic::IntoRequest< super::super::super::blog_def::v1::GetBlogNewsletterRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogNewsletterResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetBlogNewsletter", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetBlogNewsletter"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetBlogNewsletter", + )); self.inner.unary(req, path, codec).await } /** * - Returns the blog's newsletter details -*/ + Returns the blog's newsletter details + */ pub async fn get_blog_newsletter_info( &mut self, request: impl tonic::IntoRequest< super::super::super::blog_def::v1::GetBlogNewsletterInfoRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogNewsletterInfoResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetBlogNewsletterInfo", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetBlogNewsletterInfo"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetBlogNewsletterInfo", + )); self.inner.unary(req, path, codec).await } /** * - Returns the story's open graph data -*/ + Returns the story's open graph data + */ pub async fn get_story_open_graph_data( &mut self, request: impl tonic::IntoRequest< super::super::super::open_graph_def::v1::GetStoryOpenGraphDataRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::open_graph_def::v1::GetStoryOpenGraphDataResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetStoryOpenGraphData", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetStoryOpenGraphData"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetStoryOpenGraphData", + )); self.inner.unary(req, path, codec).await } /** * - Returns the tag's open graph data -*/ + Returns the tag's open graph data + */ pub async fn get_tag_open_graph_data( &mut self, request: impl tonic::IntoRequest< super::super::super::open_graph_def::v1::GetTagOpenGraphDataRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::open_graph_def::v1::GetTagOpenGraphDataResponse, - >, + tonic::Response, tonic::Status, > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static( "/api_service.v1.ApiService/GetTagOpenGraphData", ); let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new("api_service.v1.ApiService", "GetTagOpenGraphData"), - ); + req.extensions_mut().insert(GrpcMethod::new( + "api_service.v1.ApiService", + "GetTagOpenGraphData", + )); self.inner.unary(req, path, codec).await } } @@ -1332,12 +1152,13 @@ pub mod api_service_client { pub mod api_service_server { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] use tonic::codegen::*; - /// Generated trait containing gRPC methods that should be implemented for use with ApiServiceServer. + /// Generated trait containing gRPC methods that should be implemented for use with + /// ApiServiceServer. #[async_trait] pub trait ApiService: Send + Sync + 'static { /** * - Checks whether the user is authenticated using the token from the session cookie -*/ + Checks whether the user is authenticated using the token from the session cookie + */ async fn get_user_id( &self, request: tonic::Request, @@ -1346,32 +1167,28 @@ pub mod api_service_server { tonic::Status, >; /** * - Returns the username for a user by its ID -*/ + Returns the username for a user by its ID + */ async fn get_username( &self, - request: tonic::Request< - super::super::super::user_def::v1::GetUsernameRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Returns the profile page data for a user -*/ + Returns the profile page data for a user + */ async fn get_profile( &self, - request: tonic::Request< - super::super::super::profile_def::v1::GetProfileRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Returns the tag page data for a tag -*/ + Returns the tag page data for a tag + */ async fn get_tag( &self, request: tonic::Request, @@ -1380,8 +1197,8 @@ pub mod api_service_server { tonic::Status, >; /** * - Returns the token using its identifier -*/ + Returns the token using its identifier + */ async fn get_token( &self, request: tonic::Request, @@ -1390,20 +1207,18 @@ pub mod api_service_server { tonic::Status, >; /** * - Verifies a user's email using the provided token identifier -*/ + Verifies a user's email using the provided token identifier + */ async fn verify_email( &self, - request: tonic::Request< - super::super::super::token_def::v1::VerifyEmailRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Verifies a visitor's newsletter subscription request using the provided token identifier -*/ + Verifies a visitor's newsletter subscription request using the provided token identifier + */ async fn verify_newsletter_subscription( &self, request: tonic::Request< @@ -1416,8 +1231,18 @@ pub mod api_service_server { tonic::Status, >; /** * - Returns the user's credentials settings -*/ + Verifies a user's blog login request using the provided token identifier + */ + async fn verify_blog_login( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /** * + Returns the user's credentials settings + */ async fn get_credential_settings( &self, request: tonic::Request< @@ -1430,8 +1255,8 @@ pub mod api_service_server { tonic::Status, >; /** * - Returns the user's privacy settings -*/ + Returns the user's privacy settings + */ async fn get_privacy_settings( &self, request: tonic::Request< @@ -1444,8 +1269,8 @@ pub mod api_service_server { tonic::Status, >; /** * - Returns the user's notification settings -*/ + Returns the user's notification settings + */ async fn get_notification_settings( &self, request: tonic::Request< @@ -1458,8 +1283,8 @@ pub mod api_service_server { tonic::Status, >; /** * - Returns the user's connection settings -*/ + Returns the user's connection settings + */ async fn get_connection_settings( &self, request: tonic::Request< @@ -1472,166 +1297,134 @@ pub mod api_service_server { tonic::Status, >; /** * - Returns the user's login activity -*/ + Returns the user's login activity + */ async fn get_login_activity( &self, request: tonic::Request< super::super::super::login_activity_def::v1::GetLoginActivityRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::login_activity_def::v1::GetLoginActivityResponse, - >, + tonic::Response, tonic::Status, >; /** * - Validates a story -*/ + Validates a story + */ async fn validate_story( &self, - request: tonic::Request< - super::super::super::story_def::v1::ValidateStoryRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Returns the user's drafts details -*/ + Returns the user's drafts details + */ async fn get_drafts_info( &self, - request: tonic::Request< - super::super::super::story_def::v1::GetDraftsInfoRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Returns the user's stories details -*/ + Returns the user's stories details + */ async fn get_stories_info( &self, - request: tonic::Request< - super::super::super::story_def::v1::GetStoriesInfoRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Returns the user's contributions details -*/ + Returns the user's contributions details + */ async fn get_contributions_info( &self, request: tonic::Request< super::super::super::story_def::v1::GetContributionsInfoRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::story_def::v1::GetContributionsInfoResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the user's responses details -*/ + Returns the user's responses details + */ async fn get_responses_info( &self, - request: tonic::Request< - super::super::super::response_def::v1::GetResponsesInfoRequest, - >, + request: tonic::Request, ) -> std::result::Result< - tonic::Response< - super::super::super::response_def::v1::GetResponsesInfoResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the story's responses details -*/ + Returns the story's responses details + */ async fn get_story_responses_info( &self, request: tonic::Request< super::super::super::response_def::v1::GetStoryResponsesInfoRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::response_def::v1::GetStoryResponsesInfoResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the user's followed tag count -*/ + Returns the user's followed tag count + */ async fn get_followed_tag_count( &self, - request: tonic::Request< - super::super::super::tag_def::v1::GetFollowedTagCountRequest, - >, + request: tonic::Request, ) -> std::result::Result< - tonic::Response< - super::super::super::tag_def::v1::GetFollowedTagCountResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the user's relations details -*/ + Returns the user's relations details + */ async fn get_user_relations_info( &self, - request: tonic::Request< - super::super::super::user_def::v1::GetUserRelationsInfoRequest, - >, + request: tonic::Request, ) -> std::result::Result< - tonic::Response< - super::super::super::user_def::v1::GetUserRelationsInfoResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the user's blogs details -*/ + Returns the user's blogs details + */ async fn get_user_blogs_info( &self, - request: tonic::Request< - super::super::super::blog_def::v1::GetUserBlogsInfoRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Returns the user's block count -*/ + Returns the user's block count + */ async fn get_user_block_count( &self, - request: tonic::Request< - super::super::super::user_def::v1::GetUserBlockCountRequest, - >, + request: tonic::Request, ) -> std::result::Result< - tonic::Response< - super::super::super::user_def::v1::GetUserBlockCountResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the user's mute count -*/ + Returns the user's mute count + */ async fn get_user_mute_count( &self, - request: tonic::Request< - super::super::super::user_def::v1::GetUserMuteCountRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Returns the story's data -*/ + Returns the story's data + */ async fn get_story( &self, request: tonic::Request, @@ -1640,46 +1433,38 @@ pub mod api_service_server { tonic::Status, >; /** * - Returns the story's metadata -*/ + Returns the story's metadata + */ async fn get_story_metadata( &self, - request: tonic::Request< - super::super::super::story_def::v1::GetStoryMetadataRequest, - >, + request: tonic::Request, ) -> std::result::Result< - tonic::Response< - super::super::super::story_def::v1::GetStoryMetadataResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the comment's data -*/ + Returns the comment's data + */ async fn get_comment( &self, - request: tonic::Request< - super::super::super::comment_def::v1::GetCommentRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Creates a new draft -*/ + Creates a new draft + */ async fn create_draft( &self, - request: tonic::Request< - super::super::super::story_def::v1::CreateDraftRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Returns the blog's data -*/ + Returns the blog's data + */ async fn get_blog( &self, request: tonic::Request, @@ -1688,144 +1473,118 @@ pub mod api_service_server { tonic::Status, >; /** * - Returns the blog's archive data -*/ + Returns the blog's archive data + */ async fn get_blog_archive( &self, - request: tonic::Request< - super::super::super::blog_def::v1::GetBlogArchiveRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Returns the blog's pending story count -*/ + Returns the blog's pending story count + */ async fn get_blog_pending_story_count( &self, request: tonic::Request< super::super::super::blog_def::v1::GetBlogPendingStoryCountRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogPendingStoryCountResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the blog's published story count -*/ + Returns the blog's published story count + */ async fn get_blog_published_story_count( &self, request: tonic::Request< super::super::super::blog_def::v1::GetBlogPublishedStoryCountRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogPublishedStoryCountResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the blog's editors details -*/ + Returns the blog's editors details + */ async fn get_blog_editors_info( &self, - request: tonic::Request< - super::super::super::blog_def::v1::GetBlogEditorsInfoRequest, - >, + request: tonic::Request, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogEditorsInfoResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the blog's writers details -*/ + Returns the blog's writers details + */ async fn get_blog_writers_info( &self, - request: tonic::Request< - super::super::super::blog_def::v1::GetBlogWritersInfoRequest, - >, + request: tonic::Request, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogWritersInfoResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the blog's sitemap -*/ + Returns the blog's sitemap + */ async fn get_blog_sitemap( &self, - request: tonic::Request< - super::super::super::blog_def::v1::GetBlogSitemapRequest, - >, + request: tonic::Request, ) -> std::result::Result< tonic::Response, tonic::Status, >; /** * - Returns the blog's newsletter -*/ + Returns the blog's newsletter + */ async fn get_blog_newsletter( &self, - request: tonic::Request< - super::super::super::blog_def::v1::GetBlogNewsletterRequest, - >, + request: tonic::Request, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogNewsletterResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the blog's newsletter details -*/ + Returns the blog's newsletter details + */ async fn get_blog_newsletter_info( &self, request: tonic::Request< super::super::super::blog_def::v1::GetBlogNewsletterInfoRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::blog_def::v1::GetBlogNewsletterInfoResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the story's open graph data -*/ + Returns the story's open graph data + */ async fn get_story_open_graph_data( &self, request: tonic::Request< super::super::super::open_graph_def::v1::GetStoryOpenGraphDataRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::open_graph_def::v1::GetStoryOpenGraphDataResponse, - >, + tonic::Response, tonic::Status, >; /** * - Returns the tag's open graph data -*/ + Returns the tag's open graph data + */ async fn get_tag_open_graph_data( &self, request: tonic::Request< super::super::super::open_graph_def::v1::GetTagOpenGraphDataRequest, >, ) -> std::result::Result< - tonic::Response< - super::super::super::open_graph_def::v1::GetTagOpenGraphDataResponse, - >, + tonic::Response, tonic::Status, >; } /** Service definition -*/ + */ #[derive(Debug)] pub struct ApiServiceServer { inner: _Inner, @@ -1849,10 +1608,7 @@ pub mod api_service_server { max_encoding_message_size: None, } } - pub fn with_interceptor( - inner: T, - interceptor: F, - ) -> InterceptedService + pub fn with_interceptor(inner: T, interceptor: F) -> InterceptedService where F: tonic::service::Interceptor, { @@ -1908,16 +1664,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetUserId" => { #[allow(non_camel_case_types)] struct GetUserIdSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::user_def::v1::GetUserIdRequest, - > for GetUserIdSvc { + impl + tonic::server::UnaryService< + super::super::super::user_def::v1::GetUserIdRequest, + > for GetUserIdSvc + { type Response = super::super::super::user_def::v1::GetUserIdResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -1955,16 +1708,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetUsername" => { #[allow(non_camel_case_types)] struct GetUsernameSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::user_def::v1::GetUsernameRequest, - > for GetUsernameSvc { + impl + tonic::server::UnaryService< + super::super::super::user_def::v1::GetUsernameRequest, + > for GetUsernameSvc + { type Response = super::super::super::user_def::v1::GetUsernameResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -1972,9 +1722,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_username(request).await - }; + let fut = async move { (*inner).get_username(request).await }; Box::pin(fut) } } @@ -2004,16 +1752,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetProfile" => { #[allow(non_camel_case_types)] struct GetProfileSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::profile_def::v1::GetProfileRequest, - > for GetProfileSvc { + impl + tonic::server::UnaryService< + super::super::super::profile_def::v1::GetProfileRequest, + > for GetProfileSvc + { type Response = super::super::super::profile_def::v1::GetProfileResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2051,16 +1796,12 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetTag" => { #[allow(non_camel_case_types)] struct GetTagSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::tag_def::v1::GetTagRequest, - > for GetTagSvc { + impl + tonic::server::UnaryService + for GetTagSvc + { type Response = super::super::super::tag_def::v1::GetTagResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2098,16 +1839,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetToken" => { #[allow(non_camel_case_types)] struct GetTokenSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::token_def::v1::GetTokenRequest, - > for GetTokenSvc { + impl + tonic::server::UnaryService< + super::super::super::token_def::v1::GetTokenRequest, + > for GetTokenSvc + { type Response = super::super::super::token_def::v1::GetTokenResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2145,25 +1883,66 @@ pub mod api_service_server { "/api_service.v1.ApiService/VerifyEmail" => { #[allow(non_camel_case_types)] struct VerifyEmailSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::token_def::v1::VerifyEmailRequest, - > for VerifyEmailSvc { + impl + tonic::server::UnaryService< + super::super::super::token_def::v1::VerifyEmailRequest, + > for VerifyEmailSvc + { type Response = super::super::super::token_def::v1::VerifyEmailResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; + fn call( + &mut self, + request: tonic::Request< + super::super::super::token_def::v1::VerifyEmailRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { (*inner).verify_email(request).await }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = VerifyEmailSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/api_service.v1.ApiService/VerifyNewsletterSubscription" => { + #[allow(non_camel_case_types)] + struct VerifyNewsletterSubscriptionSvc(pub Arc); + impl + tonic::server::UnaryService< + super::super::super::token_def::v1::VerifyNewsletterSubscriptionRequest, + > for VerifyNewsletterSubscriptionSvc + { + type Response = super::super::super::token_def::v1::VerifyNewsletterSubscriptionResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< - super::super::super::token_def::v1::VerifyEmailRequest, + super::super::super::token_def::v1::VerifyNewsletterSubscriptionRequest, >, ) -> Self::Future { let inner = Arc::clone(&self.0); let fut = async move { - (*inner).verify_email(request).await + (*inner).verify_newsletter_subscription(request).await }; Box::pin(fut) } @@ -2175,7 +1954,7 @@ pub mod api_service_server { let inner = self.inner.clone(); let fut = async move { let inner = inner.0; - let method = VerifyEmailSvc(inner); + let method = VerifyNewsletterSubscriptionSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) .apply_compression_config( @@ -2191,29 +1970,24 @@ pub mod api_service_server { }; Box::pin(fut) } - "/api_service.v1.ApiService/VerifyNewsletterSubscription" => { + "/api_service.v1.ApiService/VerifyBlogLogin" => { #[allow(non_camel_case_types)] - struct VerifyNewsletterSubscriptionSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::token_def::v1::VerifyNewsletterSubscriptionRequest, - > for VerifyNewsletterSubscriptionSvc { - type Response = super::super::super::token_def::v1::VerifyNewsletterSubscriptionResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + struct VerifyBlogLoginSvc(pub Arc); + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::VerifyBlogLoginRequest, + > for VerifyBlogLoginSvc + { + type Response = super::super::super::blog_def::v1::VerifyBlogLoginResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< - super::super::super::token_def::v1::VerifyNewsletterSubscriptionRequest, + super::super::super::blog_def::v1::VerifyBlogLoginRequest, >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).verify_newsletter_subscription(request).await - }; + let fut = async move { (*inner).verify_blog_login(request).await }; Box::pin(fut) } } @@ -2224,7 +1998,7 @@ pub mod api_service_server { let inner = self.inner.clone(); let fut = async move { let inner = inner.0; - let method = VerifyNewsletterSubscriptionSvc(inner); + let method = VerifyBlogLoginSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) .apply_compression_config( @@ -2439,16 +2213,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetLoginActivity" => { #[allow(non_camel_case_types)] struct GetLoginActivitySvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::login_activity_def::v1::GetLoginActivityRequest, - > for GetLoginActivitySvc { - type Response = super::super::super::login_activity_def::v1::GetLoginActivityResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::login_activity_def::v1::GetLoginActivityRequest, + > for GetLoginActivitySvc + { + type Response = + super::super::super::login_activity_def::v1::GetLoginActivityResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2456,9 +2228,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_login_activity(request).await - }; + let fut = async move { (*inner).get_login_activity(request).await }; Box::pin(fut) } } @@ -2488,16 +2258,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/ValidateStory" => { #[allow(non_camel_case_types)] struct ValidateStorySvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::story_def::v1::ValidateStoryRequest, - > for ValidateStorySvc { + impl + tonic::server::UnaryService< + super::super::super::story_def::v1::ValidateStoryRequest, + > for ValidateStorySvc + { type Response = super::super::super::story_def::v1::ValidateStoryResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2505,9 +2272,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).validate_story(request).await - }; + let fut = async move { (*inner).validate_story(request).await }; Box::pin(fut) } } @@ -2537,16 +2302,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetDraftsInfo" => { #[allow(non_camel_case_types)] struct GetDraftsInfoSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::story_def::v1::GetDraftsInfoRequest, - > for GetDraftsInfoSvc { + impl + tonic::server::UnaryService< + super::super::super::story_def::v1::GetDraftsInfoRequest, + > for GetDraftsInfoSvc + { type Response = super::super::super::story_def::v1::GetDraftsInfoResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2554,9 +2316,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_drafts_info(request).await - }; + let fut = async move { (*inner).get_drafts_info(request).await }; Box::pin(fut) } } @@ -2586,16 +2346,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetStoriesInfo" => { #[allow(non_camel_case_types)] struct GetStoriesInfoSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::story_def::v1::GetStoriesInfoRequest, - > for GetStoriesInfoSvc { + impl + tonic::server::UnaryService< + super::super::super::story_def::v1::GetStoriesInfoRequest, + > for GetStoriesInfoSvc + { type Response = super::super::super::story_def::v1::GetStoriesInfoResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2603,9 +2360,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_stories_info(request).await - }; + let fut = async move { (*inner).get_stories_info(request).await }; Box::pin(fut) } } @@ -2635,16 +2390,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetContributionsInfo" => { #[allow(non_camel_case_types)] struct GetContributionsInfoSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::story_def::v1::GetContributionsInfoRequest, - > for GetContributionsInfoSvc { - type Response = super::super::super::story_def::v1::GetContributionsInfoResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::story_def::v1::GetContributionsInfoRequest, + > for GetContributionsInfoSvc + { + type Response = + super::super::super::story_def::v1::GetContributionsInfoResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2652,9 +2405,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_contributions_info(request).await - }; + let fut = async move { (*inner).get_contributions_info(request).await }; Box::pin(fut) } } @@ -2684,16 +2435,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetResponsesInfo" => { #[allow(non_camel_case_types)] struct GetResponsesInfoSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::response_def::v1::GetResponsesInfoRequest, - > for GetResponsesInfoSvc { - type Response = super::super::super::response_def::v1::GetResponsesInfoResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::response_def::v1::GetResponsesInfoRequest, + > for GetResponsesInfoSvc + { + type Response = + super::super::super::response_def::v1::GetResponsesInfoResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2701,9 +2450,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_responses_info(request).await - }; + let fut = async move { (*inner).get_responses_info(request).await }; Box::pin(fut) } } @@ -2733,16 +2480,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetStoryResponsesInfo" => { #[allow(non_camel_case_types)] struct GetStoryResponsesInfoSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::response_def::v1::GetStoryResponsesInfoRequest, - > for GetStoryResponsesInfoSvc { - type Response = super::super::super::response_def::v1::GetStoryResponsesInfoResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::response_def::v1::GetStoryResponsesInfoRequest, + > for GetStoryResponsesInfoSvc + { + type Response = + super::super::super::response_def::v1::GetStoryResponsesInfoResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2750,9 +2495,8 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_story_responses_info(request).await - }; + let fut = + async move { (*inner).get_story_responses_info(request).await }; Box::pin(fut) } } @@ -2782,16 +2526,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetFollowedTagCount" => { #[allow(non_camel_case_types)] struct GetFollowedTagCountSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::tag_def::v1::GetFollowedTagCountRequest, - > for GetFollowedTagCountSvc { - type Response = super::super::super::tag_def::v1::GetFollowedTagCountResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::tag_def::v1::GetFollowedTagCountRequest, + > for GetFollowedTagCountSvc + { + type Response = + super::super::super::tag_def::v1::GetFollowedTagCountResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2799,9 +2541,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_followed_tag_count(request).await - }; + let fut = async move { (*inner).get_followed_tag_count(request).await }; Box::pin(fut) } } @@ -2831,16 +2571,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetUserRelationsInfo" => { #[allow(non_camel_case_types)] struct GetUserRelationsInfoSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::user_def::v1::GetUserRelationsInfoRequest, - > for GetUserRelationsInfoSvc { - type Response = super::super::super::user_def::v1::GetUserRelationsInfoResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::user_def::v1::GetUserRelationsInfoRequest, + > for GetUserRelationsInfoSvc + { + type Response = + super::super::super::user_def::v1::GetUserRelationsInfoResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2848,9 +2586,8 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_user_relations_info(request).await - }; + let fut = + async move { (*inner).get_user_relations_info(request).await }; Box::pin(fut) } } @@ -2880,16 +2617,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetUserBlogsInfo" => { #[allow(non_camel_case_types)] struct GetUserBlogsInfoSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::blog_def::v1::GetUserBlogsInfoRequest, - > for GetUserBlogsInfoSvc { + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::GetUserBlogsInfoRequest, + > for GetUserBlogsInfoSvc + { type Response = super::super::super::blog_def::v1::GetUserBlogsInfoResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2897,9 +2631,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_user_blogs_info(request).await - }; + let fut = async move { (*inner).get_user_blogs_info(request).await }; Box::pin(fut) } } @@ -2929,16 +2661,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetUserBlockCount" => { #[allow(non_camel_case_types)] struct GetUserBlockCountSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::user_def::v1::GetUserBlockCountRequest, - > for GetUserBlockCountSvc { - type Response = super::super::super::user_def::v1::GetUserBlockCountResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::user_def::v1::GetUserBlockCountRequest, + > for GetUserBlockCountSvc + { + type Response = + super::super::super::user_def::v1::GetUserBlockCountResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2946,9 +2676,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_user_block_count(request).await - }; + let fut = async move { (*inner).get_user_block_count(request).await }; Box::pin(fut) } } @@ -2978,16 +2706,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetUserMuteCount" => { #[allow(non_camel_case_types)] struct GetUserMuteCountSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::user_def::v1::GetUserMuteCountRequest, - > for GetUserMuteCountSvc { + impl + tonic::server::UnaryService< + super::super::super::user_def::v1::GetUserMuteCountRequest, + > for GetUserMuteCountSvc + { type Response = super::super::super::user_def::v1::GetUserMuteCountResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -2995,9 +2720,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_user_mute_count(request).await - }; + let fut = async move { (*inner).get_user_mute_count(request).await }; Box::pin(fut) } } @@ -3027,16 +2750,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetStory" => { #[allow(non_camel_case_types)] struct GetStorySvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::story_def::v1::GetStoryRequest, - > for GetStorySvc { + impl + tonic::server::UnaryService< + super::super::super::story_def::v1::GetStoryRequest, + > for GetStorySvc + { type Response = super::super::super::story_def::v1::GetStoryResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3074,16 +2794,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetStoryMetadata" => { #[allow(non_camel_case_types)] struct GetStoryMetadataSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::story_def::v1::GetStoryMetadataRequest, - > for GetStoryMetadataSvc { - type Response = super::super::super::story_def::v1::GetStoryMetadataResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::story_def::v1::GetStoryMetadataRequest, + > for GetStoryMetadataSvc + { + type Response = + super::super::super::story_def::v1::GetStoryMetadataResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3091,9 +2809,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_story_metadata(request).await - }; + let fut = async move { (*inner).get_story_metadata(request).await }; Box::pin(fut) } } @@ -3123,16 +2839,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetComment" => { #[allow(non_camel_case_types)] struct GetCommentSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::comment_def::v1::GetCommentRequest, - > for GetCommentSvc { + impl + tonic::server::UnaryService< + super::super::super::comment_def::v1::GetCommentRequest, + > for GetCommentSvc + { type Response = super::super::super::comment_def::v1::GetCommentResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3170,16 +2883,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/CreateDraft" => { #[allow(non_camel_case_types)] struct CreateDraftSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::story_def::v1::CreateDraftRequest, - > for CreateDraftSvc { + impl + tonic::server::UnaryService< + super::super::super::story_def::v1::CreateDraftRequest, + > for CreateDraftSvc + { type Response = super::super::super::story_def::v1::CreateDraftResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3187,9 +2897,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).create_draft(request).await - }; + let fut = async move { (*inner).create_draft(request).await }; Box::pin(fut) } } @@ -3219,16 +2927,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetBlog" => { #[allow(non_camel_case_types)] struct GetBlogSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::blog_def::v1::GetBlogRequest, - > for GetBlogSvc { + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::GetBlogRequest, + > for GetBlogSvc + { type Response = super::super::super::blog_def::v1::GetBlogResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3266,16 +2971,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetBlogArchive" => { #[allow(non_camel_case_types)] struct GetBlogArchiveSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::blog_def::v1::GetBlogArchiveRequest, - > for GetBlogArchiveSvc { + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::GetBlogArchiveRequest, + > for GetBlogArchiveSvc + { type Response = super::super::super::blog_def::v1::GetBlogArchiveResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3283,9 +2985,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_blog_archive(request).await - }; + let fut = async move { (*inner).get_blog_archive(request).await }; Box::pin(fut) } } @@ -3315,16 +3015,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetBlogPendingStoryCount" => { #[allow(non_camel_case_types)] struct GetBlogPendingStoryCountSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::blog_def::v1::GetBlogPendingStoryCountRequest, - > for GetBlogPendingStoryCountSvc { - type Response = super::super::super::blog_def::v1::GetBlogPendingStoryCountResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::GetBlogPendingStoryCountRequest, + > for GetBlogPendingStoryCountSvc + { + type Response = + super::super::super::blog_def::v1::GetBlogPendingStoryCountResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3332,9 +3030,8 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_blog_pending_story_count(request).await - }; + let fut = + async move { (*inner).get_blog_pending_story_count(request).await }; Box::pin(fut) } } @@ -3364,16 +3061,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetBlogPublishedStoryCount" => { #[allow(non_camel_case_types)] struct GetBlogPublishedStoryCountSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::blog_def::v1::GetBlogPublishedStoryCountRequest, - > for GetBlogPublishedStoryCountSvc { - type Response = super::super::super::blog_def::v1::GetBlogPublishedStoryCountResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::GetBlogPublishedStoryCountRequest, + > for GetBlogPublishedStoryCountSvc + { + type Response = + super::super::super::blog_def::v1::GetBlogPublishedStoryCountResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3413,16 +3108,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetBlogEditorsInfo" => { #[allow(non_camel_case_types)] struct GetBlogEditorsInfoSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::blog_def::v1::GetBlogEditorsInfoRequest, - > for GetBlogEditorsInfoSvc { - type Response = super::super::super::blog_def::v1::GetBlogEditorsInfoResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::GetBlogEditorsInfoRequest, + > for GetBlogEditorsInfoSvc + { + type Response = + super::super::super::blog_def::v1::GetBlogEditorsInfoResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3430,9 +3123,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_blog_editors_info(request).await - }; + let fut = async move { (*inner).get_blog_editors_info(request).await }; Box::pin(fut) } } @@ -3462,16 +3153,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetBlogWritersInfo" => { #[allow(non_camel_case_types)] struct GetBlogWritersInfoSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::blog_def::v1::GetBlogWritersInfoRequest, - > for GetBlogWritersInfoSvc { - type Response = super::super::super::blog_def::v1::GetBlogWritersInfoResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::GetBlogWritersInfoRequest, + > for GetBlogWritersInfoSvc + { + type Response = + super::super::super::blog_def::v1::GetBlogWritersInfoResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3479,9 +3168,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_blog_writers_info(request).await - }; + let fut = async move { (*inner).get_blog_writers_info(request).await }; Box::pin(fut) } } @@ -3511,16 +3198,13 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetBlogSitemap" => { #[allow(non_camel_case_types)] struct GetBlogSitemapSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::blog_def::v1::GetBlogSitemapRequest, - > for GetBlogSitemapSvc { + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::GetBlogSitemapRequest, + > for GetBlogSitemapSvc + { type Response = super::super::super::blog_def::v1::GetBlogSitemapResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3528,9 +3212,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_blog_sitemap(request).await - }; + let fut = async move { (*inner).get_blog_sitemap(request).await }; Box::pin(fut) } } @@ -3560,16 +3242,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetBlogNewsletter" => { #[allow(non_camel_case_types)] struct GetBlogNewsletterSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::blog_def::v1::GetBlogNewsletterRequest, - > for GetBlogNewsletterSvc { - type Response = super::super::super::blog_def::v1::GetBlogNewsletterResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::GetBlogNewsletterRequest, + > for GetBlogNewsletterSvc + { + type Response = + super::super::super::blog_def::v1::GetBlogNewsletterResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3577,9 +3257,7 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_blog_newsletter(request).await - }; + let fut = async move { (*inner).get_blog_newsletter(request).await }; Box::pin(fut) } } @@ -3609,16 +3287,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetBlogNewsletterInfo" => { #[allow(non_camel_case_types)] struct GetBlogNewsletterInfoSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::blog_def::v1::GetBlogNewsletterInfoRequest, - > for GetBlogNewsletterInfoSvc { - type Response = super::super::super::blog_def::v1::GetBlogNewsletterInfoResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::blog_def::v1::GetBlogNewsletterInfoRequest, + > for GetBlogNewsletterInfoSvc + { + type Response = + super::super::super::blog_def::v1::GetBlogNewsletterInfoResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3626,9 +3302,8 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_blog_newsletter_info(request).await - }; + let fut = + async move { (*inner).get_blog_newsletter_info(request).await }; Box::pin(fut) } } @@ -3658,16 +3333,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetStoryOpenGraphData" => { #[allow(non_camel_case_types)] struct GetStoryOpenGraphDataSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::open_graph_def::v1::GetStoryOpenGraphDataRequest, - > for GetStoryOpenGraphDataSvc { - type Response = super::super::super::open_graph_def::v1::GetStoryOpenGraphDataResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::open_graph_def::v1::GetStoryOpenGraphDataRequest, + > for GetStoryOpenGraphDataSvc + { + type Response = + super::super::super::open_graph_def::v1::GetStoryOpenGraphDataResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3675,9 +3348,8 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_story_open_graph_data(request).await - }; + let fut = + async move { (*inner).get_story_open_graph_data(request).await }; Box::pin(fut) } } @@ -3707,16 +3379,14 @@ pub mod api_service_server { "/api_service.v1.ApiService/GetTagOpenGraphData" => { #[allow(non_camel_case_types)] struct GetTagOpenGraphDataSvc(pub Arc); - impl< - T: ApiService, - > tonic::server::UnaryService< - super::super::super::open_graph_def::v1::GetTagOpenGraphDataRequest, - > for GetTagOpenGraphDataSvc { - type Response = super::super::super::open_graph_def::v1::GetTagOpenGraphDataResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + impl + tonic::server::UnaryService< + super::super::super::open_graph_def::v1::GetTagOpenGraphDataRequest, + > for GetTagOpenGraphDataSvc + { + type Response = + super::super::super::open_graph_def::v1::GetTagOpenGraphDataResponse; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request< @@ -3724,9 +3394,8 @@ pub mod api_service_server { >, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - (*inner).get_tag_open_graph_data(request).await - }; + let fut = + async move { (*inner).get_tag_open_graph_data(request).await }; Box::pin(fut) } } @@ -3753,18 +3422,14 @@ pub mod api_service_server { }; Box::pin(fut) } - _ => { - Box::pin(async move { - Ok( - http::Response::builder() - .status(200) - .header("grpc-status", "12") - .header("content-type", "application/grpc") - .body(empty_body()) - .unwrap(), - ) - }) - } + _ => Box::pin(async move { + Ok(http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap()) + }), } } } diff --git a/src/proto/blog_def.v1.rs b/src/proto/blog_def.v1.rs index ea32973..00177bc 100644 --- a/src/proto/blog_def.v1.rs +++ b/src/proto/blog_def.v1.rs @@ -4,17 +4,17 @@ #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct BareBlog { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub id: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, tag = "2")] pub slug: ::prost::alloc::string::String, - #[prost(string, optional, tag="3")] + #[prost(string, optional, tag = "3")] pub domain: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, tag="4")] + #[prost(string, tag = "4")] pub name: ::prost::alloc::string::String, - #[prost(string, optional, tag="5")] + #[prost(string, optional, tag = "5")] pub logo_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="6")] + #[prost(string, optional, tag = "6")] pub logo_hex: ::core::option::Option<::prost::alloc::string::String>, } // Main blog request @@ -22,139 +22,139 @@ pub struct BareBlog { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct LeftSidebarItem { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub id: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, tag = "2")] pub name: ::prost::alloc::string::String, - #[prost(string, optional, tag="3")] + #[prost(string, optional, tag = "3")] pub icon: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, tag="4")] + #[prost(string, tag = "4")] pub target: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RightSidebarItem { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub id: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, tag = "2")] pub primary_text: ::prost::alloc::string::String, - #[prost(string, optional, tag="3")] + #[prost(string, optional, tag = "3")] pub secondary_text: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="4")] + #[prost(string, optional, tag = "4")] pub icon: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, tag="5")] + #[prost(string, tag = "5")] pub target: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogRequest { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub identifier: ::prost::alloc::string::String, - #[prost(string, optional, tag="2")] + #[prost(string, optional, tag = "2")] pub current_user_id: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogResponse { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub id: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, tag = "2")] pub name: ::prost::alloc::string::String, - #[prost(string, tag="3")] + #[prost(string, tag = "3")] pub slug: ::prost::alloc::string::String, - #[prost(string, optional, tag="4")] + #[prost(string, optional, tag = "4")] pub description: ::core::option::Option<::prost::alloc::string::String>, /// Banner - #[prost(string, optional, tag="5")] + #[prost(string, optional, tag = "5")] pub banner_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="6")] + #[prost(string, optional, tag = "6")] pub banner_hex: ::core::option::Option<::prost::alloc::string::String>, /// Logo - #[prost(string, optional, tag="7")] + #[prost(string, optional, tag = "7")] pub logo_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="8")] + #[prost(string, optional, tag = "8")] pub logo_hex: ::core::option::Option<::prost::alloc::string::String>, /// Newsletter splash - #[prost(string, optional, tag="9")] + #[prost(string, optional, tag = "9")] pub newsletter_splash_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="10")] + #[prost(string, optional, tag = "10")] pub newsletter_splash_hex: ::core::option::Option<::prost::alloc::string::String>, /// Mark - #[prost(string, optional, tag="11")] + #[prost(string, optional, tag = "11")] pub mark_light: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="12")] + #[prost(string, optional, tag = "12")] pub mark_dark: ::core::option::Option<::prost::alloc::string::String>, /// Font - #[prost(string, optional, tag="13")] + #[prost(string, optional, tag = "13")] pub font_code: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="14")] + #[prost(string, optional, tag = "14")] pub font_primary: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="15")] + #[prost(string, optional, tag = "15")] pub font_secondary: ::core::option::Option<::prost::alloc::string::String>, /// Theme - #[prost(string, optional, tag="16")] + #[prost(string, optional, tag = "16")] pub default_theme: ::core::option::Option<::prost::alloc::string::String>, - #[prost(bool, tag="17")] + #[prost(bool, tag = "17")] pub force_theme: bool, - #[prost(string, optional, tag="18")] + #[prost(string, optional, tag = "18")] pub favicon: ::core::option::Option<::prost::alloc::string::String>, - #[prost(bool, tag="19")] + #[prost(bool, tag = "19")] pub hide_storiny_branding: bool, - #[prost(bool, tag="20")] + #[prost(bool, tag = "20")] pub is_homepage_large_layout: bool, - #[prost(bool, tag="21")] + #[prost(bool, tag = "21")] pub is_story_minimal_layout: bool, /// SEO - #[prost(string, optional, tag="22")] + #[prost(string, optional, tag = "22")] pub seo_description: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="23")] + #[prost(string, optional, tag = "23")] pub seo_title: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="24")] + #[prost(string, optional, tag = "24")] pub preview_image: ::core::option::Option<::prost::alloc::string::String>, /// Boolean flags - #[prost(bool, tag="25")] + #[prost(bool, tag = "25")] pub is_following: bool, - #[prost(bool, tag="26")] + #[prost(bool, tag = "26")] pub is_owner: bool, - #[prost(bool, tag="27")] + #[prost(bool, tag = "27")] pub is_editor: bool, - #[prost(bool, tag="28")] + #[prost(bool, tag = "28")] pub is_writer: bool, - #[prost(bool, tag="29")] + #[prost(bool, tag = "29")] pub is_external: bool, - #[prost(bool, tag="30")] + #[prost(bool, tag = "30")] pub has_plus_features: bool, /// Connections - #[prost(string, optional, tag="31")] + #[prost(string, optional, tag = "31")] pub website_url: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="32")] + #[prost(string, optional, tag = "32")] pub public_email: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="33")] + #[prost(string, optional, tag = "33")] pub github_url: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="34")] + #[prost(string, optional, tag = "34")] pub instagram_url: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="35")] + #[prost(string, optional, tag = "35")] pub linkedin_url: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="36")] + #[prost(string, optional, tag = "36")] pub youtube_url: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="37")] + #[prost(string, optional, tag = "37")] pub twitter_url: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="38")] + #[prost(string, optional, tag = "38")] pub twitch_url: ::core::option::Option<::prost::alloc::string::String>, /// Other props - #[prost(string, optional, tag="39")] + #[prost(string, optional, tag = "39")] pub domain: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, tag="40")] + #[prost(string, tag = "40")] pub created_at: ::prost::alloc::string::String, - #[prost(string, tag="41")] + #[prost(string, tag = "41")] pub category: ::prost::alloc::string::String, - #[prost(string, tag="42")] + #[prost(string, tag = "42")] pub user_id: ::prost::alloc::string::String, - #[prost(string, tag="43")] + #[prost(string, tag = "43")] pub rsb_items_label: ::prost::alloc::string::String, - #[prost(message, repeated, tag="44")] + #[prost(message, repeated, tag = "44")] pub lsb_items: ::prost::alloc::vec::Vec, - #[prost(message, repeated, tag="45")] + #[prost(message, repeated, tag = "45")] pub rsb_items: ::prost::alloc::vec::Vec, } // Blog archive request @@ -162,23 +162,23 @@ pub struct GetBlogResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ArchiveTimeline { - #[prost(uint32, tag="1")] + #[prost(uint32, tag = "1")] pub year: u32, - #[prost(uint32, repeated, tag="2")] + #[prost(uint32, repeated, tag = "2")] pub active_months: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogArchiveRequest { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub identifier: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogArchiveResponse { - #[prost(uint32, tag="1")] + #[prost(uint32, tag = "1")] pub story_count: u32, - #[prost(message, repeated, tag="2")] + #[prost(message, repeated, tag = "2")] pub timeline: ::prost::alloc::vec::Vec, } // Get user blogs info @@ -186,17 +186,17 @@ pub struct GetBlogArchiveResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetUserBlogsInfoRequest { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub user_id: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetUserBlogsInfoResponse { - #[prost(uint32, tag="1")] + #[prost(uint32, tag = "1")] pub blog_count: u32, - #[prost(uint32, tag="2")] + #[prost(uint32, tag = "2")] pub pending_blog_request_count: u32, - #[prost(bool, tag="3")] + #[prost(bool, tag = "3")] pub can_create_blog: bool, } // Blog pending stories info @@ -204,13 +204,13 @@ pub struct GetUserBlogsInfoResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogPendingStoryCountRequest { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub identifier: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogPendingStoryCountResponse { - #[prost(uint32, tag="1")] + #[prost(uint32, tag = "1")] pub pending_story_count: u32, } // Blog published stories info @@ -218,13 +218,13 @@ pub struct GetBlogPendingStoryCountResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogPublishedStoryCountRequest { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub identifier: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogPublishedStoryCountResponse { - #[prost(uint32, tag="1")] + #[prost(uint32, tag = "1")] pub published_story_count: u32, } // Blog editors info @@ -232,15 +232,15 @@ pub struct GetBlogPublishedStoryCountResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogEditorsInfoRequest { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub identifier: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogEditorsInfoResponse { - #[prost(uint32, tag="1")] + #[prost(uint32, tag = "1")] pub editor_count: u32, - #[prost(uint32, tag="2")] + #[prost(uint32, tag = "2")] pub pending_editor_request_count: u32, } // Blog writers info @@ -248,15 +248,15 @@ pub struct GetBlogEditorsInfoResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogWritersInfoRequest { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub identifier: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogWritersInfoResponse { - #[prost(uint32, tag="1")] + #[prost(uint32, tag = "1")] pub writer_count: u32, - #[prost(uint32, tag="2")] + #[prost(uint32, tag = "2")] pub pending_writer_request_count: u32, } // Blog newsletter info @@ -264,13 +264,13 @@ pub struct GetBlogWritersInfoResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogNewsletterInfoRequest { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub identifier: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogNewsletterInfoResponse { - #[prost(uint32, tag="1")] + #[prost(uint32, tag = "1")] pub subscriber_count: u32, } // Blog sitemap @@ -278,13 +278,13 @@ pub struct GetBlogNewsletterInfoResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogSitemapRequest { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub identifier: ::prost::alloc::string::String, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogSitemapResponse { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub content: ::prost::alloc::string::String, } // Blog newsletter @@ -292,27 +292,49 @@ pub struct GetBlogSitemapResponse { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogNewsletterRequest { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub identifier: ::prost::alloc::string::String, - #[prost(string, optional, tag="2")] + #[prost(string, optional, tag = "2")] pub current_user_id: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetBlogNewsletterResponse { - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub id: ::prost::alloc::string::String, - #[prost(string, tag="2")] + #[prost(string, tag = "2")] pub name: ::prost::alloc::string::String, - #[prost(string, optional, tag="3")] + #[prost(string, optional, tag = "3")] pub description: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="4")] + #[prost(string, optional, tag = "4")] pub newsletter_splash_id: ::core::option::Option<::prost::alloc::string::String>, - #[prost(string, optional, tag="5")] + #[prost(string, optional, tag = "5")] pub newsletter_splash_hex: ::core::option::Option<::prost::alloc::string::String>, - #[prost(message, optional, tag="6")] + #[prost(message, optional, tag = "6")] pub user: ::core::option::Option, - #[prost(bool, tag="7")] + #[prost(bool, tag = "7")] pub is_subscribed: bool, } +// Blog login verification + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VerifyBlogLoginRequest { + #[prost(string, tag = "1")] + pub blog_identifier: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub token: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub host: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VerifyBlogLoginResponse { + #[prost(bool, tag = "1")] + pub is_token_valid: bool, + #[prost(string, optional, tag = "2")] + pub cookie_value: ::core::option::Option<::prost::alloc::string::String>, + #[prost(bool, optional, tag = "3")] + pub is_persistent_cookie: ::core::option::Option, +} // @@protoc_insertion_point(module) diff --git a/src/proto/blog_def.v1.serde.rs b/src/proto/blog_def.v1.serde.rs index 323a5a8..32f8b2f 100644 --- a/src/proto/blog_def.v1.serde.rs +++ b/src/proto/blog_def.v1.serde.rs @@ -29,11 +29,7 @@ impl<'de> serde::Deserialize<'de> for ArchiveTimeline { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "year", - "active_months", - "activeMonths", - ]; + const FIELDS: &[&str] = &["year", "active_months", "activeMonths"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -50,7 +46,10 @@ impl<'de> serde::Deserialize<'de> for ArchiveTimeline { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -78,8 +77,8 @@ impl<'de> serde::Deserialize<'de> for ArchiveTimeline { } fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + where + V: serde::de::MapAccess<'de>, { let mut year__ = None; let mut active_months__ = None; @@ -89,18 +88,21 @@ impl<'de> serde::Deserialize<'de> for ArchiveTimeline { if year__.is_some() { return Err(serde::de::Error::duplicate_field("year")); } - year__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + year__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } GeneratedField::ActiveMonths => { if active_months__.is_some() { return Err(serde::de::Error::duplicate_field("activeMonths")); } - active_months__ = - Some(map.next_value::>>()? - .into_iter().map(|x| x.0).collect()) - ; + active_months__ = Some( + map.next_value::>>()? + .into_iter() + .map(|x| x.0) + .collect(), + ); } } } @@ -168,14 +170,7 @@ impl<'de> serde::Deserialize<'de> for BareBlog { D: serde::Deserializer<'de>, { const FIELDS: &[&str] = &[ - "id", - "slug", - "domain", - "name", - "logo_id", - "logoId", - "logo_hex", - "logoHex", + "id", "slug", "domain", "name", "logo_id", "logoId", "logo_hex", "logoHex", ]; #[allow(clippy::enum_variant_names)] @@ -197,7 +192,10 @@ impl<'de> serde::Deserialize<'de> for BareBlog { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -229,8 +227,8 @@ impl<'de> serde::Deserialize<'de> for BareBlog { } fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + where + V: serde::de::MapAccess<'de>, { let mut id__ = None; let mut slug__ = None; @@ -302,7 +300,8 @@ impl serde::Serialize for GetBlogArchiveRequest { if !self.identifier.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogArchiveRequest", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogArchiveRequest", len)?; if !self.identifier.is_empty() { struct_ser.serialize_field("identifier", &self.identifier)?; } @@ -315,9 +314,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogArchiveRequest { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "identifier", - ]; + const FIELDS: &[&str] = &["identifier"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -333,7 +330,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogArchiveRequest { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -359,9 +359,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogArchiveRequest { formatter.write_str("struct blog_def.v1.GetBlogArchiveRequest") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut identifier__ = None; while let Some(k) = map.next_key()? { @@ -379,7 +382,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogArchiveRequest { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogArchiveRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogArchiveRequest", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogArchiveResponse { @@ -396,7 +403,8 @@ impl serde::Serialize for GetBlogArchiveResponse { if !self.timeline.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogArchiveResponse", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogArchiveResponse", len)?; if self.story_count != 0 { struct_ser.serialize_field("storyCount", &self.story_count)?; } @@ -412,11 +420,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogArchiveResponse { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "story_count", - "storyCount", - "timeline", - ]; + const FIELDS: &[&str] = &["story_count", "storyCount", "timeline"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -433,7 +437,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogArchiveResponse { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -460,9 +467,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogArchiveResponse { formatter.write_str("struct blog_def.v1.GetBlogArchiveResponse") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut story_count__ = None; let mut timeline__ = None; @@ -472,9 +482,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogArchiveResponse { if story_count__.is_some() { return Err(serde::de::Error::duplicate_field("storyCount")); } - story_count__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + story_count__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } GeneratedField::Timeline => { if timeline__.is_some() { @@ -490,7 +501,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogArchiveResponse { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogArchiveResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogArchiveResponse", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogEditorsInfoRequest { @@ -504,7 +519,8 @@ impl serde::Serialize for GetBlogEditorsInfoRequest { if !self.identifier.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogEditorsInfoRequest", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogEditorsInfoRequest", len)?; if !self.identifier.is_empty() { struct_ser.serialize_field("identifier", &self.identifier)?; } @@ -517,9 +533,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogEditorsInfoRequest { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "identifier", - ]; + const FIELDS: &[&str] = &["identifier"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -535,7 +549,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogEditorsInfoRequest { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -561,9 +578,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogEditorsInfoRequest { formatter.write_str("struct blog_def.v1.GetBlogEditorsInfoRequest") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut identifier__ = None; while let Some(k) = map.next_key()? { @@ -581,7 +601,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogEditorsInfoRequest { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogEditorsInfoRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogEditorsInfoRequest", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogEditorsInfoResponse { @@ -598,12 +622,16 @@ impl serde::Serialize for GetBlogEditorsInfoResponse { if self.pending_editor_request_count != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogEditorsInfoResponse", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogEditorsInfoResponse", len)?; if self.editor_count != 0 { struct_ser.serialize_field("editorCount", &self.editor_count)?; } if self.pending_editor_request_count != 0 { - struct_ser.serialize_field("pendingEditorRequestCount", &self.pending_editor_request_count)?; + struct_ser.serialize_field( + "pendingEditorRequestCount", + &self.pending_editor_request_count, + )?; } struct_ser.end() } @@ -636,7 +664,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogEditorsInfoResponse { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -647,7 +678,9 @@ impl<'de> serde::Deserialize<'de> for GetBlogEditorsInfoResponse { { match value { "editorCount" | "editor_count" => Ok(GeneratedField::EditorCount), - "pendingEditorRequestCount" | "pending_editor_request_count" => Ok(GeneratedField::PendingEditorRequestCount), + "pendingEditorRequestCount" | "pending_editor_request_count" => { + Ok(GeneratedField::PendingEditorRequestCount) + } _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -663,9 +696,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogEditorsInfoResponse { formatter.write_str("struct blog_def.v1.GetBlogEditorsInfoResponse") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut editor_count__ = None; let mut pending_editor_request_count__ = None; @@ -675,27 +711,36 @@ impl<'de> serde::Deserialize<'de> for GetBlogEditorsInfoResponse { if editor_count__.is_some() { return Err(serde::de::Error::duplicate_field("editorCount")); } - editor_count__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + editor_count__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } GeneratedField::PendingEditorRequestCount => { if pending_editor_request_count__.is_some() { - return Err(serde::de::Error::duplicate_field("pendingEditorRequestCount")); + return Err(serde::de::Error::duplicate_field( + "pendingEditorRequestCount", + )); } - pending_editor_request_count__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + pending_editor_request_count__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } } } Ok(GetBlogEditorsInfoResponse { editor_count: editor_count__.unwrap_or_default(), - pending_editor_request_count: pending_editor_request_count__.unwrap_or_default(), + pending_editor_request_count: pending_editor_request_count__ + .unwrap_or_default(), }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogEditorsInfoResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogEditorsInfoResponse", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogNewsletterInfoRequest { @@ -709,7 +754,8 @@ impl serde::Serialize for GetBlogNewsletterInfoRequest { if !self.identifier.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogNewsletterInfoRequest", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogNewsletterInfoRequest", len)?; if !self.identifier.is_empty() { struct_ser.serialize_field("identifier", &self.identifier)?; } @@ -722,9 +768,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterInfoRequest { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "identifier", - ]; + const FIELDS: &[&str] = &["identifier"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -740,7 +784,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterInfoRequest { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -766,9 +813,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterInfoRequest { formatter.write_str("struct blog_def.v1.GetBlogNewsletterInfoRequest") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut identifier__ = None; while let Some(k) = map.next_key()? { @@ -786,7 +836,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterInfoRequest { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogNewsletterInfoRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogNewsletterInfoRequest", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogNewsletterInfoResponse { @@ -800,7 +854,8 @@ impl serde::Serialize for GetBlogNewsletterInfoResponse { if self.subscriber_count != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogNewsletterInfoResponse", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogNewsletterInfoResponse", len)?; if self.subscriber_count != 0 { struct_ser.serialize_field("subscriberCount", &self.subscriber_count)?; } @@ -813,10 +868,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterInfoResponse { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "subscriber_count", - "subscriberCount", - ]; + const FIELDS: &[&str] = &["subscriber_count", "subscriberCount"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -832,7 +884,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterInfoResponse { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -842,7 +897,9 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterInfoResponse { E: serde::de::Error, { match value { - "subscriberCount" | "subscriber_count" => Ok(GeneratedField::SubscriberCount), + "subscriberCount" | "subscriber_count" => { + Ok(GeneratedField::SubscriberCount) + } _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -858,9 +915,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterInfoResponse { formatter.write_str("struct blog_def.v1.GetBlogNewsletterInfoResponse") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut subscriber_count__ = None; while let Some(k) = map.next_key()? { @@ -869,9 +929,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterInfoResponse { if subscriber_count__.is_some() { return Err(serde::de::Error::duplicate_field("subscriberCount")); } - subscriber_count__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + subscriber_count__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } } } @@ -880,7 +941,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterInfoResponse { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogNewsletterInfoResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogNewsletterInfoResponse", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogNewsletterRequest { @@ -897,7 +962,8 @@ impl serde::Serialize for GetBlogNewsletterRequest { if self.current_user_id.is_some() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogNewsletterRequest", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogNewsletterRequest", len)?; if !self.identifier.is_empty() { struct_ser.serialize_field("identifier", &self.identifier)?; } @@ -913,11 +979,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterRequest { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "identifier", - "current_user_id", - "currentUserId", - ]; + const FIELDS: &[&str] = &["identifier", "current_user_id", "currentUserId"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -934,7 +996,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterRequest { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -945,7 +1010,9 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterRequest { { match value { "identifier" => Ok(GeneratedField::Identifier), - "currentUserId" | "current_user_id" => Ok(GeneratedField::CurrentUserId), + "currentUserId" | "current_user_id" => { + Ok(GeneratedField::CurrentUserId) + } _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -961,9 +1028,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterRequest { formatter.write_str("struct blog_def.v1.GetBlogNewsletterRequest") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut identifier__ = None; let mut current_user_id__ = None; @@ -989,7 +1059,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterRequest { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogNewsletterRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogNewsletterRequest", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogNewsletterResponse { @@ -1021,7 +1095,8 @@ impl serde::Serialize for GetBlogNewsletterResponse { if self.is_subscribed { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogNewsletterResponse", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogNewsletterResponse", len)?; if !self.id.is_empty() { struct_ser.serialize_field("id", &self.id)?; } @@ -1085,7 +1160,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterResponse { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -1098,8 +1176,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterResponse { "id" => Ok(GeneratedField::Id), "name" => Ok(GeneratedField::Name), "description" => Ok(GeneratedField::Description), - "newsletterSplashId" | "newsletter_splash_id" => Ok(GeneratedField::NewsletterSplashId), - "newsletterSplashHex" | "newsletter_splash_hex" => Ok(GeneratedField::NewsletterSplashHex), + "newsletterSplashId" | "newsletter_splash_id" => { + Ok(GeneratedField::NewsletterSplashId) + } + "newsletterSplashHex" | "newsletter_splash_hex" => { + Ok(GeneratedField::NewsletterSplashHex) + } "user" => Ok(GeneratedField::User), "isSubscribed" | "is_subscribed" => Ok(GeneratedField::IsSubscribed), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), @@ -1117,9 +1199,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterResponse { formatter.write_str("struct blog_def.v1.GetBlogNewsletterResponse") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut id__ = None; let mut name__ = None; @@ -1150,13 +1235,17 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterResponse { } GeneratedField::NewsletterSplashId => { if newsletter_splash_id__.is_some() { - return Err(serde::de::Error::duplicate_field("newsletterSplashId")); + return Err(serde::de::Error::duplicate_field( + "newsletterSplashId", + )); } newsletter_splash_id__ = map.next_value()?; } GeneratedField::NewsletterSplashHex => { if newsletter_splash_hex__.is_some() { - return Err(serde::de::Error::duplicate_field("newsletterSplashHex")); + return Err(serde::de::Error::duplicate_field( + "newsletterSplashHex", + )); } newsletter_splash_hex__ = map.next_value()?; } @@ -1185,7 +1274,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogNewsletterResponse { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogNewsletterResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogNewsletterResponse", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogPendingStoryCountRequest { @@ -1199,7 +1292,8 @@ impl serde::Serialize for GetBlogPendingStoryCountRequest { if !self.identifier.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogPendingStoryCountRequest", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogPendingStoryCountRequest", len)?; if !self.identifier.is_empty() { struct_ser.serialize_field("identifier", &self.identifier)?; } @@ -1212,9 +1306,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogPendingStoryCountRequest { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "identifier", - ]; + const FIELDS: &[&str] = &["identifier"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -1230,7 +1322,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogPendingStoryCountRequest { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -1256,9 +1351,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogPendingStoryCountRequest { formatter.write_str("struct blog_def.v1.GetBlogPendingStoryCountRequest") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut identifier__ = None; while let Some(k) = map.next_key()? { @@ -1276,7 +1374,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogPendingStoryCountRequest { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogPendingStoryCountRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogPendingStoryCountRequest", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogPendingStoryCountResponse { @@ -1290,7 +1392,8 @@ impl serde::Serialize for GetBlogPendingStoryCountResponse { if self.pending_story_count != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogPendingStoryCountResponse", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogPendingStoryCountResponse", len)?; if self.pending_story_count != 0 { struct_ser.serialize_field("pendingStoryCount", &self.pending_story_count)?; } @@ -1303,10 +1406,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogPendingStoryCountResponse { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "pending_story_count", - "pendingStoryCount", - ]; + const FIELDS: &[&str] = &["pending_story_count", "pendingStoryCount"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -1322,7 +1422,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogPendingStoryCountResponse { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -1332,7 +1435,9 @@ impl<'de> serde::Deserialize<'de> for GetBlogPendingStoryCountResponse { E: serde::de::Error, { match value { - "pendingStoryCount" | "pending_story_count" => Ok(GeneratedField::PendingStoryCount), + "pendingStoryCount" | "pending_story_count" => { + Ok(GeneratedField::PendingStoryCount) + } _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -1348,9 +1453,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogPendingStoryCountResponse { formatter.write_str("struct blog_def.v1.GetBlogPendingStoryCountResponse") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut pending_story_count__ = None; while let Some(k) = map.next_key()? { @@ -1359,9 +1467,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogPendingStoryCountResponse { if pending_story_count__.is_some() { return Err(serde::de::Error::duplicate_field("pendingStoryCount")); } - pending_story_count__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + pending_story_count__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } } } @@ -1370,7 +1479,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogPendingStoryCountResponse { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogPendingStoryCountResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogPendingStoryCountResponse", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogPublishedStoryCountRequest { @@ -1384,7 +1497,8 @@ impl serde::Serialize for GetBlogPublishedStoryCountRequest { if !self.identifier.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogPublishedStoryCountRequest", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogPublishedStoryCountRequest", len)?; if !self.identifier.is_empty() { struct_ser.serialize_field("identifier", &self.identifier)?; } @@ -1397,9 +1511,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogPublishedStoryCountRequest { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "identifier", - ]; + const FIELDS: &[&str] = &["identifier"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -1415,7 +1527,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogPublishedStoryCountRequest { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -1441,9 +1556,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogPublishedStoryCountRequest { formatter.write_str("struct blog_def.v1.GetBlogPublishedStoryCountRequest") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut identifier__ = None; while let Some(k) = map.next_key()? { @@ -1461,7 +1579,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogPublishedStoryCountRequest { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogPublishedStoryCountRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogPublishedStoryCountRequest", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogPublishedStoryCountResponse { @@ -1475,7 +1597,8 @@ impl serde::Serialize for GetBlogPublishedStoryCountResponse { if self.published_story_count != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogPublishedStoryCountResponse", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogPublishedStoryCountResponse", len)?; if self.published_story_count != 0 { struct_ser.serialize_field("publishedStoryCount", &self.published_story_count)?; } @@ -1488,10 +1611,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogPublishedStoryCountResponse { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "published_story_count", - "publishedStoryCount", - ]; + const FIELDS: &[&str] = &["published_story_count", "publishedStoryCount"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -1507,7 +1627,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogPublishedStoryCountResponse { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -1517,7 +1640,9 @@ impl<'de> serde::Deserialize<'de> for GetBlogPublishedStoryCountResponse { E: serde::de::Error, { match value { - "publishedStoryCount" | "published_story_count" => Ok(GeneratedField::PublishedStoryCount), + "publishedStoryCount" | "published_story_count" => { + Ok(GeneratedField::PublishedStoryCount) + } _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -1533,20 +1658,26 @@ impl<'de> serde::Deserialize<'de> for GetBlogPublishedStoryCountResponse { formatter.write_str("struct blog_def.v1.GetBlogPublishedStoryCountResponse") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut published_story_count__ = None; while let Some(k) = map.next_key()? { match k { GeneratedField::PublishedStoryCount => { if published_story_count__.is_some() { - return Err(serde::de::Error::duplicate_field("publishedStoryCount")); + return Err(serde::de::Error::duplicate_field( + "publishedStoryCount", + )); } - published_story_count__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + published_story_count__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } } } @@ -1555,7 +1686,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogPublishedStoryCountResponse { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogPublishedStoryCountResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogPublishedStoryCountResponse", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogRequest { @@ -1588,11 +1723,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogRequest { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "identifier", - "current_user_id", - "currentUserId", - ]; + const FIELDS: &[&str] = &["identifier", "current_user_id", "currentUserId"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -1609,7 +1740,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogRequest { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -1620,7 +1754,9 @@ impl<'de> serde::Deserialize<'de> for GetBlogRequest { { match value { "identifier" => Ok(GeneratedField::Identifier), - "currentUserId" | "current_user_id" => Ok(GeneratedField::CurrentUserId), + "currentUserId" | "current_user_id" => { + Ok(GeneratedField::CurrentUserId) + } _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -1637,8 +1773,8 @@ impl<'de> serde::Deserialize<'de> for GetBlogRequest { } fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + where + V: serde::de::MapAccess<'de>, { let mut identifier__ = None; let mut current_user_id__ = None; @@ -2099,7 +2235,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogResponse { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -2117,8 +2256,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogResponse { "bannerHex" | "banner_hex" => Ok(GeneratedField::BannerHex), "logoId" | "logo_id" => Ok(GeneratedField::LogoId), "logoHex" | "logo_hex" => Ok(GeneratedField::LogoHex), - "newsletterSplashId" | "newsletter_splash_id" => Ok(GeneratedField::NewsletterSplashId), - "newsletterSplashHex" | "newsletter_splash_hex" => Ok(GeneratedField::NewsletterSplashHex), + "newsletterSplashId" | "newsletter_splash_id" => { + Ok(GeneratedField::NewsletterSplashId) + } + "newsletterSplashHex" | "newsletter_splash_hex" => { + Ok(GeneratedField::NewsletterSplashHex) + } "markLight" | "mark_light" => Ok(GeneratedField::MarkLight), "markDark" | "mark_dark" => Ok(GeneratedField::MarkDark), "fontCode" | "font_code" => Ok(GeneratedField::FontCode), @@ -2127,10 +2270,18 @@ impl<'de> serde::Deserialize<'de> for GetBlogResponse { "defaultTheme" | "default_theme" => Ok(GeneratedField::DefaultTheme), "forceTheme" | "force_theme" => Ok(GeneratedField::ForceTheme), "favicon" => Ok(GeneratedField::Favicon), - "hideStorinyBranding" | "hide_storiny_branding" => Ok(GeneratedField::HideStorinyBranding), - "isHomepageLargeLayout" | "is_homepage_large_layout" => Ok(GeneratedField::IsHomepageLargeLayout), - "isStoryMinimalLayout" | "is_story_minimal_layout" => Ok(GeneratedField::IsStoryMinimalLayout), - "seoDescription" | "seo_description" => Ok(GeneratedField::SeoDescription), + "hideStorinyBranding" | "hide_storiny_branding" => { + Ok(GeneratedField::HideStorinyBranding) + } + "isHomepageLargeLayout" | "is_homepage_large_layout" => { + Ok(GeneratedField::IsHomepageLargeLayout) + } + "isStoryMinimalLayout" | "is_story_minimal_layout" => { + Ok(GeneratedField::IsStoryMinimalLayout) + } + "seoDescription" | "seo_description" => { + Ok(GeneratedField::SeoDescription) + } "seoTitle" | "seo_title" => Ok(GeneratedField::SeoTitle), "previewImage" | "preview_image" => Ok(GeneratedField::PreviewImage), "isFollowing" | "is_following" => Ok(GeneratedField::IsFollowing), @@ -2138,7 +2289,9 @@ impl<'de> serde::Deserialize<'de> for GetBlogResponse { "isEditor" | "is_editor" => Ok(GeneratedField::IsEditor), "isWriter" | "is_writer" => Ok(GeneratedField::IsWriter), "isExternal" | "is_external" => Ok(GeneratedField::IsExternal), - "hasPlusFeatures" | "has_plus_features" => Ok(GeneratedField::HasPlusFeatures), + "hasPlusFeatures" | "has_plus_features" => { + Ok(GeneratedField::HasPlusFeatures) + } "websiteUrl" | "website_url" => Ok(GeneratedField::WebsiteUrl), "publicEmail" | "public_email" => Ok(GeneratedField::PublicEmail), "githubUrl" | "github_url" => Ok(GeneratedField::GithubUrl), @@ -2151,7 +2304,9 @@ impl<'de> serde::Deserialize<'de> for GetBlogResponse { "createdAt" | "created_at" => Ok(GeneratedField::CreatedAt), "category" => Ok(GeneratedField::Category), "userId" | "user_id" => Ok(GeneratedField::UserId), - "rsbItemsLabel" | "rsb_items_label" => Ok(GeneratedField::RsbItemsLabel), + "rsbItemsLabel" | "rsb_items_label" => { + Ok(GeneratedField::RsbItemsLabel) + } "lsbItems" | "lsb_items" => Ok(GeneratedField::LsbItems), "rsbItems" | "rsb_items" => Ok(GeneratedField::RsbItems), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), @@ -2170,8 +2325,8 @@ impl<'de> serde::Deserialize<'de> for GetBlogResponse { } fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + where + V: serde::de::MapAccess<'de>, { let mut id__ = None; let mut name__ = None; @@ -2270,13 +2425,17 @@ impl<'de> serde::Deserialize<'de> for GetBlogResponse { } GeneratedField::NewsletterSplashId => { if newsletter_splash_id__.is_some() { - return Err(serde::de::Error::duplicate_field("newsletterSplashId")); + return Err(serde::de::Error::duplicate_field( + "newsletterSplashId", + )); } newsletter_splash_id__ = map.next_value()?; } GeneratedField::NewsletterSplashHex => { if newsletter_splash_hex__.is_some() { - return Err(serde::de::Error::duplicate_field("newsletterSplashHex")); + return Err(serde::de::Error::duplicate_field( + "newsletterSplashHex", + )); } newsletter_splash_hex__ = map.next_value()?; } @@ -2330,19 +2489,25 @@ impl<'de> serde::Deserialize<'de> for GetBlogResponse { } GeneratedField::HideStorinyBranding => { if hide_storiny_branding__.is_some() { - return Err(serde::de::Error::duplicate_field("hideStorinyBranding")); + return Err(serde::de::Error::duplicate_field( + "hideStorinyBranding", + )); } hide_storiny_branding__ = Some(map.next_value()?); } GeneratedField::IsHomepageLargeLayout => { if is_homepage_large_layout__.is_some() { - return Err(serde::de::Error::duplicate_field("isHomepageLargeLayout")); + return Err(serde::de::Error::duplicate_field( + "isHomepageLargeLayout", + )); } is_homepage_large_layout__ = Some(map.next_value()?); } GeneratedField::IsStoryMinimalLayout => { if is_story_minimal_layout__.is_some() { - return Err(serde::de::Error::duplicate_field("isStoryMinimalLayout")); + return Err(serde::de::Error::duplicate_field( + "isStoryMinimalLayout", + )); } is_story_minimal_layout__ = Some(map.next_value()?); } @@ -2555,7 +2720,8 @@ impl serde::Serialize for GetBlogSitemapRequest { if !self.identifier.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogSitemapRequest", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogSitemapRequest", len)?; if !self.identifier.is_empty() { struct_ser.serialize_field("identifier", &self.identifier)?; } @@ -2568,9 +2734,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogSitemapRequest { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "identifier", - ]; + const FIELDS: &[&str] = &["identifier"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -2586,7 +2750,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogSitemapRequest { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -2612,9 +2779,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogSitemapRequest { formatter.write_str("struct blog_def.v1.GetBlogSitemapRequest") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut identifier__ = None; while let Some(k) = map.next_key()? { @@ -2632,7 +2802,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogSitemapRequest { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogSitemapRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogSitemapRequest", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogSitemapResponse { @@ -2646,7 +2820,8 @@ impl serde::Serialize for GetBlogSitemapResponse { if !self.content.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogSitemapResponse", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogSitemapResponse", len)?; if !self.content.is_empty() { struct_ser.serialize_field("content", &self.content)?; } @@ -2659,9 +2834,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogSitemapResponse { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "content", - ]; + const FIELDS: &[&str] = &["content"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -2677,7 +2850,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogSitemapResponse { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -2703,9 +2879,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogSitemapResponse { formatter.write_str("struct blog_def.v1.GetBlogSitemapResponse") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut content__ = None; while let Some(k) = map.next_key()? { @@ -2723,7 +2902,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogSitemapResponse { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogSitemapResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogSitemapResponse", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogWritersInfoRequest { @@ -2737,7 +2920,8 @@ impl serde::Serialize for GetBlogWritersInfoRequest { if !self.identifier.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogWritersInfoRequest", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogWritersInfoRequest", len)?; if !self.identifier.is_empty() { struct_ser.serialize_field("identifier", &self.identifier)?; } @@ -2750,9 +2934,7 @@ impl<'de> serde::Deserialize<'de> for GetBlogWritersInfoRequest { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "identifier", - ]; + const FIELDS: &[&str] = &["identifier"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -2768,7 +2950,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogWritersInfoRequest { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -2794,9 +2979,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogWritersInfoRequest { formatter.write_str("struct blog_def.v1.GetBlogWritersInfoRequest") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut identifier__ = None; while let Some(k) = map.next_key()? { @@ -2814,7 +3002,11 @@ impl<'de> serde::Deserialize<'de> for GetBlogWritersInfoRequest { }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogWritersInfoRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogWritersInfoRequest", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetBlogWritersInfoResponse { @@ -2831,12 +3023,16 @@ impl serde::Serialize for GetBlogWritersInfoResponse { if self.pending_writer_request_count != 0 { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetBlogWritersInfoResponse", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetBlogWritersInfoResponse", len)?; if self.writer_count != 0 { struct_ser.serialize_field("writerCount", &self.writer_count)?; } if self.pending_writer_request_count != 0 { - struct_ser.serialize_field("pendingWriterRequestCount", &self.pending_writer_request_count)?; + struct_ser.serialize_field( + "pendingWriterRequestCount", + &self.pending_writer_request_count, + )?; } struct_ser.end() } @@ -2869,7 +3065,10 @@ impl<'de> serde::Deserialize<'de> for GetBlogWritersInfoResponse { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -2880,7 +3079,9 @@ impl<'de> serde::Deserialize<'de> for GetBlogWritersInfoResponse { { match value { "writerCount" | "writer_count" => Ok(GeneratedField::WriterCount), - "pendingWriterRequestCount" | "pending_writer_request_count" => Ok(GeneratedField::PendingWriterRequestCount), + "pendingWriterRequestCount" | "pending_writer_request_count" => { + Ok(GeneratedField::PendingWriterRequestCount) + } _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -2896,9 +3097,12 @@ impl<'de> serde::Deserialize<'de> for GetBlogWritersInfoResponse { formatter.write_str("struct blog_def.v1.GetBlogWritersInfoResponse") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut writer_count__ = None; let mut pending_writer_request_count__ = None; @@ -2908,27 +3112,36 @@ impl<'de> serde::Deserialize<'de> for GetBlogWritersInfoResponse { if writer_count__.is_some() { return Err(serde::de::Error::duplicate_field("writerCount")); } - writer_count__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + writer_count__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } GeneratedField::PendingWriterRequestCount => { if pending_writer_request_count__.is_some() { - return Err(serde::de::Error::duplicate_field("pendingWriterRequestCount")); + return Err(serde::de::Error::duplicate_field( + "pendingWriterRequestCount", + )); } - pending_writer_request_count__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + pending_writer_request_count__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } } } Ok(GetBlogWritersInfoResponse { writer_count: writer_count__.unwrap_or_default(), - pending_writer_request_count: pending_writer_request_count__.unwrap_or_default(), + pending_writer_request_count: pending_writer_request_count__ + .unwrap_or_default(), }) } } - deserializer.deserialize_struct("blog_def.v1.GetBlogWritersInfoResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetBlogWritersInfoResponse", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetUserBlogsInfoRequest { @@ -2942,7 +3155,8 @@ impl serde::Serialize for GetUserBlogsInfoRequest { if !self.user_id.is_empty() { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetUserBlogsInfoRequest", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetUserBlogsInfoRequest", len)?; if !self.user_id.is_empty() { struct_ser.serialize_field("userId", &self.user_id)?; } @@ -2955,10 +3169,7 @@ impl<'de> serde::Deserialize<'de> for GetUserBlogsInfoRequest { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "user_id", - "userId", - ]; + const FIELDS: &[&str] = &["user_id", "userId"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -2974,7 +3185,10 @@ impl<'de> serde::Deserialize<'de> for GetUserBlogsInfoRequest { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -3000,9 +3214,12 @@ impl<'de> serde::Deserialize<'de> for GetUserBlogsInfoRequest { formatter.write_str("struct blog_def.v1.GetUserBlogsInfoRequest") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut user_id__ = None; while let Some(k) = map.next_key()? { @@ -3020,7 +3237,11 @@ impl<'de> serde::Deserialize<'de> for GetUserBlogsInfoRequest { }) } } - deserializer.deserialize_struct("blog_def.v1.GetUserBlogsInfoRequest", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetUserBlogsInfoRequest", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for GetUserBlogsInfoResponse { @@ -3040,12 +3261,14 @@ impl serde::Serialize for GetUserBlogsInfoResponse { if self.can_create_blog { len += 1; } - let mut struct_ser = serializer.serialize_struct("blog_def.v1.GetUserBlogsInfoResponse", len)?; + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.GetUserBlogsInfoResponse", len)?; if self.blog_count != 0 { struct_ser.serialize_field("blogCount", &self.blog_count)?; } if self.pending_blog_request_count != 0 { - struct_ser.serialize_field("pendingBlogRequestCount", &self.pending_blog_request_count)?; + struct_ser + .serialize_field("pendingBlogRequestCount", &self.pending_blog_request_count)?; } if self.can_create_blog { struct_ser.serialize_field("canCreateBlog", &self.can_create_blog)?; @@ -3084,7 +3307,10 @@ impl<'de> serde::Deserialize<'de> for GetUserBlogsInfoResponse { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -3095,8 +3321,12 @@ impl<'de> serde::Deserialize<'de> for GetUserBlogsInfoResponse { { match value { "blogCount" | "blog_count" => Ok(GeneratedField::BlogCount), - "pendingBlogRequestCount" | "pending_blog_request_count" => Ok(GeneratedField::PendingBlogRequestCount), - "canCreateBlog" | "can_create_blog" => Ok(GeneratedField::CanCreateBlog), + "pendingBlogRequestCount" | "pending_blog_request_count" => { + Ok(GeneratedField::PendingBlogRequestCount) + } + "canCreateBlog" | "can_create_blog" => { + Ok(GeneratedField::CanCreateBlog) + } _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -3112,9 +3342,12 @@ impl<'de> serde::Deserialize<'de> for GetUserBlogsInfoResponse { formatter.write_str("struct blog_def.v1.GetUserBlogsInfoResponse") } - fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, { let mut blog_count__ = None; let mut pending_blog_request_count__ = None; @@ -3125,17 +3358,21 @@ impl<'de> serde::Deserialize<'de> for GetUserBlogsInfoResponse { if blog_count__.is_some() { return Err(serde::de::Error::duplicate_field("blogCount")); } - blog_count__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + blog_count__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } GeneratedField::PendingBlogRequestCount => { if pending_blog_request_count__.is_some() { - return Err(serde::de::Error::duplicate_field("pendingBlogRequestCount")); + return Err(serde::de::Error::duplicate_field( + "pendingBlogRequestCount", + )); } - pending_blog_request_count__ = - Some(map.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; + pending_blog_request_count__ = Some( + map.next_value::<::pbjson::private::NumberDeserialize<_>>()? + .0, + ); } GeneratedField::CanCreateBlog => { if can_create_blog__.is_some() { @@ -3152,7 +3389,11 @@ impl<'de> serde::Deserialize<'de> for GetUserBlogsInfoResponse { }) } } - deserializer.deserialize_struct("blog_def.v1.GetUserBlogsInfoResponse", FIELDS, GeneratedVisitor) + deserializer.deserialize_struct( + "blog_def.v1.GetUserBlogsInfoResponse", + FIELDS, + GeneratedVisitor, + ) } } impl serde::Serialize for LeftSidebarItem { @@ -3197,12 +3438,7 @@ impl<'de> serde::Deserialize<'de> for LeftSidebarItem { where D: serde::Deserializer<'de>, { - const FIELDS: &[&str] = &[ - "id", - "name", - "icon", - "target", - ]; + const FIELDS: &[&str] = &["id", "name", "icon", "target"]; #[allow(clippy::enum_variant_names)] enum GeneratedField { @@ -3221,7 +3457,10 @@ impl<'de> serde::Deserialize<'de> for LeftSidebarItem { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -3251,8 +3490,8 @@ impl<'de> serde::Deserialize<'de> for LeftSidebarItem { } fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + where + V: serde::de::MapAccess<'de>, { let mut id__ = None; let mut name__ = None; @@ -3373,7 +3612,10 @@ impl<'de> serde::Deserialize<'de> for RightSidebarItem { impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { type Value = GeneratedField; - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { write!(formatter, "expected one of: {:?}", &FIELDS) } @@ -3404,8 +3646,8 @@ impl<'de> serde::Deserialize<'de> for RightSidebarItem { } fn visit_map(self, mut map: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, + where + V: serde::de::MapAccess<'de>, { let mut id__ = None; let mut primary_text__ = None; @@ -3458,3 +3700,280 @@ impl<'de> serde::Deserialize<'de> for RightSidebarItem { deserializer.deserialize_struct("blog_def.v1.RightSidebarItem", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for VerifyBlogLoginRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.blog_identifier.is_empty() { + len += 1; + } + if !self.token.is_empty() { + len += 1; + } + if !self.host.is_empty() { + len += 1; + } + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.VerifyBlogLoginRequest", len)?; + if !self.blog_identifier.is_empty() { + struct_ser.serialize_field("blogIdentifier", &self.blog_identifier)?; + } + if !self.token.is_empty() { + struct_ser.serialize_field("token", &self.token)?; + } + if !self.host.is_empty() { + struct_ser.serialize_field("host", &self.host)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for VerifyBlogLoginRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &["blog_identifier", "blogIdentifier", "token", "host"]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + BlogIdentifier, + Token, + Host, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "blogIdentifier" | "blog_identifier" => { + Ok(GeneratedField::BlogIdentifier) + } + "token" => Ok(GeneratedField::Token), + "host" => Ok(GeneratedField::Host), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = VerifyBlogLoginRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct blog_def.v1.VerifyBlogLoginRequest") + } + + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut blog_identifier__ = None; + let mut token__ = None; + let mut host__ = None; + while let Some(k) = map.next_key()? { + match k { + GeneratedField::BlogIdentifier => { + if blog_identifier__.is_some() { + return Err(serde::de::Error::duplicate_field("blogIdentifier")); + } + blog_identifier__ = Some(map.next_value()?); + } + GeneratedField::Token => { + if token__.is_some() { + return Err(serde::de::Error::duplicate_field("token")); + } + token__ = Some(map.next_value()?); + } + GeneratedField::Host => { + if host__.is_some() { + return Err(serde::de::Error::duplicate_field("host")); + } + host__ = Some(map.next_value()?); + } + } + } + Ok(VerifyBlogLoginRequest { + blog_identifier: blog_identifier__.unwrap_or_default(), + token: token__.unwrap_or_default(), + host: host__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct( + "blog_def.v1.VerifyBlogLoginRequest", + FIELDS, + GeneratedVisitor, + ) + } +} +impl serde::Serialize for VerifyBlogLoginResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.is_token_valid { + len += 1; + } + if self.cookie_value.is_some() { + len += 1; + } + if self.is_persistent_cookie.is_some() { + len += 1; + } + let mut struct_ser = + serializer.serialize_struct("blog_def.v1.VerifyBlogLoginResponse", len)?; + if self.is_token_valid { + struct_ser.serialize_field("isTokenValid", &self.is_token_valid)?; + } + if let Some(v) = self.cookie_value.as_ref() { + struct_ser.serialize_field("cookieValue", v)?; + } + if let Some(v) = self.is_persistent_cookie.as_ref() { + struct_ser.serialize_field("isPersistentCookie", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for VerifyBlogLoginResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "is_token_valid", + "isTokenValid", + "cookie_value", + "cookieValue", + "is_persistent_cookie", + "isPersistentCookie", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + IsTokenValid, + CookieValue, + IsPersistentCookie, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "isTokenValid" | "is_token_valid" => Ok(GeneratedField::IsTokenValid), + "cookieValue" | "cookie_value" => Ok(GeneratedField::CookieValue), + "isPersistentCookie" | "is_persistent_cookie" => { + Ok(GeneratedField::IsPersistentCookie) + } + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = VerifyBlogLoginResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct blog_def.v1.VerifyBlogLoginResponse") + } + + fn visit_map( + self, + mut map: V, + ) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut is_token_valid__ = None; + let mut cookie_value__ = None; + let mut is_persistent_cookie__ = None; + while let Some(k) = map.next_key()? { + match k { + GeneratedField::IsTokenValid => { + if is_token_valid__.is_some() { + return Err(serde::de::Error::duplicate_field("isTokenValid")); + } + is_token_valid__ = Some(map.next_value()?); + } + GeneratedField::CookieValue => { + if cookie_value__.is_some() { + return Err(serde::de::Error::duplicate_field("cookieValue")); + } + cookie_value__ = map.next_value()?; + } + GeneratedField::IsPersistentCookie => { + if is_persistent_cookie__.is_some() { + return Err(serde::de::Error::duplicate_field( + "isPersistentCookie", + )); + } + is_persistent_cookie__ = map.next_value()?; + } + } + } + Ok(VerifyBlogLoginResponse { + is_token_valid: is_token_valid__.unwrap_or_default(), + cookie_value: cookie_value__, + is_persistent_cookie: is_persistent_cookie__, + }) + } + } + deserializer.deserialize_struct( + "blog_def.v1.VerifyBlogLoginResponse", + FIELDS, + GeneratedVisitor, + ) + } +} diff --git a/src/routes/init.rs b/src/routes/init.rs index 42ee562..5312c4b 100644 --- a/src/routes/init.rs +++ b/src/routes/init.rs @@ -14,6 +14,12 @@ mod index; #[path = "v1/mod.rs"] mod v1; +#[cfg(test)] +pub use v1::auth::login::tests::{ + GetLoginDetailsResponse, + get as get_login_details, +}; + /// Registers common API routes. /// /// * `cfg` - Web service config @@ -65,7 +71,6 @@ pub fn init_v1_routes(cfg: &mut web::ServiceConfig) { v1::blogs::writers::init_routes(cfg); v1::blogs::feed::init_routes(cfg); v1::blogs::subscribe::init_routes(cfg); - v1::blogs::verify_login::init_routes(cfg); // Me v1::me::get::init_routes(cfg); // Me - User activity diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 17b73fc..e340e83 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -2,3 +2,9 @@ pub mod favicon; pub mod init; pub mod oauth; pub mod robots; + +#[cfg(test)] +pub use init::{ + GetLoginDetailsResponse, + get_login_details, +}; diff --git a/src/routes/v1/auth/login.rs b/src/routes/v1/auth/login.rs index c80421e..73b84ff 100644 --- a/src/routes/v1/auth/login.rs +++ b/src/routes/v1/auth/login.rs @@ -1,8 +1,12 @@ use crate::{ AppState, + RedisPool, constants::{ blog_domain_regex::BLOG_DOMAIN_REGEX, + blog_login_token_data::BlogLoginTokenData, + blog_login_token_expiration::BLOG_LOGIN_TOKEN_EXPIRATION, notification_entity_type::NotificationEntityType, + redis_namespaces::RedisNamespace, resource_lock::ResourceLock, user_flag::UserFlag, }, @@ -20,8 +24,14 @@ use crate::{ }, generate_hashed_token::generate_hashed_token, generate_totp::generate_totp, - get_client_device::get_client_device, - get_client_location::get_client_location, + get_client_device::{ + ClientDevice, + get_client_device, + }, + get_client_location::{ + ClientLocation, + get_client_location, + }, get_user_sessions::get_user_sessions, incr_resource_lock_attempts::incr_resource_lock_attempts, is_resource_locked::is_resource_locked, @@ -59,10 +69,7 @@ use storiny_session::{ Session, config::SessionLifecycle, }; -use time::{ - Duration, - OffsetDateTime, -}; +use time::OffsetDateTime; use totp_rs::Secret; use tracing::{ debug, @@ -72,28 +79,26 @@ use tracing::{ use url::Url; use validator::Validate; -// This struct is public because it is used by `blogs/verify-login` route. #[derive(Debug, Serialize, Deserialize, Validate)] -pub struct Request { +struct Request { #[validate(email(message = "Invalid e-mail"))] #[validate(length(min = 3, max = 300, message = "Invalid e-mail length"))] - pub email: String, + email: String, #[validate(length(min = 6, max = 64, message = "Invalid password length"))] - pub password: String, - pub remember_me: bool, + password: String, + remember_me: bool, #[validate(length(min = 6, max = 12, message = "Invalid authentication code"))] - pub code: Option, + code: Option, #[validate(regex = "BLOG_DOMAIN_REGEX")] #[validate(length(min = 3, max = 512, message = "Invalid blog domain"))] - pub blog_domain: Option, + blog_domain: Option, } -// This struct is public because it is used by `blogs/verify-login` route. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Response { - pub result: String, - pub is_first_login: bool, - pub blog_token: Option, +struct Response { + result: String, + is_first_login: bool, + blog_token: Option, } #[derive(Deserialize, Validate)] @@ -403,6 +408,32 @@ WHERE id = $1 } } + let mut client_device: (String, Option) = ("Unknown device".to_string(), None); + let mut client_location: (Option, Option) = (None, None); + + // Extract client details from the request. + + if let Some(ip) = req.connection_info().realip_remote_addr() { + if let Ok(parsed_ip) = ip.parse::() { + if let Some(client_location_result) = get_client_location(parsed_ip, &data.geo_db) { + client_location = ( + Some(client_location_result.display_name.to_string()), + Some(client_location_result), + ); + } + } + } + + if let Some(ua_header) = req.headers().get("user-agent") { + if let Ok(ua) = ua_header.to_str() { + let client_device_result = get_client_device(ua, &data.ua_parser); + client_device = ( + client_device_result.display_name.to_string(), + Some(client_device_result), + ); + } + } + // Handle blog login if let Some(domain) = &payload.blog_domain { tracing::Span::current().record("blog_domain", domain); @@ -411,7 +442,10 @@ WHERE id = $1 domain, user_id, payload.remember_me, + client_location.1.clone(), + client_device.1.clone(), &data.config.token_salt, + &data.redis, &mut txn, ) .await? @@ -428,51 +462,34 @@ WHERE id = $1 } } - let mut client_device_value = "Unknown device".to_string(); - let mut client_location_value: Option = None; - // Insert additional data to the session. - { - if let Some(ip) = req.connection_info().realip_remote_addr() { - if let Ok(parsed_ip) = ip.parse::() { - if let Some(client_location_result) = get_client_location(parsed_ip, &data.geo_db) { - client_location_value = Some(client_location_result.display_name.to_string()); + if let Some(ref client_location_result) = client_location.1 { + if let Ok(client_location) = serde_json::to_value(client_location_result) { + session.insert("location", client_location); + } + } - if let Ok(client_location) = serde_json::to_value(client_location_result) { - session.insert("location", client_location); - } - } - } + if let Some(ref client_device_result) = client_device.1 { + if let Ok(client_device) = serde_json::to_value(client_device_result) { + session.insert("device", client_device); } + } - if let Some(origin) = req.headers().get(actix_http::header::ORIGIN) { - if let Ok(url) = Url::parse(origin.to_str().unwrap_or_default()) { - if let Some(domain) = url.domain() { - match domain { - "storiny.com" => {} - "www.storiny.com" => {} - _ => { - if domain.chars().count() < 256 { - if let Ok(domain) = serde_json::to_value(domain) { - session.insert("domain", domain); - } + if let Some(origin) = req.headers().get(actix_http::header::ORIGIN) { + if let Ok(url) = Url::parse(origin.to_str().unwrap_or_default()) { + if let Some(domain) = url.domain() { + match domain { + "storiny.com" | "www.storiny.com" => {} + _ => { + if domain.chars().count() < 256 { + if let Ok(domain) = serde_json::to_value(domain) { + session.insert("domain", domain); } } } } } } - - if let Some(ua_header) = req.headers().get("user-agent") { - if let Ok(ua) = ua_header.to_str() { - let client_device_result = get_client_device(ua, &data.ua_parser); - client_device_value = client_device_result.display_name.to_string(); - - if let Ok(client_device) = serde_json::to_value(client_device_result) { - session.insert("device", client_device); - } - } - } } // Update the `last_login_at` and insert a login notification for the user. @@ -500,10 +517,10 @@ SELECT $2, (SELECT id FROM inserted_notification), $3 ) .bind(NotificationEntityType::LoginAttempt as i16) .bind(user_id) - .bind(if let Some(location) = client_location_value { - format!("{client_device_value}:{location}") + .bind(if let Some(location) = client_location.0 { + format!("{}:{location}", client_device.0) } else { - client_device_value + client_device.0 }) .execute(&mut *txn) .await?; @@ -570,15 +587,20 @@ SELECT $2, (SELECT id FROM inserted_notification), $3 /// /// * `domain` - The blog's domain name. /// * `user_id` - The ID of the user trying to log in. -/// * `token_salt` - The salt for generating login token. /// * `persistent` - Whether to use a persistent browser session after login. -/// * `txn` - The Postgres transaction. +/// * `location` - The optional [ClientLocation] value of the user. +/// * `device` - The optional [ClientDevice] value for the user. +/// * `token_salt` - The salt for generating login token. +/// * `db_txn` - The Postgres transaction. async fn handle_blog_login<'a>( domain: &str, user_id: i64, persistent: bool, + location: Option, + device: Option, token_salt: &str, - txn: &mut Transaction<'a, Postgres>, + redis_pool: &RedisPool, + db_txn: &mut Transaction<'a, Postgres>, ) -> Result, AppError> { let blog_result = sqlx::query( r#" @@ -590,7 +612,7 @@ WHERE "#, ) .bind(domain.to_string()) - .fetch_one(&mut **txn) + .fetch_one(&mut **db_txn) .await; match blog_result { @@ -600,31 +622,34 @@ WHERE // Generate a new login token. let (token_id, hashed_token) = generate_hashed_token(token_salt)?; - // Delete old tokens - sqlx::query( - r#" -DELETE FROM blog_login_tokens -WHERE blog_id = $1 AND user_id = $2 -"#, - ) - .bind(blog_id) - .bind(user_id) - .execute(&mut **txn) - .await?; + let mut redis_conn = redis_pool.get().await?; + let cache_key = format!("{}:{hashed_token}", RedisNamespace::BlogLogin); - sqlx::query( - r#" -INSERT INTO blog_login_tokens (id, blog_id, user_id, is_persistent_session, expires_at) -VALUES ($1, $2, $3, $4, $5) -"#, - ) - .bind(&hashed_token) - .bind(blog_id) - .bind(user_id) - .bind(persistent) - .bind(OffsetDateTime::now_utc() + Duration::minutes(3)) // 3 minutes - .execute(&mut **txn) - .await?; + let serialized_token_data = rmp_serde::to_vec_named(&BlogLoginTokenData { + uid: user_id, + bid: blog_id, + persistent, + loc: location, + device, + }) + .map_err(|error| { + AppError::InternalError(format!( + "unable to serialize the blog login token data: {error:?}" + )) + })?; + + redis::cmd("SET") + .arg(&cache_key) + .arg(&serialized_token_data) + .arg("EX") // EX: set expiry + .arg(BLOG_LOGIN_TOKEN_EXPIRATION) + .query_async::<_, ()>(&mut *redis_conn) + .await + .map_err(|error| { + AppError::InternalError(format!( + "unable to save the blog login token data: {error:?}" + )) + })?; Ok(Some(token_id)) } @@ -690,6 +715,13 @@ pub mod tests { use storiny_macros::test_context; use uuid::Uuid; + #[derive(Debug, Deserialize)] + pub struct GetLoginDetailsResponse { + pub device: Option, + pub location: Option, + pub domain: Option, + } + /// Returns the device and session data present in the session for testing. #[get("/get-login-details")] pub async fn get(session: Session) -> impl Responder { @@ -705,7 +737,7 @@ pub mod tests { } /// Returns a sample email and hashed password. - pub fn get_sample_email_and_password() -> (String, String, String) { + fn get_sample_email_and_password() -> (String, String, String) { let password = "sample"; let email = "someone@example.com"; let salt = SaltString::generate(&mut OsRng); @@ -1719,6 +1751,8 @@ VALUES ($1, $2, $3, $4, TRUE) .to_request(); let res = test::call_service(&app, req).await; + // Should fall-back to native (storiny.com) login. + assert!(res.status().is_success()); assert_response_body_text( res, @@ -1773,112 +1807,9 @@ WHERE username = $1 Ok(()) } - #[sqlx::test] - async fn can_handle_blog_login(pool: PgPool) -> sqlx::Result<()> { - let mut conn = pool.acquire().await?; - let app = init_app_for_test(post, pool, false, false, None).await.0; - - let (email, password_hash, password) = get_sample_email_and_password(); - - // Insert the user and blog. - let blog = sqlx::query( - r#" -WITH inserted_user AS ( - INSERT INTO users (name, username, email, password, email_verified) - VALUES ($1, $2, $3, $4, TRUE) - RETURNING id -) -INSERT INTO blogs (name, slug, domain, user_id) -VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) -RETURNING id -"#, - ) - .bind("Sample user".to_string()) - .bind("sample_user".to_string()) - .bind(email.to_string()) - .bind(password_hash) - .fetch_one(&mut *conn) - .await?; - - let blog_id = blog.get::("id"); - - // With `remember_me` = false - - let req = test::TestRequest::post() - .uri("/v1/auth/login") - .set_json(Request { - email: email.to_string(), - password: password.to_string(), - remember_me: false, - code: None, - blog_domain: Some("test.com".to_string()), - }) - .to_request(); - let res = test::call_service(&app, req).await; - - assert!(res.status().is_success()); - - let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); - - assert!(json.blog_token.is_some()); - assert_eq!(json.result, "success".to_string()); - - // Should insert a blog login token. - let result = sqlx::query( - r#" -SELECT is_persistent_session -FROM blog_login_tokens -WHERE blog_id = $1 -"#, - ) - .bind(blog_id) - .fetch_all(&mut *conn) - .await?; - - assert_eq!(result.len(), 1); - assert_eq!(result[0].get::("is_persistent_session"), false); - - // With `remember_me` = true - - let req = test::TestRequest::post() - .uri("/v1/auth/login") - .set_json(Request { - email: email.to_string(), - password: password.to_string(), - remember_me: true, - code: None, - blog_domain: Some("test.com".to_string()), - }) - .to_request(); - let res = test::call_service(&app, req).await; - - assert!(res.status().is_success()); - - let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); - - assert!(json.blog_token.is_some()); - assert_eq!(json.result, "success".to_string()); - - // `is_persistent_session` should be true, and old token should get deleted. - let result = sqlx::query( - r#" -SELECT is_persistent_session -FROM blog_login_tokens -WHERE blog_id = $1 -"#, - ) - .bind(blog_id) - .fetch_all(&mut *conn) - .await?; - - assert_eq!(result.len(), 1); - assert_eq!(result[0].get::("is_persistent_session"), true); - - Ok(()) - } - mod serial { use super::*; + use crate::config::get_app_config; #[test_context(RedisTestContext)] #[sqlx::test] @@ -2311,7 +2242,7 @@ VALUES ($1, $2, $3, $4, TRUE) password: password.to_string(), remember_me: true, code: None, - blog_domain: None + blog_domain: None, }) .to_request(); let res = test::call_service(&app, req).await; @@ -2329,15 +2260,7 @@ VALUES ($1, $2, $3, $4, TRUE) .uri("/get-login-details") .to_request(); let res = test::call_service(&app, req).await; - - #[derive(Deserialize)] - struct ClientSession { - device: Option, - location: Option, - domain: Option, - } - - let client_session = test::read_body_json::(res).await; + let client_session = test::read_body_json::(res).await; assert!(client_session.device.is_some()); assert!(client_session.location.is_some()); @@ -2345,5 +2268,140 @@ VALUES ($1, $2, $3, $4, TRUE) Ok(()) } + + // Blog login + + #[test_context(RedisTestContext)] + #[sqlx::test] + async fn can_handle_blog_login( + ctx: &mut RedisTestContext, + pool: PgPool, + ) -> sqlx::Result<()> { + let redis_pool = &ctx.redis_pool; + let config = get_app_config().unwrap(); + let mut conn = pool.acquire().await?; + let mut redis_conn = redis_pool.get().await.unwrap(); + let app = init_app_for_test(post, pool, false, false, None).await.0; + let salt = SaltString::from_b64(&config.token_salt).unwrap(); + + let (email, password_hash, password) = get_sample_email_and_password(); + + // Insert the user and blog. + let blog = sqlx::query( + r#" +WITH inserted_user AS ( + INSERT INTO users (name, username, email, password, email_verified) + VALUES ($1, $2, $3, $4, TRUE) + RETURNING id +) +INSERT INTO blogs (name, slug, domain, user_id) +VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) +RETURNING id, user_id +"#, + ) + .bind("Sample user".to_string()) + .bind("sample_user".to_string()) + .bind(email.to_string()) + .bind(password_hash) + .fetch_one(&mut *conn) + .await?; + + let blog_id = blog.get::("id"); + let user_id = blog.get::("user_id"); + + // With `remember_me` = false + + let req = test::TestRequest::post() + .uri("/v1/auth/login") + .set_json(Request { + email: email.to_string(), + password: password.to_string(), + remember_me: false, + code: None, + blog_domain: Some("test.com".to_string()), + }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + + let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); + + assert!(json.blog_token.is_some()); + assert_eq!(json.result, "success".to_string()); + + // Should insert a blog login token. + let token = json.blog_token.unwrap(); + let hashed_token = Argon2::default() + .hash_password(token.as_bytes(), &salt) + .unwrap(); + let cache_key = format!("{}:{hashed_token}", RedisNamespace::BlogLogin); + + let result: Option> = redis::cmd("GET") + .arg(&[&cache_key]) + .query_async(&mut *redis_conn) + .await + .expect("unable to get the login token"); + let serialized_value = result.expect("token not found"); + let value = rmp_serde::from_slice::(&serialized_value) + .expect("unable to parse login token data"); + + assert_eq!(value.uid, user_id); + assert_eq!(value.bid, blog_id); + assert_eq!(value.persistent, false); + assert!(value.loc.is_none()); + assert!(value.device.is_none()); + + // With `remember_me` = true, location, and device details. + + let req = test::TestRequest::post() + .uri("/v1/auth/login") + .peer_addr(SocketAddr::from(SocketAddrV4::new( + Ipv4Addr::new(8, 8, 8, 8), + 8080, + ))) + .append_header(("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:40.0) Gecko/20100101 Firefox/40.0")) + .append_header(("origin", "https://test.storiny.com")) + .set_json(Request { + email: email.to_string(), + password: password.to_string(), + remember_me: true, + code: None, + blog_domain: Some("test.com".to_string()), + }) + .to_request(); + let res = test::call_service(&app, req).await; + + assert!(res.status().is_success()); + + let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); + + assert!(json.blog_token.is_some()); + assert_eq!(json.result, "success".to_string()); + + // `is_persistent_session` should be true. + let token = json.blog_token.unwrap(); + let hashed_token = Argon2::default() + .hash_password(token.as_bytes(), &salt) + .unwrap(); + let cache_key = format!("{}:{hashed_token}", RedisNamespace::BlogLogin); + + let result: Option> = redis::cmd("GET") + .arg(&[&cache_key]) + .query_async(&mut *redis_conn) + .await + .expect("unable to get the login token"); + let serialized_value = result.expect("token not found"); + let value = rmp_serde::from_slice::(&serialized_value) + .expect("unable to parse login token data"); + + assert_eq!(value.uid, user_id); + assert_eq!(value.bid, blog_id); + assert_eq!(value.persistent, true); + assert!(value.loc.is_some()); + assert!(value.device.is_some()); + + Ok(()) + } } } diff --git a/src/routes/v1/blogs/mod.rs b/src/routes/v1/blogs/mod.rs index 463a726..e70db98 100644 --- a/src/routes/v1/blogs/mod.rs +++ b/src/routes/v1/blogs/mod.rs @@ -2,5 +2,4 @@ pub mod archive; pub mod editors; pub mod feed; pub mod subscribe; -pub mod verify_login; pub mod writers; diff --git a/src/routes/v1/blogs/verify_login/mod.rs b/src/routes/v1/blogs/verify_login/mod.rs deleted file mode 100644 index c5669fd..0000000 --- a/src/routes/v1/blogs/verify_login/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod verify_login; - -pub use verify_login::*; diff --git a/src/routes/v1/blogs/verify_login/verify_login.rs b/src/routes/v1/blogs/verify_login/verify_login.rs deleted file mode 100644 index 3207cdf..0000000 --- a/src/routes/v1/blogs/verify_login/verify_login.rs +++ /dev/null @@ -1,791 +0,0 @@ -use crate::{ - AppState, - constants::notification_entity_type::NotificationEntityType, - error::{ - AppError, - ToastErrorResponse, - }, - middlewares::identity::identity::Identity, - utils::{ - clear_user_sessions::clear_user_sessions, - get_client_device::get_client_device, - get_client_location::get_client_location, - get_user_sessions::get_user_sessions, - }, -}; -use actix_http::HttpMessage; -use actix_web::{ - HttpRequest, - HttpResponse, - post, - web, -}; -use actix_web_validator::Json; -use argon2::{ - Argon2, - PasswordHasher, - password_hash::SaltString, -}; -use serde::{ - Deserialize, - Serialize, -}; -use sqlx::Row; -use std::net::IpAddr; -use storiny_session::{ - Session, - config::SessionLifecycle, -}; -use tracing::debug; -use url::Url; -use validator::Validate; - -#[derive(Deserialize, Validate)] -struct Fragments { - blog_id: String, -} - -#[derive(Debug, Serialize, Deserialize, Validate)] -struct Request { - #[validate(length(equal = 48, message = "Invalid token length"))] - token: String, -} - -#[derive(Debug, Clone, Serialize)] -struct Response { - result: String, -} - -#[post("/v1/blogs/{blog_id}/verify-login")] -#[tracing::instrument(name = "POST /v1/blogs/{blog_id}/verify-login", skip_all, err)] -async fn post( - req: HttpRequest, - payload: Json, - data: web::Data, - path: web::Path, - session: Session, -) -> Result { - let blog_id = path - .blog_id - .parse::() - .map_err(|_| AppError::from("Invalid blog ID"))?; - let token = &payload.token; - let mut req_host: Option = None; - - if let Some(origin) = req.headers().get(actix_http::header::ORIGIN) { - if let Ok(url) = Url::parse(origin.to_str().unwrap_or_default()) { - if let Some(domain) = url.domain() { - req_host = Some(domain.strip_prefix("www.").unwrap_or(domain).to_string()); - } - } - } - - if req_host.is_none() { - return Err(AppError::InternalError("missing host name".to_string())); - } - - let pg_pool = &data.db_pool; - let mut txn = pg_pool.begin().await?; - - let blog = sqlx::query( - r#" -SELECT domain -FROM blogs -WHERE - id = $1 - AND deleted_at IS NULL -"#, - ) - .bind(blog_id) - .fetch_one(&mut *txn) - .await - .map_err(|error| { - if matches!(error, sqlx::Error::RowNotFound) { - AppError::ToastError(ToastErrorResponse::new(None, "Unknown blog")) - } else { - AppError::SqlxError(error) - } - })?; - - if blog.get::, _>("domain") != req_host { - return Err(AppError::ToastError(ToastErrorResponse::new( - None, - "Blog domain does not match", - ))); - } - - let salt = SaltString::from_b64(&data.config.token_salt) - .map_err(|error| AppError::InternalError(error.to_string()))?; - - let hashed_token = Argon2::default() - .hash_password(token.as_bytes(), &salt) - .map_err(|error| AppError::InternalError(format!("unable to hash the token: {error:?}")))?; - - let (user_id, is_persistent_session) = match sqlx::query( - r#" -DELETE FROM blog_login_tokens -WHERE - id = $1 - AND blog_id = $2 - AND expires_at > NOW() -RETURNING user_id, is_persistent_session -"#, - ) - .bind(hashed_token.to_string()) - .bind(blog_id) - .fetch_one(&mut *txn) - .await - { - Ok(row) => { - let user_id = row.get::("user_id"); - let is_persistent_session = row.get::("is_persistent_session"); - - (user_id, is_persistent_session) - } - Err(error) => { - if matches!(error, sqlx::Error::RowNotFound) { - return Ok(HttpResponse::Ok().json(Response { - result: "invalid_token".to_string(), - })); - } - - return Err(AppError::from(error)); - } - }; - - // Proceed with login - - let mut client_device_value = "Unknown device".to_string(); - let mut client_location_value: Option = None; - - // Insert additional data to the session. - { - if let Ok(domain) = serde_json::to_value(req_host.clone().unwrap_or_default()) { - session.insert("domain", domain); - } - - if let Some(ip) = req.connection_info().realip_remote_addr() { - if let Ok(parsed_ip) = ip.parse::() { - if let Some(client_location_result) = get_client_location(parsed_ip, &data.geo_db) { - client_location_value = Some(client_location_result.display_name.to_string()); - - if let Ok(client_location) = serde_json::to_value(client_location_result) { - session.insert("location", client_location); - } - } - } - } - - if let Some(ua_header) = req.headers().get("user-agent") { - if let Ok(ua) = ua_header.to_str() { - let client_device_result = get_client_device(ua, &data.ua_parser); - client_device_value = client_device_result.display_name.to_string(); - - if let Ok(client_device) = serde_json::to_value(client_device_result) { - session.insert("device", client_device); - } - } - } - } - - // Update the `last_login_at` and insert a login notification for the user. - sqlx::query( - r#" -WITH updated_user AS ( - UPDATE users - SET last_login_at = NOW() - WHERE id = $2 -), -inserted_notification AS ( - INSERT INTO notifications (entity_type) - VALUES ($1) - RETURNING id -) -INSERT -INTO - notification_outs ( - notified_id, - notification_id, - rendered_content - ) -SELECT $2, (SELECT id FROM inserted_notification), $3 -"#, - ) - .bind(NotificationEntityType::LoginAttempt as i16) - .bind(user_id) - .bind(if let Some(location) = client_location_value { - format!("{client_device_value}:{location}") - } else { - client_device_value - }) - .execute(&mut *txn) - .await?; - - // Session cookie domain - session.set_cookie_domain(Some(req_host.unwrap_or_default())); - - // Session lifecycle depends on the `is_persistent_session` value. - session.set_lifecycle(if is_persistent_session { - SessionLifecycle::PersistentSession - } else { - SessionLifecycle::BrowserSession - }); - - // Check if the user maintains more than or equal to 10 sessions, and - // delete all the previous sessions if the current number of active - // sessions for the user exceeds the per user session limit (10). - match get_user_sessions(&data.redis, user_id).await { - Ok(sessions) => { - if sessions.len() >= 10 { - match clear_user_sessions(&data.redis, user_id).await { - Ok(_) => { - debug!( - "cleared {} overflowing sessions for the user", - sessions.len() - ); - } - Err(error) => { - return Err(AppError::InternalError(format!( - "unable to clear the overflowing sessions for the user: {:?}", - error - ))); - } - }; - } - } - Err(error) => { - return Err(AppError::InternalError(format!( - "unable to fetch the sessions for the user: {:?}", - error - ))); - } - }; - - let login_result = Identity::login(&req.extensions(), user_id); - - match login_result { - Ok(_) => { - txn.commit().await?; - - debug!("user logged in to blog"); - - Ok(HttpResponse::Ok().json(Response { - result: "success".to_string(), - })) - } - Err(error) => Err(AppError::InternalError(format!( - "identity error: {:?}", - error - ))), - } -} - -pub fn init_routes(cfg: &mut web::ServiceConfig) { - cfg.service(post); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - config::get_app_config, - constants::{ - redis_namespaces::RedisNamespace, - session_cookie::SESSION_COOKIE_NAME, - }, - routes::init::v1::auth::login::{ - Request as LoginRequest, - Response as LoginResponse, - post as login_post, - tests::{ - get as test_login_get, - get_sample_email_and_password, - }, - }, - test_utils::{ - RedisTestContext, - assert_response_body_text, - assert_toast_error_response, - init_app_for_test, - res_to_string, - }, - utils::{ - generate_hashed_token::generate_hashed_token, - get_client_device::ClientDevice, - get_client_location::ClientLocation, - get_user_sessions::UserSession, - }, - }; - use actix_web::{ - services, - test, - }; - use redis::AsyncCommands; - use sqlx::PgPool; - use std::net::{ - Ipv4Addr, - SocketAddr, - SocketAddrV4, - }; - use storiny_macros::test_context; - use time::{ - Duration, - OffsetDateTime, - }; - use uuid::Uuid; - - #[sqlx::test] - async fn can_verify_login(pool: PgPool) -> sqlx::Result<()> { - let mut conn = pool.acquire().await?; - let app = init_app_for_test(services![login_post, post], pool, false, false, None) - .await - .0; - - let (email, password_hash, password) = get_sample_email_and_password(); - - // Insert the user and blog. - let blog = sqlx::query( - r#" -WITH inserted_user AS ( - INSERT INTO users (name, username, email, password, email_verified) - VALUES ($1, $2, $3, $4, TRUE) - RETURNING id -) -INSERT INTO blogs (name, slug, domain, user_id) -VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) -RETURNING id -"#, - ) - .bind("Sample user".to_string()) - .bind("sample_user".to_string()) - .bind(email.to_string()) - .bind(password_hash) - .fetch_one(&mut *conn) - .await?; - - let blog_id = blog.get::("id"); - - // Send a login request. - let req = test::TestRequest::post() - .uri("/v1/auth/login") - .set_json(LoginRequest { - email: email.to_string(), - password: password.to_string(), - remember_me: true, - code: None, - blog_domain: Some("test.com".to_string()), - }) - .to_request(); - let res = test::call_service(&app, req).await; - - assert!(res.status().is_success()); - - let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); - - assert_eq!(json.result, "success".to_string()); - assert!(json.blog_token.is_some()); - - let login_token = json.blog_token.unwrap(); - - // Verify login token. - let req = test::TestRequest::post() - .append_header(("origin", "https://test.com")) - .uri(&format!("/v1/blogs/{blog_id}/verify-login")) - .set_json(Request { token: login_token }) - .to_request(); - let res = test::call_service(&app, req).await; - - assert!(res.status().is_success()); - assert_response_body_text( - res, - &serde_json::to_string(&Response { - result: "success".to_string(), - }) - .unwrap_or_default(), - ) - .await; - - // Should delete login token. - let result = sqlx::query( - r#" -SELECT EXISTS ( - SELECT - 1 - FROM - blog_login_tokens - WHERE - blog_id = $1 - ) -"#, - ) - .bind(blog_id) - .fetch_one(&mut *conn) - .await?; - - assert!(!result.get::("exists")); - - // Should also insert a notification. - let result = sqlx::query( - r#" -SELECT EXISTS ( - SELECT - 1 - FROM - notification_outs - WHERE - notification_id = ( - SELECT id FROM notifications - WHERE entity_type = $1 - ) - ) -"#, - ) - .bind(NotificationEntityType::LoginAttempt as i16) - .fetch_one(&mut *conn) - .await?; - - assert!(result.get::("exists")); - - // Should also update the `last_login_at` column. - let result = sqlx::query( - r#" -SELECT last_login_at FROM users -WHERE username = $1 -"#, - ) - .bind("sample_user") - .fetch_one(&mut *conn) - .await?; - - assert!( - result - .get::, _>("last_login_at") - .is_some() - ); - - Ok(()) - } - - #[sqlx::test] - async fn can_reject_login_from_unmatched_host(pool: PgPool) -> sqlx::Result<()> { - let mut conn = pool.acquire().await?; - let config = get_app_config().unwrap(); - let app = init_app_for_test(post, pool, false, false, None).await.0; - let (token_id, hashed_token) = generate_hashed_token(&config.token_salt).unwrap(); - - // Insert data. - let blog = sqlx::query( - r#" -WITH inserted_user AS ( - INSERT INTO users (name, username, email, email_verified) - VALUES ('Sample user', 'sample_user', 'sample@example.com', TRUE) - RETURNING id -), inserted_blog AS ( - INSERT INTO blogs (name, slug, domain, user_id) - VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) - RETURNING id -) -INSERT INTO blog_login_tokens (id, user_id, blog_id, expires_at) -VALUES ($1, (SELECT id FROM inserted_user), (SELECT id FROM inserted_blog), NOW()) -RETURNING blog_id -"#, - ) - .bind(&hashed_token) - .fetch_one(&mut *conn) - .await?; - - let blog_id = blog.get::("blog_id"); - - // Try to verify login token. - let req = test::TestRequest::post() - .append_header(("origin", "https://invalid.com")) - .uri(&format!("/v1/blogs/{blog_id}/verify-login")) - .set_json(Request { token: token_id }) - .to_request(); - let res = test::call_service(&app, req).await; - - assert!(res.status().is_client_error()); - assert_toast_error_response(res, "Blog domain does not match").await; - - Ok(()) - } - - #[sqlx::test] - async fn can_reject_login_for_expired_token(pool: PgPool) -> sqlx::Result<()> { - let mut conn = pool.acquire().await?; - let config = get_app_config().unwrap(); - let app = init_app_for_test(post, pool, false, false, None).await.0; - let (token_id, hashed_token) = generate_hashed_token(&config.token_salt).unwrap(); - - // Insert an expired token. - let blog = sqlx::query( - r#" -WITH inserted_user AS ( - INSERT INTO users (name, username, email, email_verified) - VALUES ('Sample user', 'sample_user', 'sample@example.com', TRUE) - RETURNING id -), inserted_blog AS ( - INSERT INTO blogs (name, slug, domain, user_id) - VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) - RETURNING id -) -INSERT INTO blog_login_tokens (id, user_id, blog_id, expires_at) -VALUES ($1, (SELECT id FROM inserted_user), (SELECT id FROM inserted_blog), $2) -RETURNING blog_id -"#, - ) - .bind(&hashed_token) - .bind(OffsetDateTime::now_utc() - Duration::days(1)) // Yesterday - .fetch_one(&mut *conn) - .await?; - - let blog_id = blog.get::("blog_id"); - - // Try to verify login token. - let req = test::TestRequest::post() - .append_header(("origin", "https://test.com")) - .uri(&format!("/v1/blogs/{blog_id}/verify-login")) - .set_json(Request { token: token_id }) - .to_request(); - let res = test::call_service(&app, req).await; - - assert!(res.status().is_success()); - assert_response_body_text( - res, - &serde_json::to_string(&Response { - result: "invalid_token".to_string(), - }) - .unwrap_or_default(), - ) - .await; - - Ok(()) - } - - mod serial { - use super::*; - - #[test_context(RedisTestContext)] - #[sqlx::test] - async fn can_clear_overflowing_sessions_on_login( - ctx: &mut RedisTestContext, - pool: PgPool, - ) -> sqlx::Result<()> { - let redis_pool = &ctx.redis_pool; - let mut redis_conn = redis_pool.get().await.unwrap(); - let mut conn = pool.acquire().await?; - let (app, _, user_id) = - init_app_for_test(services![login_post, post], pool, true, true, None).await; - - let (email, password_hash, password) = get_sample_email_and_password(); - - // Create 10 sessions (one is already created from `init_app_for_test`). - for _ in 0..9 { - let _: () = redis_conn - .set( - &format!( - "{}:{}:{}", - RedisNamespace::Session, - user_id.unwrap(), - Uuid::new_v4() - ), - &rmp_serde::to_vec_named(&UserSession { - user_id: user_id.unwrap(), - ..Default::default() - }) - .unwrap(), - ) - .await - .unwrap(); - } - - let sessions = get_user_sessions(redis_pool, user_id.unwrap()) - .await - .unwrap(); - - assert_eq!(sessions.len(), 10); - - // Insert the user and blog. - let blog = sqlx::query( - r#" -WITH inserted_user AS ( - INSERT INTO users (id, name, username, email, password, email_verified) - VALUES ($1, $2, $3, $4, $5, TRUE) -) -INSERT INTO blogs (name, slug, domain, user_id) -VALUES ('Sample blog', 'sample_blog', 'test.com', $1) -RETURNING id -"#, - ) - .bind(user_id.unwrap()) - .bind("Sample user".to_string()) - .bind("sample_user".to_string()) - .bind(email.to_string()) - .bind(password_hash) - .fetch_one(&mut *conn) - .await?; - - let blog_id = blog.get::("id"); - - // Send login request. - let req = test::TestRequest::post() - .uri("/v1/auth/login") - .set_json(LoginRequest { - email: email.to_string(), - password: password.to_string(), - remember_me: true, - code: None, - blog_domain: Some("test.com".to_string()), - }) - .to_request(); - let res = test::call_service(&app, req).await; - - assert!(res.status().is_success()); - - let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); - - assert_eq!(json.result, "success".to_string()); - assert!(json.blog_token.is_some()); - - let login_token = json.blog_token.unwrap(); - - // Verify login token. - let req = test::TestRequest::post() - .append_header(("origin", "https://test.com")) - .uri(&format!("/v1/blogs/{blog_id}/verify-login")) - .set_json(Request { token: login_token }) - .to_request(); - let res = test::call_service(&app, req).await; - - assert!(res.status().is_success()); - assert_response_body_text( - res, - &serde_json::to_string(&Response { - result: "success".to_string(), - }) - .unwrap_or_default(), - ) - .await; - - // Should remove previous sessions. - let sessions = get_user_sessions(redis_pool, user_id.unwrap()) - .await - .unwrap(); - - assert_eq!(sessions.len(), 1); - - Ok(()) - } - - #[test_context(RedisTestContext)] - #[sqlx::test] - async fn can_insert_client_device_and_location_into_the_session( - _ctx: &mut RedisTestContext, - pool: PgPool, - ) -> sqlx::Result<()> { - let mut conn = pool.acquire().await?; - let app = init_app_for_test( - services![test_login_get, login_post, post], - pool, - false, - false, - None, - ) - .await - .0; - - let (email, password_hash, password) = get_sample_email_and_password(); - - // Insert the user and blog. - let blog = sqlx::query( - r#" -WITH inserted_user AS ( - INSERT INTO users (name, username, email, password, email_verified) - VALUES ($1, $2, $3, $4, TRUE) - RETURNING id -) -INSERT INTO blogs (name, slug, domain, user_id) -VALUES ('Sample blog', 'sample_blog', 'test.com', (SELECT id FROM inserted_user)) -RETURNING id -"#, - ) - .bind("Sample user".to_string()) - .bind("sample_user".to_string()) - .bind(email.to_string()) - .bind(password_hash) - .fetch_one(&mut *conn) - .await?; - - let blog_id = blog.get::("id"); - - // Send login request. - let req = test::TestRequest::post() - .uri("/v1/auth/login") - .set_json(LoginRequest { - email: email.to_string(), - password: password.to_string(), - remember_me: true, - code: None, - blog_domain: Some("test.com".to_string()), - }) - .to_request(); - let res = test::call_service(&app, req).await; - - assert!(res.status().is_success()); - - let json = serde_json::from_str::(&res_to_string(res).await).unwrap(); - - assert_eq!(json.result, "success".to_string()); - assert!(json.blog_token.is_some()); - - let login_token = json.blog_token.unwrap(); - - // Verify login token. - let req = test::TestRequest::post() - .peer_addr(SocketAddr::from(SocketAddrV4::new( - Ipv4Addr::new(8, 8, 8, 8), - 8080, - ))) - .append_header(("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:40.0) Gecko/20100101 Firefox/40.0")) - .append_header(("origin", "https://test.com")) - .uri(&format!("/v1/blogs/{blog_id}/verify-login")) - .set_json(Request { token: login_token }) - .to_request(); - let res = test::call_service(&app, req).await; - - let cookie_value = res - .response() - .cookies() - .find(|cookie| cookie.name() == SESSION_COOKIE_NAME); - - assert!(res.status().is_success()); - assert!(cookie_value.is_some()); - - let cookie_value = cookie_value.unwrap(); - - // Should use the correct domain. - assert_eq!(cookie_value.domain(), Some("test.com")); - - let req = test::TestRequest::get() - .cookie(cookie_value) - .uri("/get-login-details") - .to_request(); - let res = test::call_service(&app, req).await; - - #[derive(Deserialize)] - struct ClientSession { - device: Option, - location: Option, - domain: Option, - } - - let client_session = test::read_body_json::(res).await; - - assert!(client_session.device.is_some()); - assert!(client_session.location.is_some()); - assert_eq!(client_session.domain, Some("test.com".to_string())); - - Ok(()) - } - } -} diff --git a/src/test_utils/test_grpc_service.rs b/src/test_utils/test_grpc_service.rs index f287ebc..ee409c6 100644 --- a/src/test_utils/test_grpc_service.rs +++ b/src/test_utils/test_grpc_service.rs @@ -10,6 +10,7 @@ use crate::{ }, test_utils::get_redis_pool, }; +use actix_web::cookie::Key; use sqlx::{ PgPool, Row, @@ -45,6 +46,8 @@ pub async fn test_grpc_service( ) where B: Future, { + let config = get_app_config().unwrap(); + let cookie_secret_key = Key::from(config.session_secret_key.as_bytes()); let mut user_id: i64 = 1_i64; if insert_user { @@ -74,12 +77,14 @@ RETURNING id // Redis let redis_pool = get_redis_pool(); + let server_future = async { let result = Server::builder() .add_service(ApiServiceServer::new(GrpcService { config: get_app_config().unwrap(), redis_pool: redis_pool.clone(), db_pool: db_pool.clone(), + cookie_secret_key, })) .serve_with_incoming(stream) .await; diff --git a/src/utils/get_client_device.rs b/src/utils/get_client_device.rs index 1d81414..5f70f6c 100644 --- a/src/utils/get_client_device.rs +++ b/src/utils/get_client_device.rs @@ -6,7 +6,7 @@ use serde::{ use std::borrow::Cow; use user_agent_parser::UserAgentParser; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ClientDevice { pub display_name: String, pub r#type: i32, @@ -15,7 +15,7 @@ pub struct ClientDevice { /// Parses and return client's device information from the user-agent string. /// /// * `ua` - User-agent string -/// * `parser` - User-agent parser instannce +/// * `parser` - User-agent parser instance pub fn get_client_device(ua: &str, parser: &UserAgentParser) -> ClientDevice { let device = parser.parse_device(ua); let os = parser.parse_os(ua); diff --git a/src/utils/get_client_location.rs b/src/utils/get_client_location.rs index 805141d..f2df929 100644 --- a/src/utils/get_client_location.rs +++ b/src/utils/get_client_location.rs @@ -8,7 +8,7 @@ use serde::{ }; use std::net::IpAddr; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ClientLocation { pub display_name: String, pub lat: Option, diff --git a/src/utils/get_user_sessions.rs b/src/utils/get_user_sessions.rs index 4a8de8b..d744032 100644 --- a/src/utils/get_user_sessions.rs +++ b/src/utils/get_user_sessions.rs @@ -19,11 +19,20 @@ use serde::{ #[derive(Debug, Default, Deserialize, Serialize)] pub struct UserSession { + /// The ID of the user. pub user_id: i64, + /// Timestamp of the session creation. pub created_at: i64, + /// The user device. pub device: Option, + /// The user location. pub location: Option, + /// The acknowledged flag. pub ack: bool, + /// The external blog flag. This is only set when the session belongs to a blog on an external + /// domain. + pub ext_blog: Option, + /// The domain of the blog that this session belongs to. pub domain: Option, } @@ -102,6 +111,7 @@ mod tests { user_id, created_at: OffsetDateTime::now_utc().unix_timestamp(), ack: false, + ext_blog: None, device: Some(ClientDevice { display_name: "Some device".to_string(), r#type: 0, diff --git a/tests/tables/blogs/blogs.rs b/tests/tables/blogs/blogs.rs index e0a0ba6..ebe9cdb 100644 --- a/tests/tables/blogs/blogs.rs +++ b/tests/tables/blogs/blogs.rs @@ -2469,54 +2469,4 @@ SELECT EXISTS ( Ok(()) } - - #[sqlx::test(fixtures("user"))] - async fn can_delete_login_tokens_on_blog_hard_delete(pool: PgPool) -> sqlx::Result<()> { - let mut conn = pool.acquire().await?; - let blog_id = (insert_sample_blog(&mut conn).await?).get::("id"); - let token_id = nanoid!(TOKEN_LENGTH); - - // Insert a login token. - let result = sqlx::query( - r#" -INSERT INTO blog_login_tokens (id, blog_id, user_id, expires_at) -VALUES ($1, $2, $3, NOW()) -"#, - ) - .bind(token_id) - .bind(blog_id) - .bind(1_i64) - .execute(&mut *conn) - .await?; - - assert_eq!(result.rows_affected(), 1); - - // Delete the blog - sqlx::query( - r#" -DELETE FROM blogs -WHERE id = $1 -"#, - ) - .bind(blog_id) - .execute(&mut *conn) - .await?; - - // Token should get deleted - let result = sqlx::query( - r#" -SELECT EXISTS ( - SELECT 1 FROM blog_login_tokens - WHERE blog_id = $1 -) -"#, - ) - .bind(blog_id) - .fetch_one(&mut *conn) - .await?; - - assert!(!result.get::("exists")); - - Ok(()) - } } diff --git a/tests/tables/users/users.rs b/tests/tables/users/users.rs index afbe6ee..4f441a3 100644 --- a/tests/tables/users/users.rs +++ b/tests/tables/users/users.rs @@ -12510,56 +12510,6 @@ SELECT EXISTS ( Ok(()) } - #[sqlx::test(fixtures("user", "blog"))] - async fn can_delete_blog_login_token_on_user_hard_delete(pool: PgPool) -> sqlx::Result<()> { - let mut conn = pool.acquire().await?; - let token_id = nanoid!(TOKEN_LENGTH); - - // Insert a token - let insert_result = sqlx::query( - r#" -INSERT INTO blog_login_tokens (id, user_id, blog_id, expires_at) -VALUES ($1, $2, $3, NOW()) -RETURNING id -"#, - ) - .bind(&token_id) - .bind(1_i16) - .bind(1_i64) - .execute(&mut *conn) - .await?; - - assert_eq!(insert_result.rows_affected(), 1); - - // Delete the user - sqlx::query( - r#" -DELETE FROM users -WHERE id = $1 -"#, - ) - .bind(1_i64) - .execute(&mut *conn) - .await?; - - // Login token should get deleted - let result = sqlx::query( - r#" -SELECT EXISTS ( - SELECT 1 FROM blog_login_tokens - WHERE id = $1 -) -"#, - ) - .bind(&token_id) - .fetch_one(&mut *conn) - .await?; - - assert!(!result.get::("exists")); - - Ok(()) - } - #[sqlx::test] async fn can_delete_transmitted_notifications_on_user_hard_delete( pool: PgPool, From ecca99db6c1af078775141594caf743c4ce2cf4e Mon Sep 17 00:00:00 2001 From: zignis Date: Sat, 26 Apr 2025 22:11:29 +0530 Subject: [PATCH 6/7] fix tests --- src/grpc/endpoints/verify_blog_login/mod.rs | 2 + .../verify_blog_login/verify_blog_login.rs | 224 +++++++++++++++++- 2 files changed, 216 insertions(+), 10 deletions(-) diff --git a/src/grpc/endpoints/verify_blog_login/mod.rs b/src/grpc/endpoints/verify_blog_login/mod.rs index 0dca71c..4edd9b7 100644 --- a/src/grpc/endpoints/verify_blog_login/mod.rs +++ b/src/grpc/endpoints/verify_blog_login/mod.rs @@ -1,3 +1,5 @@ mod verify_blog_login; pub use verify_blog_login::*; + +// TODO: Revert migration `blog_login_tokens` diff --git a/src/grpc/endpoints/verify_blog_login/verify_blog_login.rs b/src/grpc/endpoints/verify_blog_login/verify_blog_login.rs index ddd93ec..accabe6 100644 --- a/src/grpc/endpoints/verify_blog_login/verify_blog_login.rs +++ b/src/grpc/endpoints/verify_blog_login/verify_blog_login.rs @@ -52,7 +52,6 @@ use tracing::{ error, warn, }; -// TODO: Revert migration `blog_login_tokens` /// The TTL value (in seconds) for a user's session. static SESSION_TTL: i64 = Duration::weeks(1).whole_seconds(); @@ -455,10 +454,11 @@ mod tests { mod serial { use super::*; + use tonic::Code; #[test_context(RedisTestContext)] #[sqlx::test(fixtures("verify_blog_login"))] - async fn can_verify_blog_login(_ctx: &mut RedisTestContext, pool: PgPool) { + async fn can_verify_blog_login_by_id(_ctx: &mut RedisTestContext, pool: PgPool) { test_grpc_service( pool, true, @@ -548,10 +548,10 @@ SELECT EXISTS ( let result = sqlx::query( r#" SELECT last_login_at FROM users -WHERE username = $1 +WHERE id = $1 "#, ) - .bind("sample_user") + .bind(user_id) .fetch_one(&mut *db_conn) .await .unwrap(); @@ -661,9 +661,103 @@ WHERE username = $1 #[test_context(RedisTestContext)] #[sqlx::test(fixtures("verify_blog_login"))] - async fn can_handle_an_expired_token(_ctx: &mut RedisTestContext, _pool: PgPool) { + async fn can_verify_blog_login_by_slug(_ctx: &mut RedisTestContext, pool: PgPool) { test_grpc_service( - _pool, + pool, + true, + Box::new(|mut client, _pool, redis_pool, user_id| async move { + let config = get_app_config().unwrap(); + let mut redis_conn = redis_pool.get().await.unwrap(); + let user_id = user_id.unwrap(); + let blog_id = 1_i64; + let domain = "test.com"; + let (cache_key, token_id, _) = get_token(&config.token_salt); + + insert_login_token( + &cache_key, + &BlogLoginTokenData { + uid: user_id, + bid: blog_id, + persistent: true, + loc: None, + device: None, + }, + &mut *redis_conn, + ) + .await + .unwrap(); + + let response = client + .verify_blog_login(Request::new(VerifyBlogLoginRequest { + blog_identifier: "test-blog".to_string(), + token: token_id.to_string(), + host: domain.to_string(), + })) + .await; + + assert!(response.is_ok()); + + let response = response.unwrap().into_inner(); + + assert!(response.cookie_value.is_some()); + assert_eq!(response.is_token_valid, true); + }), + ) + .await; + } + + #[test_context(RedisTestContext)] + #[sqlx::test(fixtures("verify_blog_login"))] + async fn can_verify_blog_login_by_domain(_ctx: &mut RedisTestContext, pool: PgPool) { + test_grpc_service( + pool, + true, + Box::new(|mut client, _pool, redis_pool, user_id| async move { + let config = get_app_config().unwrap(); + let mut redis_conn = redis_pool.get().await.unwrap(); + let user_id = user_id.unwrap(); + let blog_id = 1_i64; + let domain = "test.com"; + let (cache_key, token_id, _) = get_token(&config.token_salt); + + insert_login_token( + &cache_key, + &BlogLoginTokenData { + uid: user_id, + bid: blog_id, + persistent: true, + loc: None, + device: None, + }, + &mut *redis_conn, + ) + .await + .unwrap(); + + let response = client + .verify_blog_login(Request::new(VerifyBlogLoginRequest { + blog_identifier: domain.to_string(), + token: token_id.to_string(), + host: domain.to_string(), + })) + .await; + + assert!(response.is_ok()); + + let response = response.unwrap().into_inner(); + + assert!(response.cookie_value.is_some()); + assert_eq!(response.is_token_valid, true); + }), + ) + .await; + } + + #[test_context(RedisTestContext)] + #[sqlx::test] + async fn can_handle_an_expired_token(_ctx: &mut RedisTestContext, pool: PgPool) { + test_grpc_service( + pool, true, Box::new(|mut client, _, redis_pool, user_id| async move { let config = get_app_config().unwrap(); @@ -715,10 +809,10 @@ WHERE username = $1 #[sqlx::test(fixtures("verify_blog_login"))] async fn can_reject_verification_for_unmatched_host( _ctx: &mut RedisTestContext, - _pool: PgPool, + pool: PgPool, ) { test_grpc_service( - _pool, + pool, true, Box::new(|mut client, _, redis_pool, user_id| async move { let config = get_app_config().unwrap(); @@ -759,14 +853,124 @@ WHERE username = $1 .await; } + #[test_context(RedisTestContext)] + #[sqlx::test(fixtures("verify_blog_login"))] + async fn can_reject_verification_for_unmatched_blog_id( + _ctx: &mut RedisTestContext, + pool: PgPool, + ) { + test_grpc_service( + pool, + true, + Box::new(|mut client, _, redis_pool, user_id| async move { + let config = get_app_config().unwrap(); + let mut redis_conn = redis_pool.get().await.unwrap(); + let user_id = user_id.unwrap(); + let blog_id = 10_i64; // Invalid blog ID. + let domain = "test.com"; + let (cache_key, token_id, _) = get_token(&config.token_salt); + + insert_login_token( + &cache_key, + &BlogLoginTokenData { + uid: user_id, + bid: blog_id, + persistent: true, + loc: None, + device: None, + }, + &mut *redis_conn, + ) + .await + .unwrap(); + + let response = client + .verify_blog_login(Request::new(VerifyBlogLoginRequest { + blog_identifier: "test-blog".to_string(), + token: token_id.to_string(), + host: domain.to_string(), + })) + .await; + + assert!(response.is_ok()); + + let response = response.unwrap().into_inner(); + + assert_eq!(response.is_token_valid, false); + }), + ) + .await; + } + + #[test_context(RedisTestContext)] + #[sqlx::test(fixtures("verify_blog_login"))] + async fn can_reject_verification_for_soft_deleted_blog( + _ctx: &mut RedisTestContext, + pool: PgPool, + ) { + test_grpc_service( + pool, + true, + Box::new(|mut client, pool, redis_pool, user_id| async move { + let config = get_app_config().unwrap(); + let mut redis_conn = redis_pool.get().await.unwrap(); + let user_id = user_id.unwrap(); + let blog_id = 1_i64; + let domain = "test.com"; + let (cache_key, token_id, _) = get_token(&config.token_salt); + + insert_login_token( + &cache_key, + &BlogLoginTokenData { + uid: user_id, + bid: blog_id, + persistent: true, + loc: None, + device: None, + }, + &mut *redis_conn, + ) + .await + .unwrap(); + + // Soft-delete the blog. + let result = sqlx::query( + r#" +UPDATE blogs +SET deleted_at = NOW() +WHERE id = $1 +"#, + ) + .bind(blog_id) + .execute(&pool) + .await + .unwrap(); + + assert_eq!(result.rows_affected(), 1); + + let response = client + .verify_blog_login(Request::new(VerifyBlogLoginRequest { + blog_identifier: blog_id.to_string(), + token: token_id.to_string(), + host: domain.to_string(), + })) + .await; + + assert!(response.is_err()); + assert_eq!(response.unwrap_err().code(), Code::NotFound); + }), + ) + .await; + } + #[test_context(RedisTestContext)] #[sqlx::test(fixtures("verify_blog_login"))] async fn can_clear_overflowing_sessions_on_verification( _ctx: &mut RedisTestContext, - _pool: PgPool, + pool: PgPool, ) { test_grpc_service( - _pool, + pool, true, Box::new(|mut client, _, redis_pool, user_id| async move { let config = get_app_config().unwrap(); From 2554b25f0018cb229a97349c66c2ac09365c3c53 Mon Sep 17 00:00:00 2001 From: zignis Date: Sat, 26 Apr 2025 22:58:27 +0530 Subject: [PATCH 7/7] cleanup --- src/grpc/endpoints/verify_blog_login/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/grpc/endpoints/verify_blog_login/mod.rs b/src/grpc/endpoints/verify_blog_login/mod.rs index 4edd9b7..0dca71c 100644 --- a/src/grpc/endpoints/verify_blog_login/mod.rs +++ b/src/grpc/endpoints/verify_blog_login/mod.rs @@ -1,5 +1,3 @@ mod verify_blog_login; pub use verify_blog_login::*; - -// TODO: Revert migration `blog_login_tokens`