From 9c88b4bb5573562b76884fb02104f2d6f54f5731 Mon Sep 17 00:00:00 2001 From: SquidSpirit Date: Fri, 1 Aug 2025 13:24:08 +0800 Subject: [PATCH] BLOG-94 Create user in DB when first login through OIDC (#96) ### Description This PR introduces the functionality to persist user information in the database. When a user logs in via OIDC for the first time, a new user record is created. Subsequent logins will retrieve the existing user data from the database. This change ensures that users have a persistent identity within our system, identified by their unique combination of OIDC issuer and subject ID. #### Key Changes * **User Persistence Logic**: * In `ExchangeAuthCodeUseCase`, after successfully exchanging the authorization code, the logic now checks if the user exists in our database using their `issuer` and `source_id`. * If the user is not found (`AuthError::UserNotFound`), a new record is created in the `user` table. * The `User` entity returned by the use case now contains the internal database `id`. * **Database Integration in Auth Feature**: * Introduced a new `UserDbService` trait and its `sqlx`-based implementation, `UserDbServiceImpl`, to handle database operations for users. * The `AuthRepository` is extended to include methods for querying (`get_user_by_source_id`) and saving (`save_user`) users, delegating the calls to the new `UserDbService`. * The dependency injection container in `server/src/container.rs` has been updated to provide the `UserDbServiceImpl` to the `AuthRepositoryImpl`. * **Domain and Data Model Updates**: * The `User` domain entity now includes `id` (the database primary key) and `issuer` (from OIDC claims) to uniquely identify a user across different identity providers. * The `UserResponseDto` now exposes the internal `id` instead of the `source_id`. * **Session Management**: * The user's session now stores the database `user_id` (`i32`) instead of the entire user object. This is more efficient and secure. * Session keys have been centralized into a `constants.rs` file for better maintainability. #### Database Changes * A new database migration has been added to create the `user` table. * The table includes columns for `id`, `issuer`, `source_id`, `displayed_name`, and `email`. * A **`UNIQUE` index** has been created on `(source_id, issuer)` to guarantee that each user from a specific identity provider is stored only once. #### Refactoring * Minor refactoring in the `image` feature to change `id: Option` to `id: i32` for consistency with the new `User` entity model. ### Package Changes _No response_ ### Screenshots _No response_ ### Reference Resolves #94 ### Checklist - [x] A milestone is set - [x] The related issuse has been linked to this branch Reviewed-on: https://git.squidspirit.com/squid/blog/pulls/96 Co-authored-by: SquidSpirit Co-committed-by: SquidSpirit --- backend/Cargo.lock | 1 + backend/feature/auth/Cargo.toml | 1 + .../src/adapter/delivery/user_response_dto.rs | 4 +- backend/feature/auth/src/adapter/gateway.rs | 4 +- .../adapter/gateway/auth_repository_impl.rs | 32 ++++++++- .../gateway/oidc_claims_response_dto.rs | 3 + .../src/adapter/gateway/user_db_mapper.rs | 33 ++++++++++ .../src/adapter/gateway/user_db_service.rs | 14 ++++ .../auth/src/application/error/auth_error.rs | 2 + .../application/gateway/auth_repository.rs | 6 ++ .../use_case/exchange_auth_code_use_case.rs | 28 +++++++- .../feature/auth/src/domain/entity/user.rs | 3 + backend/feature/auth/src/framework.rs | 1 + backend/feature/auth/src/framework/db.rs | 2 + .../src/framework/db/user_db_service_impl.rs | 65 +++++++++++++++++++ .../auth/src/framework/db/user_record.rs | 24 +++++++ .../framework/oidc/auth_oidc_service_impl.rs | 3 + backend/feature/auth/src/framework/web.rs | 2 + .../auth/src/framework/web/auth_web_routes.rs | 19 +++--- .../auth/src/framework/web/constants.rs | 3 + .../src/adapter/delivery/image_request_dto.rs | 2 +- .../src/adapter/gateway/image_db_mapper.rs | 2 +- .../feature/image/src/domain/entity/image.rs | 2 +- .../src/framework/db/image_db_service_impl.rs | 2 +- backend/migrations/20250725231740_v0.3.0.sql | 18 +++++ backend/server/src/container.rs | 8 ++- 26 files changed, 260 insertions(+), 24 deletions(-) create mode 100644 backend/feature/auth/src/adapter/gateway/user_db_mapper.rs create mode 100644 backend/feature/auth/src/adapter/gateway/user_db_service.rs create mode 100644 backend/feature/auth/src/framework/db.rs create mode 100644 backend/feature/auth/src/framework/db/user_db_service_impl.rs create mode 100644 backend/feature/auth/src/framework/db/user_record.rs create mode 100644 backend/feature/auth/src/framework/web/constants.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 1c503c2..b9586a0 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -428,6 +428,7 @@ dependencies = [ "log", "openidconnect", "serde", + "sqlx", ] [[package]] diff --git a/backend/feature/auth/Cargo.toml b/backend/feature/auth/Cargo.toml index 26ffa18..27362a0 100644 --- a/backend/feature/auth/Cargo.toml +++ b/backend/feature/auth/Cargo.toml @@ -10,3 +10,4 @@ async-trait.workspace = true log.workspace = true openidconnect.workspace = true serde.workspace = true +sqlx.workspace = true diff --git a/backend/feature/auth/src/adapter/delivery/user_response_dto.rs b/backend/feature/auth/src/adapter/delivery/user_response_dto.rs index 975ab8c..2726c57 100644 --- a/backend/feature/auth/src/adapter/delivery/user_response_dto.rs +++ b/backend/feature/auth/src/adapter/delivery/user_response_dto.rs @@ -4,7 +4,7 @@ use crate::domain::entity::user::User; #[derive(Serialize)] pub struct UserResponseDto { - pub source_id: String, + pub id: i32, pub displayed_name: String, pub email: String, } @@ -12,7 +12,7 @@ pub struct UserResponseDto { impl From for UserResponseDto { fn from(user: User) -> Self { UserResponseDto { - source_id: user.source_id, + id: user.id, displayed_name: user.displayed_name, email: user.email, } diff --git a/backend/feature/auth/src/adapter/gateway.rs b/backend/feature/auth/src/adapter/gateway.rs index d813de5..4b3c881 100644 --- a/backend/feature/auth/src/adapter/gateway.rs +++ b/backend/feature/auth/src/adapter/gateway.rs @@ -1,3 +1,5 @@ -pub mod oidc_claims_response_dto; pub mod auth_oidc_service; pub mod auth_repository_impl; +pub mod oidc_claims_response_dto; +pub mod user_db_service; +pub mod user_db_mapper; diff --git a/backend/feature/auth/src/adapter/gateway/auth_repository_impl.rs b/backend/feature/auth/src/adapter/gateway/auth_repository_impl.rs index 533da3a..4c42eb6 100644 --- a/backend/feature/auth/src/adapter/gateway/auth_repository_impl.rs +++ b/backend/feature/auth/src/adapter/gateway/auth_repository_impl.rs @@ -3,7 +3,9 @@ use std::sync::Arc; use async_trait::async_trait; use crate::{ - adapter::gateway::auth_oidc_service::AuthOidcService, + adapter::gateway::{ + auth_oidc_service::AuthOidcService, user_db_service::UserDbService, user_db_mapper::UserMapper, + }, application::{ error::auth_error::AuthError, gateway::auth_repository::AuthRepository, use_case::get_auth_url_use_case::AuthUrl, @@ -12,12 +14,19 @@ use crate::{ }; pub struct AuthRepositoryImpl { + user_db_service: Arc, auth_oidc_service: Arc, } impl AuthRepositoryImpl { - pub fn new(auth_oidc_service: Arc) -> Self { - Self { auth_oidc_service } + pub fn new( + user_db_service: Arc, + auth_oidc_service: Arc, + ) -> Self { + Self { + user_db_service, + auth_oidc_service, + } } } @@ -37,4 +46,21 @@ impl AuthRepository for AuthRepositoryImpl { .await .map(|dto| dto.into_entity()) } + + async fn get_user_by_source_id( + &self, + issuer: &str, + source_id: &str, + ) -> Result { + 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 { + self.user_db_service + .create_user(UserMapper::from(user)) + .await + } } diff --git a/backend/feature/auth/src/adapter/gateway/oidc_claims_response_dto.rs b/backend/feature/auth/src/adapter/gateway/oidc_claims_response_dto.rs index 94c17b7..3d36876 100644 --- a/backend/feature/auth/src/adapter/gateway/oidc_claims_response_dto.rs +++ b/backend/feature/auth/src/adapter/gateway/oidc_claims_response_dto.rs @@ -2,6 +2,7 @@ use crate::domain::entity::user::User; pub struct OidcClaimsResponseDto { pub sub: String, + pub issuer: String, pub preferred_username: Option, pub email: Option, } @@ -9,6 +10,8 @@ pub struct OidcClaimsResponseDto { impl OidcClaimsResponseDto { pub fn into_entity(self) -> User { User { + id: -1, + issuer: self.issuer, source_id: self.sub, displayed_name: self.preferred_username.unwrap_or_default(), email: self.email.unwrap_or_default(), diff --git a/backend/feature/auth/src/adapter/gateway/user_db_mapper.rs b/backend/feature/auth/src/adapter/gateway/user_db_mapper.rs new file mode 100644 index 0000000..31a01d0 --- /dev/null +++ b/backend/feature/auth/src/adapter/gateway/user_db_mapper.rs @@ -0,0 +1,33 @@ +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 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, + } + } +} diff --git a/backend/feature/auth/src/adapter/gateway/user_db_service.rs b/backend/feature/auth/src/adapter/gateway/user_db_service.rs new file mode 100644 index 0000000..e8b1c40 --- /dev/null +++ b/backend/feature/auth/src/adapter/gateway/user_db_service.rs @@ -0,0 +1,14 @@ +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; + + async fn create_user(&self, user: UserMapper) -> Result; +} diff --git a/backend/feature/auth/src/application/error/auth_error.rs b/backend/feature/auth/src/application/error/auth_error.rs index 820a405..9fa2375 100644 --- a/backend/feature/auth/src/application/error/auth_error.rs +++ b/backend/feature/auth/src/application/error/auth_error.rs @@ -1,8 +1,10 @@ #[derive(Debug, PartialEq)] pub enum AuthError { + DatabaseError(String), OidcError(String), InvalidState, InvalidNonce, InvalidAuthCode, InvalidIdToken, + UserNotFound, } diff --git a/backend/feature/auth/src/application/gateway/auth_repository.rs b/backend/feature/auth/src/application/gateway/auth_repository.rs index 98a12f5..c88567c 100644 --- a/backend/feature/auth/src/application/gateway/auth_repository.rs +++ b/backend/feature/auth/src/application/gateway/auth_repository.rs @@ -8,6 +8,12 @@ use crate::{ #[async_trait] pub trait AuthRepository: Send + Sync { fn get_auth_url(&self) -> Result; + async fn exchange_auth_code(&self, code: &str, expected_nonce: &str) -> Result; + + async fn get_user_by_source_id(&self, issuer: &str, source_id: &str) + -> Result; + + async fn save_user(&self, user: User) -> Result; } diff --git a/backend/feature/auth/src/application/use_case/exchange_auth_code_use_case.rs b/backend/feature/auth/src/application/use_case/exchange_auth_code_use_case.rs index 65e90d1..ae59f7a 100644 --- a/backend/feature/auth/src/application/use_case/exchange_auth_code_use_case.rs +++ b/backend/feature/auth/src/application/use_case/exchange_auth_code_use_case.rs @@ -41,8 +41,32 @@ impl ExchangeAuthCodeUseCase for ExchangeAuthCodeUseCaseImpl { return Err(AuthError::InvalidState); } - self.auth_repository + let mut logged_in_user = self + .auth_repository .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) } } diff --git a/backend/feature/auth/src/domain/entity/user.rs b/backend/feature/auth/src/domain/entity/user.rs index 2cd83ca..a03bc1e 100644 --- a/backend/feature/auth/src/domain/entity/user.rs +++ b/backend/feature/auth/src/domain/entity/user.rs @@ -1,4 +1,7 @@ +#[derive(Clone)] pub struct User { + pub id: i32, + pub issuer: String, pub source_id: String, pub displayed_name: String, pub email: String, diff --git a/backend/feature/auth/src/framework.rs b/backend/feature/auth/src/framework.rs index 39c6581..5dfd9bf 100644 --- a/backend/feature/auth/src/framework.rs +++ b/backend/feature/auth/src/framework.rs @@ -1,2 +1,3 @@ +pub mod db; pub mod oidc; pub mod web; diff --git a/backend/feature/auth/src/framework/db.rs b/backend/feature/auth/src/framework/db.rs new file mode 100644 index 0000000..1737d13 --- /dev/null +++ b/backend/feature/auth/src/framework/db.rs @@ -0,0 +1,2 @@ +pub mod user_db_service_impl; +pub mod user_record; diff --git a/backend/feature/auth/src/framework/db/user_db_service_impl.rs b/backend/feature/auth/src/framework/db/user_db_service_impl.rs new file mode 100644 index 0000000..6f5cc40 --- /dev/null +++ b/backend/feature/auth/src/framework/db/user_db_service_impl.rs @@ -0,0 +1,65 @@ +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, +} + +impl UserDbServiceImpl { + pub fn new(db_pool: Pool) -> Self { + Self { db_pool } + } +} + +#[async_trait] +impl UserDbService for UserDbServiceImpl { + async fn get_user_by_source_id( + &self, + issuer: &str, + source_id: &str, + ) -> Result { + 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 { + 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) + } +} diff --git a/backend/feature/auth/src/framework/db/user_record.rs b/backend/feature/auth/src/framework/db/user_record.rs new file mode 100644 index 0000000..57cd481 --- /dev/null +++ b/backend/feature/auth/src/framework/db/user_record.rs @@ -0,0 +1,24 @@ +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, + } + } +} diff --git a/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs b/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs index d60f7da..d4a85dd 100644 --- a/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs +++ b/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs @@ -93,6 +93,8 @@ impl AuthOidcService for AuthOidcServiceImpl { ) .map_err(|_| AuthError::InvalidIdToken)?; + let issuer = claims.issuer().to_string(); + let preferred_username = claims .preferred_username() .map(|username| username.to_string()); @@ -101,6 +103,7 @@ impl AuthOidcService for AuthOidcServiceImpl { Ok(OidcClaimsResponseDto { sub: claims.subject().to_string(), + issuer: issuer, preferred_username: preferred_username, email: email, }) diff --git a/backend/feature/auth/src/framework/web.rs b/backend/feature/auth/src/framework/web.rs index 60c8eb8..8853d00 100644 --- a/backend/feature/auth/src/framework/web.rs +++ b/backend/feature/auth/src/framework/web.rs @@ -1 +1,3 @@ pub mod auth_web_routes; + +mod constants; diff --git a/backend/feature/auth/src/framework/web/auth_web_routes.rs b/backend/feature/auth/src/framework/web/auth_web_routes.rs index c56dda3..f0ab70d 100644 --- a/backend/feature/auth/src/framework/web/auth_web_routes.rs +++ b/backend/feature/auth/src/framework/web/auth_web_routes.rs @@ -6,12 +6,11 @@ use crate::{ auth_controller::AuthController, oidc_callback_query_dto::OidcCallbackQueryDto, }, 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) { cfg.service( web::scope("/auth") @@ -29,11 +28,11 @@ async fn oidc_login_handler( match result { Ok(auth_url) => { - if let Err(e) = session.insert(SESSION_KEY_AUTH_STATE, auth_url.state) { + if let Err(e) = session.insert::(SESSION_KEY_AUTH_STATE, auth_url.state) { log::error!("{e:?}"); return HttpResponse::InternalServerError().finish(); } - if let Err(e) = session.insert(SESSION_KEY_AUTH_NONCE, auth_url.nonce) { + if let Err(e) = session.insert::(SESSION_KEY_AUTH_NONCE, auth_url.nonce) { log::error!("{e:?}"); return HttpResponse::InternalServerError().finish(); } @@ -53,12 +52,12 @@ async fn oidc_callback_handler( query: web::Query, session: Session, ) -> impl Responder { - let expected_state: String = match session.get(SESSION_KEY_AUTH_STATE) { + let expected_state = match session.get::(SESSION_KEY_AUTH_STATE) { Ok(Some(state)) => state, _ => return HttpResponse::BadRequest().finish(), }; - let expected_nonce: String = match session.get(SESSION_KEY_AUTH_NONCE) { + let expected_nonce = match session.get::(SESSION_KEY_AUTH_NONCE) { Ok(Some(nonce)) => nonce, _ => return HttpResponse::BadRequest().finish(), }; @@ -71,7 +70,7 @@ async fn oidc_callback_handler( session.remove(SESSION_KEY_AUTH_NONCE); match result { Ok(user) => { - if let Err(e) = session.insert(SESSION_KEY_USER, user) { + if let Err(e) = session.insert::(SESSION_KEY_USER_ID, user.id) { log::error!("{e:?}"); return HttpResponse::InternalServerError().finish(); } @@ -95,7 +94,7 @@ async fn oidc_callback_handler( async fn logout_handler(session: Session) -> impl Responder { session.remove(SESSION_KEY_AUTH_STATE); session.remove(SESSION_KEY_AUTH_NONCE); - session.remove(SESSION_KEY_USER); + session.remove(SESSION_KEY_USER_ID); HttpResponse::Found() .append_header((header::LOCATION, "/")) .finish() diff --git a/backend/feature/auth/src/framework/web/constants.rs b/backend/feature/auth/src/framework/web/constants.rs new file mode 100644 index 0000000..346177d --- /dev/null +++ b/backend/feature/auth/src/framework/web/constants.rs @@ -0,0 +1,3 @@ +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"; diff --git a/backend/feature/image/src/adapter/delivery/image_request_dto.rs b/backend/feature/image/src/adapter/delivery/image_request_dto.rs index 8c86c17..56ef488 100644 --- a/backend/feature/image/src/adapter/delivery/image_request_dto.rs +++ b/backend/feature/image/src/adapter/delivery/image_request_dto.rs @@ -8,7 +8,7 @@ pub struct ImageRequestDto { impl ImageRequestDto { pub fn into_entity(self) -> Image { Image { - id: None, + id: -1, mime_type: self.mime_type, data: self.data, } diff --git a/backend/feature/image/src/adapter/gateway/image_db_mapper.rs b/backend/feature/image/src/adapter/gateway/image_db_mapper.rs index fb92823..1f2ea76 100644 --- a/backend/feature/image/src/adapter/gateway/image_db_mapper.rs +++ b/backend/feature/image/src/adapter/gateway/image_db_mapper.rs @@ -1,7 +1,7 @@ use crate::domain::entity::image::Image; pub struct ImageDbMapper { - pub id: Option, + pub id: i32, pub mime_type: String, } diff --git a/backend/feature/image/src/domain/entity/image.rs b/backend/feature/image/src/domain/entity/image.rs index 5f2dde4..516905f 100644 --- a/backend/feature/image/src/domain/entity/image.rs +++ b/backend/feature/image/src/domain/entity/image.rs @@ -1,5 +1,5 @@ pub struct Image { - pub id: Option, + pub id: i32, pub mime_type: String, pub data: Vec, } diff --git a/backend/feature/image/src/framework/db/image_db_service_impl.rs b/backend/feature/image/src/framework/db/image_db_service_impl.rs index bf3df3d..e61cc4c 100644 --- a/backend/feature/image/src/framework/db/image_db_service_impl.rs +++ b/backend/feature/image/src/framework/db/image_db_service_impl.rs @@ -54,7 +54,7 @@ impl ImageDbService for ImageDbServiceImpl { match image_record { Ok(record) => match record { Some(record) => Ok(ImageDbMapper { - id: Some(record.id), + id: record.id, mime_type: record.mime_type, }), None => Err(ImageError::NotFound), diff --git a/backend/migrations/20250725231740_v0.3.0.sql b/backend/migrations/20250725231740_v0.3.0.sql index 8186a66..4b7a6a0 100644 --- a/backend/migrations/20250725231740_v0.3.0.sql +++ b/backend/migrations/20250725231740_v0.3.0.sql @@ -6,7 +6,25 @@ CREATE TABLE "image" ( "updated_time" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); +CREATE TABLE "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 "user_source_id_issuer_key" ON "user" ("source_id", "issuer"); +CREATE INDEX "user_email_key" ON "user" HASH ("email"); + CREATE TRIGGER "update_image_updated_time" BEFORE UPDATE ON "image" FOR EACH ROW EXECUTE FUNCTION update_updated_time_column(); + +CREATE TRIGGER "update_user_updated_time" +BEFORE UPDATE ON "user" +FOR EACH ROW +EXECUTE FUNCTION update_updated_time_column(); diff --git a/backend/server/src/container.rs b/backend/server/src/container.rs index f9a04b2..50be621 100644 --- a/backend/server/src/container.rs +++ b/backend/server/src/container.rs @@ -9,7 +9,10 @@ use auth::{ exchange_auth_code_use_case::ExchangeAuthCodeUseCaseImpl, get_auth_url_use_case::LoginUseCaseImpl, }, - framework::oidc::auth_oidc_service_impl::AuthOidcServiceImpl, + framework::{ + db::user_db_service_impl::UserDbServiceImpl, + oidc::auth_oidc_service_impl::AuthOidcServiceImpl, + }, }; use image::{ adapter::{ @@ -60,7 +63,8 @@ impl Container { oidc_configuration.redirect_url.clone(), http_client, )); - let auth_repository = Arc::new(AuthRepositoryImpl::new(auth_oidc_service)); + let user_db_service = Arc::new(UserDbServiceImpl::new(db_pool.clone())); + 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 exchange_auth_code_use_case = Arc::new(ExchangeAuthCodeUseCaseImpl::new(auth_repository.clone()));