From 166c5be863c71e7d65856c39e3ffbb1b22944fac Mon Sep 17 00:00:00 2001 From: skettiks Date: Thu, 23 Apr 2026 18:47:30 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20V1=20=D0=BC=D0=B8=D0=B3=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20-=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=8F=D1=82=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20email=20=D0=A0=D0=B5=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=20saveWithOAuth2()?= =?UTF-8?q?=20=D0=B2=20UserRepository=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=20findByProviderAndProviderId()?= =?UTF-8?q?=20=D0=B2=20UserRepository=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20CustomOAuth2UserService=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20OAuth2=20=D0=BC=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=B4=D0=BE=D0=B2=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20package=20declaration=20=D0=B2=D0=BE=20=D0=B2=D1=81?= =?UTF-8?q?=D0=B5=D1=85=207=20OAuth2=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapters/persistence/entity/UserEntity.kt | 2 + .../persistence/jdbc/UserRepository.kt | 56 +++++++++++++++++-- .../security/CustomOAuth2UserService.kt | 37 ++++++++---- .../adapters/security/GoogleOAuth2UserInfo.kt | 2 +- .../adapters/security/OAuth2UserInfo.kt | 2 +- .../security/OAuth2UserInfoFactory.kt | 2 +- .../adapters/security/UserPrincipal.kt | 2 +- .../adapters/security/VkOAuth2UserInfo.kt | 2 +- .../adapters/security/YandexOAuth2UserInfo.kt | 2 +- .../ports/output/UserRepositoryPort.kt | 4 ++ src/main/resources/db/migration/V1__init.sql | 1 + 11 files changed, 88 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/com/project/movienight/adapters/persistence/entity/UserEntity.kt b/src/main/kotlin/com/project/movienight/adapters/persistence/entity/UserEntity.kt index 0beda74..f606ea1 100644 --- a/src/main/kotlin/com/project/movienight/adapters/persistence/entity/UserEntity.kt +++ b/src/main/kotlin/com/project/movienight/adapters/persistence/entity/UserEntity.kt @@ -9,6 +9,7 @@ data class UserEntity( val id: UUID, val name: String, val email: String, + val password: String?, val provider: String?, val providerId: String?, val createdAt: LocalDateTime, @@ -31,6 +32,7 @@ fun User.toEntity( id = id, name = name, email = email, + password = password, provider = provider?.name, providerId = providerId, createdAt = createdAt, diff --git a/src/main/kotlin/com/project/movienight/adapters/persistence/jdbc/UserRepository.kt b/src/main/kotlin/com/project/movienight/adapters/persistence/jdbc/UserRepository.kt index 686899f..f3ea7ad 100644 --- a/src/main/kotlin/com/project/movienight/adapters/persistence/jdbc/UserRepository.kt +++ b/src/main/kotlin/com/project/movienight/adapters/persistence/jdbc/UserRepository.kt @@ -20,6 +20,7 @@ class UserRepository( id = UUID.fromString(rs.getString("id")), name = rs.getString("name"), email = rs.getString("email"), + password = rs.getString("password"), provider = rs.getString("provider"), providerId = rs.getString("provider_id"), createdAt = rs.getTimestamp("created_at").toLocalDateTime(), @@ -32,11 +33,12 @@ class UserRepository( jdbc.update( """ UPDATE users - SET name = ?, email = ?, provider = ?, provider_id = ? + SET name = ?, email = ?, password = ?, provider = ?, provider_id = ? WHERE id = ? """.trimIndent(), entity.name, entity.email, + user.password, entity.provider, entity.providerId, entity.id, @@ -44,12 +46,13 @@ class UserRepository( if (updatedRows == 0) { jdbc.update( """ - INSERT INTO users (id, name, email, provider, provider_id, created_at) - VALUES (?, ?, ?, ?, ?, ?) + INSERT INTO users (id, name, email, password, provider, provider_id, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?) """.trimIndent(), entity.id, entity.name, entity.email, + user.password, entity.provider, entity.providerId, entity.createdAt, @@ -61,7 +64,7 @@ class UserRepository( override fun findById(id: UUID): User? { val entities = jdbc.query( - "SELECT id, name, email, provider, provider_id, created_at FROM users WHERE id = ?", + "SELECT id, name, email, password, provider, provider_id, created_at FROM users WHERE id = ?", userEntityRowMapper, id, ) @@ -71,7 +74,7 @@ class UserRepository( override fun findAll(): List = jdbc .query( - "SELECT id, name, email, provider, provider_id, created_at FROM users", + "SELECT id, name, email, password, provider, provider_id, created_at FROM users", userEntityRowMapper, ).map { it.toDomain() } @@ -79,6 +82,47 @@ class UserRepository( jdbc.update("DELETE FROM users WHERE id = ?", id) } + override fun saveWithOAuth2(user: User, provider: String, providerId: String): User { + val updatedRows = jdbc.update( + """ + UPDATE users + SET name = ?, email = ?, password = ?, provider = ?, provider_id = ? + WHERE id = ? + """.trimIndent(), + user.name, + user.email, + user.password, + provider, + providerId, + user.id, + ) + if (updatedRows == 0) { + jdbc.update( + """ + INSERT INTO users (id, name, email, password, provider, provider_id) + VALUES (?, ?, ?, ?, ?, ?) + """.trimIndent(), + user.id, + user.name, + user.email, + user.password, + provider, + providerId, + ) + } + return user + } + + override fun findByProviderAndProviderId(provider: String, providerId: String): User? { + val entities = jdbc.query( + "SELECT id, name, email, password, provider, provider_id, created_at FROM users WHERE provider = ? AND provider_id = ?", + userEntityRowMapper, + provider, + providerId, + ) + return entities.firstOrNull()?.toDomain() + } + override fun findByProviderAndProviderId( provider: AuthProvider, providerId: String, @@ -86,7 +130,7 @@ class UserRepository( val entities = jdbc.query( """ - SELECT id, name, email, provider, provider_id, created_at + SELECT id, name, email, password, provider, provider_id, created_at FROM users WHERE provider = ? AND provider_id = ? """.trimIndent(), diff --git a/src/main/kotlin/com/project/movienight/adapters/security/CustomOAuth2UserService.kt b/src/main/kotlin/com/project/movienight/adapters/security/CustomOAuth2UserService.kt index 189e927..968424a 100644 --- a/src/main/kotlin/com/project/movienight/adapters/security/CustomOAuth2UserService.kt +++ b/src/main/kotlin/com/project/movienight/adapters/security/CustomOAuth2UserService.kt @@ -1,4 +1,4 @@ -package com.project.movienight.adapters.security.oauth2 +package com.project.movienight.adapters.security import com.project.movienight.application.ports.output.IdGenerator import com.project.movienight.application.ports.output.UserRepositoryPort @@ -37,21 +37,34 @@ class CustomOAuth2UserService( } private fun findOrCreateUser(userInfo: OAuth2UserInfo): User { - val existingUser = userRepository.findByEmail(userInfo.getEmail()) + // Сначала ищем по provider + provider_id (основной способ для OAuth2) + val existingUser = userRepository.findByProviderAndProviderId( + userInfo.getProvider(), + userInfo.getProviderId() + ) return if (existingUser != null) { - log.debug("User found by email: {}", userInfo.getEmail()) + log.debug("User found by provider: {}", userInfo.getProvider()) existingUser } else { - log.debug("Creating new user for provider: {}", userInfo.getProvider()) - val newUser = User( - id = idGenerator.generateId(), - name = userInfo.getName(), - email = userInfo.getEmail(), - password = "", // OAuth2 пользователи не имеют пароля - library = null, - ) - userRepository.save(newUser) + // Проверяем нет ли пользователя с таким email (связывание аккаунтов) + val userByEmail = userRepository.findByEmail(userInfo.getEmail()) + + if (userByEmail != null) { + // Пользователь существует, обновляем его OAuth2 данными + log.debug("Linking OAuth2 account to existing user: {}", userInfo.getEmail()) + userRepository.saveWithOAuth2(userByEmail, userInfo.getProvider(), userInfo.getProviderId()) + } else { + log.debug("Creating new user for provider: {}", userInfo.getProvider()) + val newUser = User( + id = idGenerator.generateId(), + name = userInfo.getName(), + email = userInfo.getEmail(), + password = "", + library = null, + ) + userRepository.saveWithOAuth2(newUser, userInfo.getProvider(), userInfo.getProviderId()) + } } } } diff --git a/src/main/kotlin/com/project/movienight/adapters/security/GoogleOAuth2UserInfo.kt b/src/main/kotlin/com/project/movienight/adapters/security/GoogleOAuth2UserInfo.kt index dcff86e..fa41de5 100644 --- a/src/main/kotlin/com/project/movienight/adapters/security/GoogleOAuth2UserInfo.kt +++ b/src/main/kotlin/com/project/movienight/adapters/security/GoogleOAuth2UserInfo.kt @@ -1,4 +1,4 @@ -package com.project.movienight.adapters.security.oauth2 +package com.project.movienight.adapters.security class GoogleOAuth2UserInfo( private val attributes: Map diff --git a/src/main/kotlin/com/project/movienight/adapters/security/OAuth2UserInfo.kt b/src/main/kotlin/com/project/movienight/adapters/security/OAuth2UserInfo.kt index 5a155b9..b6abf09 100644 --- a/src/main/kotlin/com/project/movienight/adapters/security/OAuth2UserInfo.kt +++ b/src/main/kotlin/com/project/movienight/adapters/security/OAuth2UserInfo.kt @@ -1,4 +1,4 @@ -package com.project.movienight.adapters.security.oauth2 +package com.project.movienight.adapters.security interface OAuth2UserInfo { fun getProviderId(): String diff --git a/src/main/kotlin/com/project/movienight/adapters/security/OAuth2UserInfoFactory.kt b/src/main/kotlin/com/project/movienight/adapters/security/OAuth2UserInfoFactory.kt index 001b789..e2db545 100644 --- a/src/main/kotlin/com/project/movienight/adapters/security/OAuth2UserInfoFactory.kt +++ b/src/main/kotlin/com/project/movienight/adapters/security/OAuth2UserInfoFactory.kt @@ -1,4 +1,4 @@ -package com.project.movienight.adapters.security.oauth2 +package com.project.movienight.adapters.security import org.springframework.security.oauth2.core.OAuth2AuthenticationException import org.springframework.security.oauth2.core.user.OAuth2User diff --git a/src/main/kotlin/com/project/movienight/adapters/security/UserPrincipal.kt b/src/main/kotlin/com/project/movienight/adapters/security/UserPrincipal.kt index 5fbbee1..a4d94ea 100644 --- a/src/main/kotlin/com/project/movienight/adapters/security/UserPrincipal.kt +++ b/src/main/kotlin/com/project/movienight/adapters/security/UserPrincipal.kt @@ -1,4 +1,4 @@ -package com.project.movienight.adapters.security.oauth2 +package com.project.movienight.adapters.security import com.project.movienight.domain.model.User import org.springframework.security.core.GrantedAuthority diff --git a/src/main/kotlin/com/project/movienight/adapters/security/VkOAuth2UserInfo.kt b/src/main/kotlin/com/project/movienight/adapters/security/VkOAuth2UserInfo.kt index 492e74c..47c41d2 100644 --- a/src/main/kotlin/com/project/movienight/adapters/security/VkOAuth2UserInfo.kt +++ b/src/main/kotlin/com/project/movienight/adapters/security/VkOAuth2UserInfo.kt @@ -1,4 +1,4 @@ -package com.project.movienight.adapters.security.oauth2 +package com.project.movienight.adapters.security @Suppress("UNCHECKED_CAST") class VkOAuth2UserInfo( diff --git a/src/main/kotlin/com/project/movienight/adapters/security/YandexOAuth2UserInfo.kt b/src/main/kotlin/com/project/movienight/adapters/security/YandexOAuth2UserInfo.kt index 2929389..467aa85 100644 --- a/src/main/kotlin/com/project/movienight/adapters/security/YandexOAuth2UserInfo.kt +++ b/src/main/kotlin/com/project/movienight/adapters/security/YandexOAuth2UserInfo.kt @@ -1,4 +1,4 @@ -package com.project.movienight.adapters.security.oauth2 +package com.project.movienight.adapters.security @Suppress("UNCHECKED_CAST") class YandexOAuth2UserInfo( diff --git a/src/main/kotlin/com/project/movienight/application/ports/output/UserRepositoryPort.kt b/src/main/kotlin/com/project/movienight/application/ports/output/UserRepositoryPort.kt index dd69728..678cea0 100644 --- a/src/main/kotlin/com/project/movienight/application/ports/output/UserRepositoryPort.kt +++ b/src/main/kotlin/com/project/movienight/application/ports/output/UserRepositoryPort.kt @@ -7,6 +7,10 @@ import java.util.UUID interface UserRepositoryPort { fun save(user: User): User + fun saveWithOAuth2(user: User, provider: String, providerId: String): User + + fun findByProviderAndProviderId(provider: String, providerId: String): User? + fun findById(id: UUID): User? fun findByEmail(email: String): User? diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql index 900f6b5..ee51933 100644 --- a/src/main/resources/db/migration/V1__init.sql +++ b/src/main/resources/db/migration/V1__init.sql @@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS public.users ( id UUID PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(320) NOT NULL UNIQUE, + password VARCHAR(255), provider VARCHAR(64), provider_id VARCHAR(255), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP