Compare commits

..

No commits in common. "0d6810f3d52e5159c37b788ad0f1300701185ca9" and "dd0567c937b9a4db59ba140be10f548da7e68313" have entirely different histories.

36 changed files with 27 additions and 428 deletions

1
backend/.gitignore vendored
View File

@ -1,2 +1,3 @@
.env .env
/.sqlx
/target /target

View File

@ -1,28 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id, mime_type\n FROM image\n WHERE id = $1 AND deleted_time IS NULL\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "mime_type",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Int4"
]
},
"nullable": [
false,
false
]
},
"hash": "1926140fd0232511d302cd514f41af1e619a1c68b94e18cdc53234c9de701390"
}

View File

@ -1,22 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO image (mime_type)\n VALUES ($1)\n RETURNING id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Varchar"
]
},
"nullable": [
false
]
},
"hash": "715922e4ffa6881f23ea890ebf77abd86937c3f4fe606572156a29d4441028e9"
}

View File

@ -1,47 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id, issuer, source_id, displayed_name, email\n FROM \"user\"\n WHERE issuer = $1 AND source_id = $2\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "issuer",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "source_id",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "displayed_name",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "email",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "9c3ae9a539390e3b0493d325439fdc73cb2925bdb17330a21db4341e5822291b"
}

View File

@ -1,25 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO \"user\" (issuer, source_id, displayed_name, email)\n VALUES ($1, $2, $3, $4)\n RETURNING id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar"
]
},
"nullable": [
false
]
},
"hash": "e9741186ea464ef1ba3598223ad9f042ec876cbdc4e2d9eca787ff1de598551c"
}

1
backend/Cargo.lock generated
View File

@ -428,7 +428,6 @@ dependencies = [
"log", "log",
"openidconnect", "openidconnect",
"serde", "serde",
"sqlx",
] ]
[[package]] [[package]]

View File

@ -10,4 +10,3 @@ async-trait.workspace = true
log.workspace = true log.workspace = true
openidconnect.workspace = true openidconnect.workspace = true
serde.workspace = true serde.workspace = true
sqlx.workspace = true

View File

