diff --git a/README.md b/README.md index 2c64dbbe0..a7e61236f 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ This repository is a monorepo that contains many different pieces of Codebloom, - Our public task board on [Notion](https://www.notion.so/) which is used to define a relationship between tasks and PRs - [View public taskboard](https://codebloom.notion.site/) - [Goto `.github/scripts/notion/index.ts` to view custom PR/commit check validation implementation](./.github/scripts/notion/index.ts) -- Our backend which is written in [Java 25](https://dev.java/) and [Spring Boot 3](https://spring.io/projects/spring-boot). The backend interfaces with our database via [pgJDBC](https://jdbc.postgresql.org/), Discord via [JDA](https://jda.wiki/), [leetcode.com](https://leetcode.com) via a custom abstraction over their [GraphQL](https://graphql.org/) API as well as [Playwright](https://playwright.dev/java/) to obtain credentials to [leetcode.com](https://leetcode.com) via [GitHub OAuth](https://github.com). +- Our backend which is written in [Java 25](https://dev.java/) and [Spring Boot 3](https://spring.io/projects/spring-boot). The backend interfaces with our database via [pgJDBC](https://jdbc.postgresql.org/), Discord via [JDA](https://jda.wiki/), and [leetcode.com](https://leetcode.com) via a custom abstraction over their [GraphQL](https://graphql.org/) API, using credentials from [`leetcode-auth-service`](./internal/leetcode-auth-service/README.md). - [Goto `src/`](./src/) - [View detailed documentation](./src/README.md) - [View OpenAPI schema](https://codebloom.patinanetwork.org/swagger-ui/index.html) diff --git a/example.env b/example.env index 6410002fa..31a9a4982 100644 --- a/example.env +++ b/example.env @@ -44,9 +44,7 @@ SECRET_KEY= # Server URL, pretty self explanatory. # Why is this port 5173? Vite will forward api -# requests to the backend if needed. We also -# load some frontend URLs via Playwright so it -# needs to go through the frontend. +# requests to the backend if needed. SERVER_URL=http://localhost:5173 # read by leetcode-auth-service diff --git a/exclusions.ts b/exclusions.ts index 2f3703836..830f73e64 100644 --- a/exclusions.ts +++ b/exclusions.ts @@ -9,7 +9,6 @@ const frontendBaseDir = "js/src"; export const backendExclusions = [ `${backendBaseDir}/common/dto/**`, `${backendBaseDir}/config/**`, - `${backendBaseDir}/playwright/PlaywrightProvider.java`, `${backendBaseDir}/CodebloomApplication.java`, ]; export const frontendExclusions = [ diff --git a/internal/leetcode-auth-service/Cargo.lock b/internal/leetcode-auth-service/Cargo.lock index 7e5f5ba26..d2a3de957 100644 --- a/internal/leetcode-auth-service/Cargo.lock +++ b/internal/leetcode-auth-service/Cargo.lock @@ -921,6 +921,7 @@ dependencies = [ "pin-project-lite", "prost", "redis", + "strum", "tokio", "tonic", "tonic-health", @@ -1818,6 +1819,27 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/internal/leetcode-auth-service/Cargo.toml b/internal/leetcode-auth-service/Cargo.toml index 4193fb6dd..aa1409171 100644 --- a/internal/leetcode-auth-service/Cargo.toml +++ b/internal/leetcode-auth-service/Cargo.toml @@ -26,6 +26,7 @@ tracing = "0.1.44" tracing-subscriber = { version = "0.3.23", features = ["json", "env-filter"] } chrono = { version = "0.4.45", default-features = false, features = ["std", "clock"] } redis = { version = "1.8.1", default-features = false, features = ["tokio-comp", "connection-manager", "script"] } +strum = { version = "0.28.0", features = ["derive"] } [dev-dependencies] parking_lot = "0.12" diff --git a/internal/leetcode-auth-service/README.md b/internal/leetcode-auth-service/README.md index 8f2fbc4f9..60a0720b7 100644 --- a/internal/leetcode-auth-service/README.md +++ b/internal/leetcode-auth-service/README.md @@ -60,7 +60,13 @@ Zero or unparsable durations fall back to the default. - `/livez`: always `ok`. - `/readyz`: `503` when Redis does not answer `PING` within 3 seconds, so the pod is taken out of the Service until Redis is back. The pod starts and stays alive while Redis is down; the Redis connection opens on first use and reconnects on its own. -- `/metrics`: Prometheus, including `grpc_requests_total`, `grpc_request_duration_seconds` and `leetcode_auth_refresh_total{result="stored|lass_failed|store_failed"}`. +- `/metrics`: Prometheus. Besides process metrics, `grpc_requests_total` and `grpc_request_duration_seconds`: + - `leetcode_auth_refresh_total{result="stored|lass_failed|store_failed"}`: finished refreshes. + - `leetcode_auth_refresh_requests_total{source="rpc|schedule",result="started|locked|lock_error"}`: refresh requests. `locked` means another refresh held the lock and the request was ignored. + - `leetcode_auth_refresh_in_progress`: refreshes running in this pod. + - `leetcode_auth_lass_request_duration_seconds{status}`: LASS `Authenticate` latency by gRPC status, with buckets from 1s to 240s. + - `leetcode_auth_scheduled_checks_total{result="fresh|stale|missing|read_error"}`: scheduled staleness checks. + - `leetcode_auth_credentials_stored_timestamp_seconds`: when the stored credentials were written, as last seen by this pod. Credential age is `time() - max(leetcode_auth_credentials_stored_timestamp_seconds)`. - `/version`: `VERSION` env var. ## Code layout diff --git a/internal/leetcode-auth-service/src/config.rs b/internal/leetcode-auth-service/src/config.rs index 49cebea86..c59372032 100644 --- a/internal/leetcode-auth-service/src/config.rs +++ b/internal/leetcode-auth-service/src/config.rs @@ -66,9 +66,8 @@ impl Config { .filter(|v| !v.is_empty()) .unwrap_or_else(|| "localhost:50051".to_string()); let tls = var("GRPC_LASS_TLS") - .map(|b| b.parse::().ok()) - .flatten() - .unwrap_or_else(|| false); + .and_then(|b| b.parse::().ok()) + .unwrap_or(false); format!("{}{host}", if tls { "https://" } else { "http://" }) }, diff --git a/internal/leetcode-auth-service/src/grpc/auth.rs b/internal/leetcode-auth-service/src/grpc/auth.rs index 093d29916..baf69c5f2 100644 --- a/internal/leetcode-auth-service/src/grpc/auth.rs +++ b/internal/leetcode-auth-service/src/grpc/auth.rs @@ -7,7 +7,7 @@ use crate::{ GetTokenRequest, GetTokenResponse, RefreshTokenRequest, RefreshTokenResponse, leetcode_auth_service_server, }, - service::refresh::TokenManager, + service::refresh::{RefreshSource, TokenManager}, }; pub use leetcode_auth_service_server::LeetcodeAuthServiceServer; @@ -55,7 +55,7 @@ impl leetcode_auth_service_server::LeetcodeAuthServi ) -> Result, Status> { let started = self .token_manager - .trigger() + .trigger(RefreshSource::Rpc) .await .map_err(redis_status)? .is_some(); diff --git a/internal/leetcode-auth-service/src/grpc/metrics.rs b/internal/leetcode-auth-service/src/grpc/metrics.rs index 6bd1ffc8a..3dd52e69a 100644 --- a/internal/leetcode-auth-service/src/grpc/metrics.rs +++ b/internal/leetcode-auth-service/src/grpc/metrics.rs @@ -83,6 +83,28 @@ fn status_code_from(headers: &HeaderMap) -> Option<&'static str> { }) } +pub fn code_name(code: tonic::Code) -> &'static str { + match code { + tonic::Code::Ok => "OK", + tonic::Code::Cancelled => "CANCELLED", + tonic::Code::Unknown => "UNKNOWN", + tonic::Code::InvalidArgument => "INVALID_ARGUMENT", + tonic::Code::DeadlineExceeded => "DEADLINE_EXCEEDED", + tonic::Code::NotFound => "NOT_FOUND", + tonic::Code::AlreadyExists => "ALREADY_EXISTS", + tonic::Code::PermissionDenied => "PERMISSION_DENIED", + tonic::Code::ResourceExhausted => "RESOURCE_EXHAUSTED", + tonic::Code::FailedPrecondition => "FAILED_PRECONDITION", + tonic::Code::Aborted => "ABORTED", + tonic::Code::OutOfRange => "OUT_OF_RANGE", + tonic::Code::Unimplemented => "UNIMPLEMENTED", + tonic::Code::Internal => "INTERNAL", + tonic::Code::Unavailable => "UNAVAILABLE", + tonic::Code::DataLoss => "DATA_LOSS", + tonic::Code::Unauthenticated => "UNAUTHENTICATED", + } +} + fn record_grpc_request_metrics(grpc_method: Arc, status: &'static str, start: Instant) { // Unknown services and methods answer UNIMPLEMENTED. Their paths come from the client, so // collapse them into one label value to keep the series count bounded. @@ -160,25 +182,7 @@ impl http_body::Body for ObservedGrpcBody { Poll::Ready(Some(Ok(frame))) } Poll::Ready(Some(Err(status))) => { - let code = match status.code() { - tonic::Code::Ok => "OK", - tonic::Code::Cancelled => "CANCELLED", - tonic::Code::Unknown => "UNKNOWN", - tonic::Code::InvalidArgument => "INVALID_ARGUMENT", - tonic::Code::DeadlineExceeded => "DEADLINE_EXCEEDED", - tonic::Code::NotFound => "NOT_FOUND", - tonic::Code::AlreadyExists => "ALREADY_EXISTS", - tonic::Code::PermissionDenied => "PERMISSION_DENIED", - tonic::Code::ResourceExhausted => "RESOURCE_EXHAUSTED", - tonic::Code::FailedPrecondition => "FAILED_PRECONDITION", - tonic::Code::Aborted => "ABORTED", - tonic::Code::OutOfRange => "OUT_OF_RANGE", - tonic::Code::Unimplemented => "UNIMPLEMENTED", - tonic::Code::Internal => "INTERNAL", - tonic::Code::Unavailable => "UNAVAILABLE", - tonic::Code::DataLoss => "DATA_LOSS", - tonic::Code::Unauthenticated => "UNAUTHENTICATED", - }; + let code = code_name(status.code()); this.recorder.record(code); diff --git a/internal/leetcode-auth-service/src/lib.rs b/internal/leetcode-auth-service/src/lib.rs index 99b03a05c..680e73e49 100644 --- a/internal/leetcode-auth-service/src/lib.rs +++ b/internal/leetcode-auth-service/src/lib.rs @@ -3,5 +3,6 @@ pub mod db; pub mod domain; pub mod grpc; pub mod http; +pub mod observe; pub mod proto; pub mod service; diff --git a/internal/leetcode-auth-service/src/main.rs b/internal/leetcode-auth-service/src/main.rs index 12ecd899f..5bf3e6876 100644 --- a/internal/leetcode-auth-service/src/main.rs +++ b/internal/leetcode-auth-service/src/main.rs @@ -15,7 +15,7 @@ use leetcode_auth_service::{ metrics::GrpcRequestMetricsMiddleware, }, http::{router, state::AppState}, - proto, + observe, proto, service::refresh::TokenManager, }; @@ -44,9 +44,11 @@ fn init_tracing() { async fn main() -> anyhow::Result<()> { init_tracing(); - let prom_handle = PrometheusBuilder::new() + let prom_handle = observe::configure(PrometheusBuilder::new()) + .context("failed to configure Prometheus histogram buckets")? .install_recorder() .context("failed to install Prometheus recorder")?; + observe::describe(); let sys_collector = metrics_process::Collector::default(); sys_collector.describe(); diff --git a/internal/leetcode-auth-service/src/observe.rs b/internal/leetcode-auth-service/src/observe.rs new file mode 100644 index 000000000..e580ee06d --- /dev/null +++ b/internal/leetcode-auth-service/src/observe.rs @@ -0,0 +1,130 @@ +use std::time::Instant; + +use chrono::{DateTime, Utc}; +use metrics::{ + Unit, counter, describe_counter, describe_gauge, describe_histogram, gauge, histogram, +}; +use metrics_exporter_prometheus::{BuildError, Matcher, PrometheusBuilder}; +use strum::IntoStaticStr; +use tonic::Status; + +use crate::{ + grpc::metrics::code_name, + service::refresh::{RefreshOutcome, RefreshSource}, +}; + +const REFRESH_TOTAL: &str = "leetcode_auth_refresh_total"; +const REFRESH_REQUESTS: &str = "leetcode_auth_refresh_requests_total"; +const REFRESH_IN_PROGRESS: &str = "leetcode_auth_refresh_in_progress"; +const LASS_DURATION: &str = "leetcode_auth_lass_request_duration_seconds"; +const SCHEDULED_CHECKS: &str = "leetcode_auth_scheduled_checks_total"; +const CREDENTIALS_STORED_AT: &str = "leetcode_auth_credentials_stored_timestamp_seconds"; + +/// A LASS login drives a browser and may run until `GRPC_LASS_TIMEOUT_SECONDS` (180s by default). +const LASS_BUCKETS: &[f64] = &[ + 1.0, 5.0, 10.0, 20.0, 30.0, 45.0, 60.0, 90.0, 120.0, 180.0, 240.0, +]; + +/// Whether a refresh request took the lock. +#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)] +#[strum(serialize_all = "snake_case")] +pub enum RefreshRequestResult { + Started, + /// Another refresh holds the lock, so the request was ignored. + Locked, + LockError, +} + +/// What a scheduled staleness check found. +#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)] +#[strum(serialize_all = "snake_case")] +pub enum ScheduledCheck { + Fresh, + Stale, + Missing, + ReadError, +} + +pub fn configure(builder: PrometheusBuilder) -> Result { + builder.set_buckets_for_metric(Matcher::Full(LASS_DURATION.to_string()), LASS_BUCKETS) +} + +pub fn describe() { + describe_counter!( + REFRESH_TOTAL, + "Finished refreshes by result: stored, lass_failed or store_failed." + ); + describe_counter!( + REFRESH_REQUESTS, + "Refresh requests by source (rpc or schedule) and result: started, locked (another refresh holds the lock) or lock_error." + ); + describe_gauge!( + REFRESH_IN_PROGRESS, + "Refreshes currently running in this pod." + ); + describe_histogram!( + LASS_DURATION, + Unit::Seconds, + "Duration of LASS Authenticate calls by gRPC status." + ); + describe_counter!( + SCHEDULED_CHECKS, + "Scheduled staleness checks by result: fresh, stale, missing or read_error." + ); + describe_gauge!( + CREDENTIALS_STORED_AT, + Unit::Seconds, + "Unix time the stored credentials were written, as last seen by this pod." + ); +} + +pub fn record_refresh(outcome: RefreshOutcome) { + let result: &'static str = outcome.into(); + counter!(REFRESH_TOTAL, "result" => result).increment(1); +} + +pub fn record_refresh_request(source: RefreshSource, result: RefreshRequestResult) { + let source: &'static str = source.into(); + let result: &'static str = result.into(); + counter!(REFRESH_REQUESTS, "source" => source, "result" => result).increment(1); +} + +pub fn record_scheduled_check(result: ScheduledCheck) { + let result: &'static str = result.into(); + counter!(SCHEDULED_CHECKS, "result" => result).increment(1); +} + +pub fn record_credentials_stored_at(date_stored: DateTime) { + gauge!(CREDENTIALS_STORED_AT).set(date_stored.timestamp_millis() as f64 / 1000.0); +} + +pub async fn time_lass_call(call: impl Future>) -> Result { + let start = Instant::now(); + let result = call.await; + let status = match &result { + Ok(_) => "OK", + Err(status) => code_name(status.code()), + }; + histogram!(LASS_DURATION, "status" => status).record(start.elapsed().as_secs_f64()); + result +} + +pub async fn track_refresh(refresh: impl Future) -> T { + let _in_progress = InProgress::start(); + refresh.await +} + +struct InProgress; + +impl InProgress { + fn start() -> Self { + gauge!(REFRESH_IN_PROGRESS).increment(1.0); + Self + } +} + +impl Drop for InProgress { + fn drop(&mut self) { + gauge!(REFRESH_IN_PROGRESS).decrement(1.0); + } +} diff --git a/internal/leetcode-auth-service/src/service/refresh.rs b/internal/leetcode-auth-service/src/service/refresh.rs index b5ade3ab0..4648302c2 100644 --- a/internal/leetcode-auth-service/src/service/refresh.rs +++ b/internal/leetcode-auth-service/src/service/refresh.rs @@ -1,30 +1,34 @@ use std::time::Duration; use chrono::Utc; -use metrics::counter; use redis::RedisError; +use strum::IntoStaticStr; use tokio::{task::JoinHandle, time::MissedTickBehavior}; use tracing::{debug, error, info, warn}; -use crate::{db::auth::AuthRepository, grpc::client::lass::LASS}; +use crate::{ + db::auth::AuthRepository, + grpc::client::lass::LASS, + observe::{self, RefreshRequestResult, ScheduledCheck}, +}; + +/// Who asked for a refresh. Also the `source` metric label. +#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)] +#[strum(serialize_all = "snake_case")] +pub enum RefreshSource { + Rpc, + Schedule, +} -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Also the `result` label on `leetcode_auth_refresh_total`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr)] +#[strum(serialize_all = "snake_case")] pub enum RefreshOutcome { Stored, LassFailed, StoreFailed, } -impl RefreshOutcome { - fn as_str(self) -> &'static str { - match self { - Self::Stored => "stored", - Self::LassFailed => "lass_failed", - Self::StoreFailed => "store_failed", - } - } -} - #[derive(Clone)] pub struct TokenManager { repository: R, @@ -54,28 +58,43 @@ impl TokenManager { /// Starts a refresh in the background. `None` means another refresh holds the lock and this /// call was ignored. If the refresh panics, the lock is not released and expires after /// `lock_ttl`. - pub async fn trigger(&self) -> Result>, RedisError> { + pub async fn trigger( + &self, + source: RefreshSource, + ) -> Result>, RedisError> { let owner = lock_owner(&self.hostname); - if !self.repository.try_lock(&owner, self.lock_ttl).await? { + let locked = self.repository.try_lock(&owner, self.lock_ttl).await; + observe::record_refresh_request( + source, + match locked { + Ok(true) => RefreshRequestResult::Started, + Ok(false) => RefreshRequestResult::Locked, + Err(_) => RefreshRequestResult::LockError, + }, + ); + + if !locked? { return Ok(None); } let this = self.clone(); Ok(Some(tokio::spawn(async move { - let outcome = this.refresh().await; + let outcome = observe::track_refresh(this.refresh()).await; if let Err(error) = this.repository.unlock(&owner).await { warn!(%error, "could not release refresh lock, it will expire on its own"); } - counter!("leetcode_auth_refresh_total", "result" => outcome.as_str()).increment(1); + observe::record_refresh(outcome); outcome }))) } async fn refresh(&self) -> RefreshOutcome { info!("requesting new credentials from LASS"); - let auth = match self.authenticator.authenticate().await { + let result = observe::time_lass_call(self.authenticator.authenticate()).await; + + let auth = match result { Ok(auth) => auth, Err(status) => { warn!(code = ?status.code(), message = status.message(), "LASS refresh failed"); @@ -86,6 +105,7 @@ impl TokenManager { let date_stored = Utc::now(); match self.repository.store(&auth, date_stored).await { Ok(()) => { + observe::record_credentials_stored_at(date_stored); info!(%date_stored, "stored new credentials"); RefreshOutcome::Stored } @@ -103,18 +123,29 @@ impl TokenManager { let stored = match self.repository.get().await { Ok(stored) => stored, Err(error) => { + observe::record_scheduled_check(ScheduledCheck::ReadError); warn!(%error, "could not read stored credentials, skipping scheduled refresh"); return None; } }; + if let (Some(_), Some(date_stored)) = (&stored.auth, stored.date_stored) { + observe::record_credentials_stored_at(date_stored); + } + if !stored.is_stale(Utc::now(), self.max_age) { + observe::record_scheduled_check(ScheduledCheck::Fresh); debug!("stored credentials are fresh"); return None; } + observe::record_scheduled_check(if stored.auth.is_none() { + ScheduledCheck::Missing + } else { + ScheduledCheck::Stale + }); info!(date_stored = ?stored.date_stored, "stored credentials are stale"); - match self.trigger().await { + match self.trigger(RefreshSource::Schedule).await { Ok(Some(running)) => Some(running), Ok(None) => { info!("another refresh holds the lock, skipping scheduled refresh"); @@ -188,12 +219,18 @@ mod tests { let token_refresher = token_refresher(&repository, &lass); let running = token_refresher - .trigger() + .trigger(RefreshSource::Rpc) .await .unwrap() .expect("first trigger starts a refresh"); for _ in 0..5 { - assert!(token_refresher.trigger().await.unwrap().is_none()); + assert!( + token_refresher + .trigger(RefreshSource::Rpc) + .await + .unwrap() + .is_none() + ); } lass.release(); @@ -204,7 +241,7 @@ mod tests { lass.release(); let next = token_refresher - .trigger() + .trigger(RefreshSource::Rpc) .await .unwrap() .expect("a finished refresh allows the next one"); @@ -219,7 +256,13 @@ mod tests { repository.set_lock(Some("other-pod:0")); let token_refresher = token_refresher(&repository, &lass); - assert!(token_refresher.trigger().await.unwrap().is_none()); + assert!( + token_refresher + .trigger(RefreshSource::Rpc) + .await + .unwrap() + .is_none() + ); assert!(token_refresher.refresh_if_stale().await.is_none()); assert_eq!(lass.calls(), 0); assert_eq!(repository.snapshot().lock.as_deref(), Some("other-pod:0")); @@ -232,7 +275,7 @@ mod tests { let lass = FakeAuthenticator::new(Outcome::Fail); let running = token_refresher(&repository, &lass) - .trigger() + .trigger(RefreshSource::Rpc) .await .unwrap() .unwrap(); @@ -246,7 +289,7 @@ mod tests { let repository = FakeAuthRepository::stored(stored("old", TimeDelta::hours(5))); let lass = FakeAuthenticator::gated(Outcome::Succeed(token("ours"))); let running = token_refresher(&repository, &lass) - .trigger() + .trigger(RefreshSource::Rpc) .await .unwrap() .unwrap(); @@ -287,7 +330,7 @@ mod tests { let token_refresher = token_refresher(&FakeAuthRepository::Unavailable, &lass); assert!(token_refresher.refresh_if_stale().await.is_none()); - assert!(token_refresher.trigger().await.is_err()); + assert!(token_refresher.trigger(RefreshSource::Rpc).await.is_err()); assert_eq!(lass.calls(), 0); } } diff --git a/src/README.md b/src/README.md index c65087a00..0b4ef780c 100644 --- a/src/README.md +++ b/src/README.md @@ -80,8 +80,6 @@ src/main │   │   ├── JDAClientManager.java # wires up JDAClient │   │   ├── JDACommandRegisterHandler.java # registers slash commands │   │   └── properties -│   ├── playwright # external playwright client -│   │   └── PlaywrightClient.java │   ├── scheduled # holds all background processes │   │   ├── discord # used to send weekly leaderboard to discord │   │   ├── duel # WIP: used to cleanup expired duels @@ -229,7 +227,7 @@ The [`LeetcodeClient.java`](https://github.com/tahminator/codebloom/blob/main/sr - [`leetcode-auth-service`](../internal/leetcode-auth-service/README.md) owns credential storage in Redis and schedules refreshes. - That service calls [`leetcode-auth-stealer-service`](../internal/leetcode-auth-stealer-service/README.md) to authenticate with LeetCode using email and password. -- The backend no longer runs a scheduled Playwright/GitHub OAuth stealer or stores LeetCode credentials in Postgres. The legacy `Auth` table is retained but unused by the backend; Codebloom user sessions are separate and unchanged. +- The backend no longer runs a scheduled GitHub OAuth stealer or stores LeetCode credentials in Postgres. The legacy `Auth` table is retained but unused by the backend; Codebloom user sessions are separate and unchanged. - Under the `ci` Spring profile, `CiLeetcodeAuthenticationService` returns empty credentials without calling the auth service. This is necessary because leetcode.com requires authenticated requests for most API calls. diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/auth/Auth.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/auth/Auth.java index dde8ed0db..587b24599 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/models/auth/Auth.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/auth/Auth.java @@ -15,6 +15,7 @@ @Jacksonized @EqualsAndHashCode @ToString +@Deprecated public class Auth { private String id; diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepository.java b/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepository.java index 11aeaecf6..fd196f95d 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepository.java +++ b/src/main/java/org/patinanetwork/codebloom/common/db/repos/auth/AuthRepository.java @@ -3,6 +3,7 @@ import java.util.Optional; import org.patinanetwork.codebloom.common.db.models.auth.Auth; +@Deprecated public interface AuthRepository { /** * NOTE - Modifies the passed in Auth object and overrides any new properties from the database. @@ -13,6 +14,7 @@ public interface AuthRepository { *
  • csrf * */ + @Deprecated void createAuth(Auth auth); /** @@ -24,11 +26,15 @@ public interface AuthRepository { *
  • csrf * */ + @Deprecated boolean updateAuthById(Auth auth); + @Deprecated Optional getAuthById(String id); + @Deprecated Optional getMostRecentAuth(); + @Deprecated boolean deleteAuthById(String id); } diff --git a/src/main/java/org/patinanetwork/codebloom/common/leetcode/auth/DefaultLeetcodeAuthenticationService.java b/src/main/java/org/patinanetwork/codebloom/common/leetcode/auth/DefaultLeetcodeAuthenticationService.java index d8095ef80..61f138604 100644 --- a/src/main/java/org/patinanetwork/codebloom/common/leetcode/auth/DefaultLeetcodeAuthenticationService.java +++ b/src/main/java/org/patinanetwork/codebloom/common/leetcode/auth/DefaultLeetcodeAuthenticationService.java @@ -1,5 +1,6 @@ package org.patinanetwork.codebloom.common.leetcode.auth; +import io.micrometer.core.annotation.Timed; import java.util.Optional; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -13,6 +14,7 @@ @Service @Slf4j @AllArgsConstructor +@Timed @Profile("!ci") public class DefaultLeetcodeAuthenticationService implements LeetcodeAuthenticationService {