feat: migrate imphnen-backend-qr into workspace as imphnen-qr crate

Ports the Go QR campaign overlay service to a self-contained Rust crate
nested at /v1/qr/... in the gateway.

Features:
- Auth: register, login, Google OAuth, JWT refresh (bcrypt compat with Go DB)
- Users: profile management + admin CRUD (list/role/delete)
- Campaigns: create (auto-generates QR PNG via qrcode crate), list,
  activate, delete; process-image endpoint overlays active campaign QR
  onto uploaded images (bottom-right corner, image crate)
- QR pool connects to imphnen_qr database via QR_DATABASE_URL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
maulanasdqn
2026-04-02 16:03:39 +07:00
co-authored by Claude Sonnet 4.6
parent 5715e75593
commit 4bba182ea3
51 changed files with 1942 additions and 0 deletions
@@ -0,0 +1,72 @@
use axum::{Extension, Json, response::IntoResponse};
use axum::extract::Query;
use std::sync::Arc;
use serde::Deserialize;
use imphnen_utils::response_format::ApiSuccess;
use imphnen_utils::errors::AppError;
use crate::auth::domain::service::QrAuthService;
use crate::config::QrConfig;
use super::dto::{RegisterRequest, LoginRequest, RefreshRequest, AuthResponse, TokensResponse};
pub async fn register_handler(
Extension(service): Extension<Arc<dyn QrAuthService>>,
Json(body): Json<RegisterRequest>,
) -> Result<axum::response::Response, AppError> {
let (tokens, user) = service.register(body.email, body.password, body.name).await?;
Ok(ApiSuccess(AuthResponse {
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
user,
}).into_response())
}
pub async fn login_handler(
Extension(service): Extension<Arc<dyn QrAuthService>>,
Json(body): Json<LoginRequest>,
) -> Result<axum::response::Response, AppError> {
let (tokens, user) = service.login(body.email, body.password).await?;
Ok(ApiSuccess(AuthResponse {
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
user,
}).into_response())
}
pub async fn google_redirect_handler(
Extension(config): Extension<Arc<QrConfig>>,
) -> Result<axum::response::Response, AppError> {
let url = format!(
"https://accounts.google.com/o/oauth2/v2/auth?client_id={}&redirect_uri={}&response_type=code&scope=email+profile",
config.google_client_id,
config.google_redirect_url,
);
Ok(axum::response::Redirect::temporary(&url).into_response())
}
#[derive(Debug, Deserialize)]
pub struct GoogleCallbackQuery {
pub code: String,
}
pub async fn google_callback_handler(
Extension(service): Extension<Arc<dyn QrAuthService>>,
Query(params): Query<GoogleCallbackQuery>,
) -> Result<axum::response::Response, AppError> {
let (tokens, user) = service.google_callback(params.code).await?;
Ok(ApiSuccess(AuthResponse {
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
user,
}).into_response())
}
pub async fn refresh_handler(
Extension(service): Extension<Arc<dyn QrAuthService>>,
Json(body): Json<RefreshRequest>,
) -> Result<axum::response::Response, AppError> {
let tokens = service.refresh_token(body.refresh_token).await?;
Ok(ApiSuccess(TokensResponse {
access_token: tokens.access_token,
refresh_token: tokens.refresh_token,
}).into_response())
}