@ -4,7 +4,7 @@ use crate::domain::entity::user::User;
#[derive(Serialize)] #[derive(Serialize)]
pub struct UserResponseDto { pub struct UserResponseDto {
pub id: i32, pub source_id: String,
pub displayed_name: String, pub displayed_name: String,
pub email: String, pub email: String,
} }
@ -12,7 +12,7 @@ pub struct UserResponseDto {
impl From<User> for UserResponseDto { impl From<User> for UserResponseDto {
fn from(user: User) -> Self { fn from(user: User) -> Self {
UserResponseDto { UserResponseDto {
id: user.id, source_id: user.source_id,
displayed_name: user.displayed_name, displayed_name: user.displayed_name,
email: user.email, email: user.email,
} }

View File

@ -1,5 +1,3 @@
pub mod oidc_claims_response_dto;
pub mod auth_oidc_service; pub mod auth_oidc_service;
pub mod auth_repository_impl; pub mod auth_repository_impl;
pub mod oidc_claims_response_dto;
pub mod user_db_service;
pub mod user_db_mapper;

View File

@ -3,9 +3,7 @@ use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use crate::{ use crate::{
adapter::gateway::{ adapter::gateway::auth_oidc_service::AuthOidcService,
auth_oidc_service::AuthOidcService, user_db_service::UserDbService, user_db_mapper::UserMapper,
},
application::{ application::{
error::auth_error::AuthError, gateway::auth_repository::AuthRepository, error::auth_error::AuthError, gateway::auth_repository::AuthRepository,
use_case::get_auth_url_use_case::AuthUrl, use_case::get_auth_url_use_case::AuthUrl,
@ -14,19 +12,12 @@ use crate::{
}; };
pub struct AuthRepositoryImpl { pub struct AuthRepositoryImpl {
user_db_service: Arc<dyn UserDbService>,
auth_oidc_service: Arc<dyn AuthOidcService>, auth_oidc_service: Arc<dyn AuthOidcService>,
} }
impl AuthRepositoryImpl { impl AuthRepositoryImpl {
pub fn new( pub fn new(auth_oidc_service: Arc<dyn AuthOidcService>) -> Self {
user_db_service: Arc<dyn UserDbService>, Self { auth_oidc_service }
auth_oidc_service: Arc<dyn AuthOidcService>,
) -> Self {
Self {
user_db_service,
auth_oidc_service,
}
} }
} }
@ -46,21 +37,4 @@ impl AuthRepository for AuthRepositoryImpl {
.await .await
.map(|dto| dto.into_entity()) .map(|dto| dto.into_entity())
} }
async fn get_user_by_source_id(
&self,
issuer: &str,
source_id: &str,
) -> Result<User, AuthError> {
self.user_db_service
.get_user_by_source_id(issuer, source_id)
.await
.map(|mapper| mapper.into_entity())
}
async fn save_user(&self, user: User) -> Result<i32, AuthError> {
self.user_db_service
.create_user(UserMapper::from(user))
.await
}
} }

View File

@ -2,7 +2,6 @@ use crate::domain::entity::user::User;
pub struct OidcClaimsResponseDto { pub struct OidcClaimsResponseDto {
pub sub: String, pub sub: String,
pub issuer: String,
pub preferred_username: Option<String>, pub preferred_username: Option<String>,
pub email: Option<String>, pub email: Option<String>,
} }
@ -10,8 +9,6 @@ pub struct OidcClaimsResponseDto {
impl OidcClaimsResponseDto { impl OidcClaimsResponseDto {
pub fn into_entity(self) -> User { pub fn into_entity(self) -> User {
User { User {
id: -1,
issuer: self.issuer,
source_id: self.sub, source_id: self.sub,
displayed_name: self.preferred_username.unwrap_or_default(), displayed_name: self.preferred_username.unwrap_or_default(),
email: self.email.unwrap_or_default(), email: self.email.unwrap_or_default(),

View File

@ -1,33 +0,0 @@
use crate::domain::entity::user::User;
pub struct UserMapper {
pub id: i32,
pub issuer: String,
pub source_id: String,
pub displayed_name: String,
pub email: String,
}
impl From<User> for UserMapper {
fn from(user: User) -> Self {
Self {
id: user.id,
issuer: user.issuer,
source_id: user.source_id,
displayed_name: user.displayed_name,
email: user.email,
}
}
}
impl UserMapper {
pub fn into_entity(self) -> User {
User {
id: self.id,
issuer: self.issuer,
source_id: self.source_id,
displayed_name: self.displayed_name,
email: self.email,
}
}
}

View File

@ -1,14 +0,0 @@
use async_trait::async_trait;
use crate::{adapter::gateway::user_db_mapper::UserMapper, application::error::auth_error::AuthError};
#[async_trait]
pub trait UserDbService: Send + Sync {
async fn get_user_by_source_id(
&self,
issuer: &str,
source_id: &str,
) -> Result<UserMapper, AuthError>;
async fn create_user(&self, user: UserMapper) -> Result<i32, AuthError>;
}

View File

@ -1,10 +1,8 @@
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum AuthError { pub enum AuthError {
DatabaseError(String),
OidcError(String), OidcError(String),
InvalidState, InvalidState,
InvalidNonce, InvalidNonce,
InvalidAuthCode, InvalidAuthCode,
InvalidIdToken, InvalidIdToken,
UserNotFound,
} }

View File

@ -8,12 +8,6 @@ use crate::{
#[async_trait] #[async_trait]
pub trait AuthRepository: Send + Sync { pub trait AuthRepository: Send + Sync {
fn get_auth_url(&self) -> Result<AuthUrl, AuthError>; fn get_auth_url(&self) -> Result<AuthUrl, AuthError>;
async fn exchange_auth_code(&self, code: &str, expected_nonce: &str) async fn exchange_auth_code(&self, code: &str, expected_nonce: &str)
-> Result<User, AuthError>; -> Result<User, AuthError>;
async fn get_user_by_source_id(&self, issuer: &str, source_id: &str)
-> Result<User, AuthError>;
async fn save_user(&self, user: User) -> Result<i32, AuthError>;
} }

View File

@ -41,32 +41,8 @@ impl ExchangeAuthCodeUseCase for ExchangeAuthCodeUseCaseImpl {
return Err(AuthError::InvalidState); return Err(AuthError::InvalidState);
} }
let mut logged_in_user = self self.auth_repository
.auth_repository
.exchange_auth_code(code, expected_nonce) .exchange_auth_code(code, expected_nonce)
.await?; .await
let saved_user_result = self
.auth_repository
.get_user_by_source_id(&logged_in_user.issuer, &logged_in_user.source_id)
.await;
match saved_user_result {
Ok(user) => {
logged_in_user.id = user.id;
}
Err(AuthError::UserNotFound) => {
let id = self
.auth_repository
.save_user(logged_in_user.clone())
.await?;
logged_in_user.id = id;
}
Err(e) => {
return Err(e);
}
};
Ok(logged_in_user)
} }
} }

View File

@ -1,7 +1,4 @@
#[derive(Clone)]
pub struct User { pub struct User {
pub id: i32,
pub issuer: String,
pub source_id: String, pub source_id: String,
pub displayed_name: String, pub displayed_name: String,
pub email: String, pub email: String,

View File

@ -1,3 +1,2 @@
pub mod db;
pub mod oidc; pub mod oidc;
pub mod web; pub mod web;

View File

@ -1,2 +0,0 @@
pub mod user_db_service_impl;
pub mod user_record;

View File

@ -1,65 +0,0 @@
use async_trait::async_trait;
use sqlx::{Pool, Postgres};
use crate::{
adapter::gateway::{user_db_service::UserDbService, user_db_mapper::UserMapper},
application::error::auth_error::AuthError,
framework::db::user_record::UserRecord,
};
pub struct UserDbServiceImpl {
db_pool: Pool<Postgres>,
}
impl UserDbServiceImpl {
pub fn new(db_pool: Pool<Postgres>) -> Self {
Self { db_pool }
}
}
#[async_trait]
impl UserDbService for UserDbServiceImpl {
async fn get_user_by_source_id(
&self,
issuer: &str,
source_id: &str,
) -> Result<UserMapper, AuthError> {
let record = sqlx::query_as!(
UserRecord,
r#"
SELECT id, issuer, source_id, displayed_name, email
FROM "user"
WHERE issuer = $1 AND source_id = $2
"#,
issuer,
source_id
)
.fetch_optional(&self.db_pool)
.await
.map_err(|e| AuthError::DatabaseError(e.to_string()))?;
match record {
Some(record) => Ok(record.into_mapper()),
None => Err(AuthError::UserNotFound),
}
}
async fn create_user(&self, user: UserMapper) -> Result<i32, AuthError> {
let id = sqlx::query_scalar!(
r#"
INSERT INTO "user" (issuer, source_id, displayed_name, email)
VALUES ($1, $2, $3, $4)
RETURNING id
"#,
user.issuer,
user.source_id,
user.displayed_name,
user.email
)
.fetch_one(&self.db_pool)
.await
.map_err(|e| AuthError::DatabaseError(e.to_string()))?;
Ok(id)
}
}

View File

@ -1,24 +0,0 @@
use sqlx::FromRow;
use crate::adapter::gateway::user_db_mapper::UserMapper;
#[derive(FromRow)]
pub struct UserRecord {
pub id: i32,
pub issuer: String,
pub source_id: String,
pub displayed_name: String,
pub email: String,
}
impl UserRecord {
pub fn into_mapper(self) -> UserMapper {
UserMapper {
id: self.id,
issuer: self.issuer,
source_id: self.source_id,
displayed_name: self.displayed_name,
email: self.email,
}
}
}

View File

@ -93,8 +93,6 @@ impl AuthOidcService for AuthOidcServiceImpl {
) )
.map_err(|_| AuthError::InvalidIdToken)?; .map_err(|_| AuthError::InvalidIdToken)?;
let issuer = claims.issuer().to_string();
let preferred_username = claims let preferred_username = claims
.preferred_username() .preferred_username()
.map(|username| username.to_string()); .map(|username| username.to_string());
@ -103,7 +101,6 @@ impl AuthOidcService for AuthOidcServiceImpl {
Ok(OidcClaimsResponseDto { Ok(OidcClaimsResponseDto {
sub: claims.subject().to_string(), sub: claims.subject().to_string(),
issuer: issuer,
preferred_username: preferred_username, preferred_username: preferred_username,
email: email, email: email,
}) })

View File

@ -1,3 +1 @@
pub mod auth_web_routes; pub mod auth_web_routes;
mod constants;

View File

@ -6,11 +6,12 @@ use crate::{
auth_controller::AuthController, oidc_callback_query_dto::OidcCallbackQueryDto, auth_controller::AuthController, oidc_callback_query_dto::OidcCallbackQueryDto,
}, },
application::error::auth_error::AuthError, application::error::auth_error::AuthError,
framework::web::constants::{
SESSION_KEY_AUTH_NONCE, SESSION_KEY_AUTH_STATE, SESSION_KEY_USER_ID,
},
}; };
const SESSION_KEY_AUTH_STATE: &str = "auth_state";
const SESSION_KEY_AUTH_NONCE: &str = "auth_nonce";
const SESSION_KEY_USER: &str = "user";
pub fn configure_auth_routes(cfg: &mut web::ServiceConfig) { pub fn configure_auth_routes(cfg: &mut web::ServiceConfig) {
cfg.service( cfg.service(
web::scope("/auth") web::scope("/auth")
@ -28,11 +29,11 @@ async fn oidc_login_handler(
match result { match result {
Ok(auth_url) => { Ok(auth_url) => {
if let Err(e) = session.insert::<String>(SESSION_KEY_AUTH_STATE, auth_url.state) { if let Err(e) = session.insert(SESSION_KEY_AUTH_STATE, auth_url.state) {
log::error!("{e:?}"); log::error!("{e:?}");
return HttpResponse::InternalServerError().finish(); return HttpResponse::InternalServerError().finish();
} }
if let Err(e) = session.insert::<String>(SESSION_KEY_AUTH_NONCE, auth_url.nonce) { if let Err(e) = session.insert(SESSION_KEY_AUTH_NONCE, auth_url.nonce) {
log::error!("{e:?}"); log::error!("{e:?}");
return HttpResponse::InternalServerError().finish(); return HttpResponse::InternalServerError().finish();
} }
@ -52,12 +53,12 @@ async fn oidc_callback_handler(
query: web::Query<OidcCallbackQueryDto>, query: web::Query<OidcCallbackQueryDto>,
session: Session, session: Session,
) -> impl Responder { ) -> impl Responder {
let expected_state = match session.get::<String>(SESSION_KEY_AUTH_STATE) { let expected_state: String = match session.get(SESSION_KEY_AUTH_STATE) {
Ok(Some(state)) => state, Ok(Some(state)) => state,
_ => return HttpResponse::BadRequest().finish(), _ => return HttpResponse::BadRequest().finish(),
}; };
let expected_nonce = match session.get::<String>(SESSION_KEY_AUTH_NONCE) { let expected_nonce: String = match session.get(SESSION_KEY_AUTH_NONCE) {
Ok(Some(nonce)) => nonce, Ok(Some(nonce)) => nonce,
_ => return HttpResponse::BadRequest().finish(), _ => return HttpResponse::BadRequest().finish(),
}; };
@ -70,7 +71,7 @@ async fn oidc_callback_handler(
session.remove(SESSION_KEY_AUTH_NONCE); session.remove(SESSION_KEY_AUTH_NONCE);
match result { match result {
Ok(user) => { Ok(user) => {
if let Err(e) = session.insert::<i32>(SESSION_KEY_USER_ID, user.id) { if let Err(e) = session.insert(SESSION_KEY_USER, user) {
log::error!("{e:?}"); log::error!("{e:?}");
return HttpResponse::InternalServerError().finish(); return HttpResponse::InternalServerError().finish();
} }
@ -94,7 +95,7 @@ async fn oidc_callback_handler(
async fn logout_handler(session: Session) -> impl Responder { async fn logout_handler(session: Session) -> impl Responder {
session.remove(SESSION_KEY_AUTH_STATE); session.remove(SESSION_KEY_AUTH_STATE);
session.remove(SESSION_KEY_AUTH_NONCE); session.remove(SESSION_KEY_AUTH_NONCE);
session.remove(SESSION_KEY_USER_ID); session.remove(SESSION_KEY_USER);
HttpResponse::Found() HttpResponse::Found()
.append_header((header::LOCATION, "/")) .append_header((header::LOCATION, "/"))
.finish() .finish()

View File

@ -1,3 +0,0 @@
pub const SESSION_KEY_AUTH_STATE: &str = "auth_state";
pub const SESSION_KEY_AUTH_NONCE: &str = "auth_nonce";
pub const SESSION_KEY_USER_ID: &str = "user_id";

View File

@ -8,7 +8,7 @@ pub struct ImageRequestDto {
impl ImageRequestDto { impl ImageRequestDto {
pub fn into_entity(self) -> Image { pub fn into_entity(self) -> Image {
Image { Image {
id: -1, id: None,
mime_type: self.mime_type, mime_type: self.mime_type,
data: self.data, data: self.data,
} }

View File

@ -1,7 +1,7 @@
use crate::domain::entity::image::Image; use crate::domain::entity::image::Image;
pub struct ImageDbMapper { pub struct ImageDbMapper {
pub id: i32, pub id: Option<i32>,
pub mime_type: String, pub mime_type: String,
} }

View File

@ -1,5 +1,5 @@
pub struct Image { pub struct Image {
pub id: i32, pub id: Option<i32>,
pub mime_type: String, pub mime_type: String,
pub data: Vec<u8>, pub data: Vec<u8>,
} }

View File

@ -54,7 +54,7 @@ impl ImageDbService for ImageDbServiceImpl {
match image_record { match image_record {
Ok(record) => match record { Ok(record) => match record {
Some(record) => Ok(ImageDbMapper { Some(record) => Ok(ImageDbMapper {
id: record.id, id: Some(record.id),
mime_type: record.mime_type, mime_type: record.mime_type,
}), }),
None => Err(ImageError::NotFound), None => Err(ImageError::NotFound),

View File

@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS "image" ( CREATE TABLE "image" (
"id" SERIAL PRIMARY KEY NOT NULL, "id" SERIAL PRIMARY KEY NOT NULL,
"mime_type" VARCHAR(100) NOT NULL, "mime_type" VARCHAR(100) NOT NULL,
"deleted_time" TIMESTAMP, "deleted_time" TIMESTAMP,
@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS "image" (
"updated_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP "updated_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
); );
CREATE OR REPLACE TRIGGER "update_image_updated_time" CREATE TRIGGER "update_image_updated_time"
BEFORE UPDATE ON "image" BEFORE UPDATE ON "image"
FOR EACH ROW FOR EACH ROW
EXECUTE FUNCTION update_updated_time_column(); EXECUTE FUNCTION update_updated_time_column();

View File

@ -1,6 +0,0 @@
CREATE OR REPLACE FUNCTION update_updated_time_column() RETURNS TRIGGER AS $$
BEGIN
NEW.updated_time = CURRENT_TIMESTAMP;
return NEW;
END;
$$ LANGUAGE 'plpgsql';

View File

@ -1,16 +0,0 @@
CREATE TABLE IF NOT EXISTS "post" (
"id" SERIAL PRIMARY KEY NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL,
"preview_image_url" TEXT NOT NULL,
"content" TEXT NOT NULL,
"published_time" TIMESTAMP,
"deleted_time" TIMESTAMP,
"created_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE OR REPLACE TRIGGER "update_post_updated_time"
BEFORE UPDATE ON "post"
FOR EACH ROW
EXECUTE FUNCTION update_updated_time_column();

View File

@ -1,13 +0,0 @@
CREATE TABLE IF NOT EXISTS "label" (
"id" SERIAL PRIMARY KEY NOT NULL,
"name" TEXT NOT NULL,
"color" BIGINT NOT NULL CHECK ("color" >= 0 AND "color" <= 4294967295),
"deleted_time" TIMESTAMP,
"created_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE OR REPLACE TRIGGER "update_label_updated_time"
BEFORE UPDATE ON "label"
FOR EACH ROW
EXECUTE FUNCTION update_updated_time_column();

View File

@ -1,7 +0,0 @@
CREATE TABLE IF NOT EXISTS "post_label" (
"post_id" INTEGER NOT NULL,
"label_id" INTEGER NOT NULL,
PRIMARY KEY ("post_id", "label_id"),
FOREIGN KEY ("post_id") REFERENCES "post" ("id") ON DELETE CASCADE,
FOREIGN KEY ("label_id") REFERENCES "label" ("id") ON DELETE CASCADE
);

View File

@ -1,20 +0,0 @@
CREATE TABLE IF NOT EXISTS "user" (
"id" SERIAL PRIMARY KEY NOT NULL,
"issuer" VARCHAR(100) NOT NULL,
"source_id" VARCHAR(100) NOT NULL,
"displayed_name" VARCHAR(100) NOT NULL,
"email" VARCHAR(100) NOT NULL,
"created_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS "idx_user_source_id_issuer"
ON "user" ("source_id", "issuer");
CREATE INDEX IF NOT EXISTS "idx_user_email"
ON "user" ("email");
CREATE OR REPLACE TRIGGER "update_user_updated_time"
BEFORE UPDATE ON "user"
FOR EACH ROW
EXECUTE FUNCTION update_updated_time_column();

View File

@ -9,10 +9,7 @@ use auth::{
exchange_auth_code_use_case::ExchangeAuthCodeUseCaseImpl, exchange_auth_code_use_case::ExchangeAuthCodeUseCaseImpl,
get_auth_url_use_case::LoginUseCaseImpl, get_auth_url_use_case::LoginUseCaseImpl,
}, },
framework::{ framework::oidc::auth_oidc_service_impl::AuthOidcServiceImpl,
db::user_db_service_impl::UserDbServiceImpl,
oidc::auth_oidc_service_impl::AuthOidcServiceImpl,
},
}; };
use image::{ use image::{
adapter::{ adapter::{
@ -63,8 +60,7 @@ impl Container {
oidc_configuration.redirect_url.clone(), oidc_configuration.redirect_url.clone(),
http_client, http_client,
)); ));
let user_db_service = Arc::new(UserDbServiceImpl::new(db_pool.clone())); let auth_repository = Arc::new(AuthRepositoryImpl::new(auth_oidc_service));
let auth_repository = Arc::new(AuthRepositoryImpl::new(user_db_service, auth_oidc_service));
let get_auth_url_use_case = Arc::new(LoginUseCaseImpl::new(auth_repository.clone())); let get_auth_url_use_case = Arc::new(LoginUseCaseImpl::new(auth_repository.clone()));
let exchange_auth_code_use_case = let exchange_auth_code_use_case =
Arc::new(ExchangeAuthCodeUseCaseImpl::new(auth_repository.clone())); Arc::new(ExchangeAuthCodeUseCaseImpl::new(auth_repository.clone()));