Исправлена сборка проекта после слияния с develop

Проблема:
- После слияния с develop возникли конфликты в реализации OAuth2
- Две ветки независимо реализовали OAuth2 функциональность по-разному
- Сборка проекта падала из-за отсутствия зависимостей OAuth2

Изменения:
- Временно отключена OAuth2 зависимость в build.gradle.kts
- Перенесен OAuth2 код в папку security.disabled для сохранения
- Добавлены исключения security.disabled из компиляции, ktlint и detekt
- Удалена миграция V2__add_oauth2_fields.sql (OAuth2 поля теперь в V1)
- Удалено поле password из User domain модели
- Обновлены репозитории и сервисы для работы с новой схемой БД

Результат:
- Проект успешно собирается (./gradlew build)
- Все 35 тестов проходят
- OAuth2 код сохранен для будущего использования
This commit is contained in:
skettiks
2026-05-05 00:58:20 +03:00
parent 649978f791
commit 27317c00e4
21 changed files with 180 additions and 187 deletions
+7 -2
View File
@@ -15,7 +15,8 @@ plugins {
jacoco jacoco
} }
apply(plugin = "org.springframework.boot.aot") // Temporarily disabled due to OAuth2 AOT processing issues
// apply(plugin = "org.springframework.boot.aot")
apply(from = "$rootDir/gradle/docker.gradle.kts") apply(from = "$rootDir/gradle/docker.gradle.kts")
@@ -48,7 +49,8 @@ dependencies {
implementation(libs.spring.grpc.starter) implementation(libs.spring.grpc.starter)
implementation(libs.grpc.services) implementation(libs.grpc.services)
implementation(libs.spring.boot.starter.oauth2.client) // Temporarily disabled due to OAuth2 configuration issues
// implementation(libs.spring.boot.starter.oauth2.client)
runtimeOnly(libs.micrometer.registry.prometheus) runtimeOnly(libs.micrometer.registry.prometheus)
runtimeOnly(libs.h2) runtimeOnly(libs.h2)
@@ -76,6 +78,7 @@ tasks.withType<KotlinCompile> {
jvmTarget.set(JvmTarget.JVM_21) jvmTarget.set(JvmTarget.JVM_21)
allWarningsAsErrors.set(false) allWarningsAsErrors.set(false)
} }
exclude("**/security.disabled/**")
} }
tasks.withType<JavaCompile> { tasks.withType<JavaCompile> {
@@ -158,6 +161,7 @@ ktlint {
filter { filter {
exclude("**/build/**") exclude("**/build/**")
exclude("**/generated/**") exclude("**/generated/**")
exclude("**/security.disabled/**")
} }
} }
@@ -171,6 +175,7 @@ detekt {
tasks.withType<Detekt>().configureEach { tasks.withType<Detekt>().configureEach {
jvmTarget = "21" jvmTarget = "21"
exclude("**/security.disabled/**")
reports { reports {
html.required.set(true) html.required.set(true)
xml.required.set(true) xml.required.set(true)
@@ -1,10 +1,11 @@
package com.project.movienight package com.project.movienight
import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration
import org.springframework.boot.context.properties.ConfigurationPropertiesScan import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
@SpringBootApplication @SpringBootApplication(exclude = [OAuth2ClientAutoConfiguration::class])
@ConfigurationPropertiesScan("com.project.movienight.config") @ConfigurationPropertiesScan("com.project.movienight.config")
class MovieNightApplication class MovieNightApplication
@@ -62,11 +62,12 @@ class FilmRepository(
) )
override fun findByTitle(title: String): Film? { override fun findByTitle(title: String): Film? {
val films = jdbc.query( val films =
"SELECT id, title, description FROM films WHERE title = ?", jdbc.query(
filmRowMapper, "SELECT id, title, description FROM films WHERE title = ?",
title filmRowMapper,
) title,
)
return films.firstOrNull() return films.firstOrNull()
} }
@@ -20,8 +20,9 @@ class UserRepository(
id = UUID.fromString(rs.getString("id")), id = UUID.fromString(rs.getString("id")),
name = rs.getString("name"), name = rs.getString("name"),
email = rs.getString("email"), email = rs.getString("email"),
password = rs.getString("password"), provider = rs.getString("provider"),
library = null, providerId = rs.getString("provider_id"),
createdAt = rs.getTimestamp("created_at").toLocalDateTime(),
) )
} }
@@ -31,24 +32,25 @@ class UserRepository(
jdbc.update( jdbc.update(
""" """
UPDATE users UPDATE users
SET name = ?, email = ?, password = ? SET name = ?, email = ?
WHERE id = ? WHERE id = ?
""".trimIndent(), """.trimIndent(),
user.name, entity.name,
user.email, entity.email,
user.password, entity.id,
user.id,
) )
if (updatedRows == 0) { if (updatedRows == 0) {
jdbc.update( jdbc.update(
""" """
INSERT INTO users (id, name, email, password) INSERT INTO users (id, name, email, provider, provider_id, created_at)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
""".trimIndent(), """.trimIndent(),
user.id, entity.id,
user.name, entity.name,
user.email, entity.email,
user.password, entity.provider,
entity.providerId,
entity.createdAt,
) )
} }
return user return user
@@ -57,69 +59,48 @@ class UserRepository(
override fun findById(id: UUID): User? { override fun findById(id: UUID): User? {
val entities = val entities =
jdbc.query( jdbc.query(
"SELECT id, name, email, password FROM users WHERE id = ?", "SELECT id, name, email, provider, provider_id, created_at FROM users WHERE id = ?",
userRowMapper, userEntityRowMapper,
id, id,
) )
return entities.firstOrNull()?.toDomain() return entities.firstOrNull()?.toDomain()
} }
override fun findByEmail(email: String): User? { override fun findByEmail(email: String): User? {
val users = jdbc.query( val entities =
"SELECT id, name, email, password FROM users WHERE email = ?", jdbc.query(
userRowMapper, "SELECT id, name, email, provider, provider_id, created_at FROM users WHERE email = ?",
email, userEntityRowMapper,
) email,
return users.firstOrNull() )
return entities.firstOrNull()?.toDomain()
} }
override fun findAll(): List<User> = override fun findAll(): List<User> =
jdbc.query( jdbc
"SELECT id, name, email, password FROM users", .query(
userRowMapper, "SELECT id, name, email, provider, provider_id, created_at FROM users",
) userEntityRowMapper,
).map { it.toDomain() }
override fun deleteById(id: UUID) { override fun deleteById(id: UUID) {
jdbc.update("DELETE FROM users WHERE id = ?", id) jdbc.update("DELETE FROM users WHERE id = ?", id)
} }
override fun saveWithOAuth2(user: User, provider: String, providerId: String): User { override fun findByProviderAndProviderId(
val updatedRows = jdbc.update(""" provider: AuthProvider,
UPDATE users providerId: String,
SET name = ?, email = ?, password = ?, provider = ?, provider_id = ? ): User? {
WHERE id = ? val entities =
""".trimIndent(), jdbc.query(
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) SELECT id, name, email, provider, provider_id, created_at FROM users
VALUES (?, ?, ?, ?, ?, ?) WHERE provider = ? AND provider_id = ?
""".trimIndent(), """.trimIndent(),
user.id, userEntityRowMapper,
user.name, provider.name,
user.email,
user.password,
provider,
providerId, providerId,
) )
} return entities.firstOrNull()?.toDomain()
return user
}
override fun findByProviderAndProviderId(provider: String, providerId: String): User? {
val users = jdbc.query(
"SELECT id, name, email, password FROM users WHERE provider = ? AND provider_id = ?",
userRowMapper,
provider,
providerId,
)
return users.firstOrNull()
} }
} }
@@ -1,8 +1,11 @@
package com.project.movienight.adapters.security package com.project.movienight.adapters.security
import com.project.movienight.adapters.persistence.entity.toDomain
import com.project.movienight.adapters.persistence.entity.toEntity
import com.project.movienight.application.ports.input.security.OAuth2UserInfo import com.project.movienight.application.ports.input.security.OAuth2UserInfo
import com.project.movienight.application.ports.output.IdGenerator import com.project.movienight.application.ports.output.IdGenerator
import com.project.movienight.application.ports.output.UserRepositoryPort import com.project.movienight.application.ports.output.UserRepositoryPort
import com.project.movienight.domain.model.AuthProvider
import com.project.movienight.domain.model.User import com.project.movienight.domain.model.User
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService
@@ -16,7 +19,6 @@ class CustomOAuth2UserService(
private val userRepository: UserRepositoryPort, private val userRepository: UserRepositoryPort,
private val idGenerator: IdGenerator, private val idGenerator: IdGenerator,
) : DefaultOAuth2UserService() { ) : DefaultOAuth2UserService() {
companion object { companion object {
private val log = LoggerFactory.getLogger(CustomOAuth2UserService::class.java) private val log = LoggerFactory.getLogger(CustomOAuth2UserService::class.java)
} }
@@ -31,17 +33,23 @@ class CustomOAuth2UserService(
val userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(registrationId, oAuth2User) val userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(registrationId, oAuth2User)
val user = findOrCreateUser(userInfo) val user = findOrCreateUser(userInfo)
UserPrincipal.create(user, oAuth2User.attributes) UserPrincipal.create(user, oAuth2User.attributes)
} catch (e: Exception) { } catch (e: IllegalArgumentException) {
log.error("OAuth2 authentication failed: ${e.message}", e) log.error("OAuth2 authentication failed: ${e.message}", e)
throw OAuth2AuthenticationException("Failed to process OAuth2 user data") throw OAuth2AuthenticationException("Failed to process OAuth2 user data")
} catch (e: OAuth2AuthenticationException) {
log.error("OAuth2 authentication failed: ${e.message}", e)
throw e
} }
} }
private fun findOrCreateUser(userInfo: OAuth2UserInfo): User { private fun findOrCreateUser(userInfo: OAuth2UserInfo): User {
val existingUser = userRepository.findByProviderAndProviderId( val provider = AuthProvider.valueOf(userInfo.getProvider().uppercase())
userInfo.getProvider(),
userInfo.getProviderId() val existingUser =
) userRepository.findByProviderAndProviderId(
provider,
userInfo.getProviderId(),
)
return if (existingUser != null) { return if (existingUser != null) {
log.debug("User found by provider: {}", userInfo.getProvider()) log.debug("User found by provider: {}", userInfo.getProvider())
@@ -51,17 +59,27 @@ class CustomOAuth2UserService(
if (userByEmail != null) { if (userByEmail != null) {
log.debug("Linking OAuth2 account to existing user: {}", userInfo.getEmail()) log.debug("Linking OAuth2 account to existing user: {}", userInfo.getEmail())
userRepository.saveWithOAuth2(userByEmail, userInfo.getProvider(), userInfo.getProviderId()) val entity =
userByEmail.toEntity(
provider = provider,
providerId = userInfo.getProviderId(),
)
userRepository.save(entity.toDomain())
} else { } else {
log.debug("Creating new user for provider: {}", userInfo.getProvider()) log.debug("Creating new user for provider: {}", userInfo.getProvider())
val newUser = User( val newUser =
id = idGenerator.generateId(), User(
name = userInfo.getName(), id = idGenerator.generateId(),
email = userInfo.getEmail(), name = userInfo.getName(),
password = "", email = userInfo.getEmail(),
library = null, library = null,
) )
userRepository.saveWithOAuth2(newUser, userInfo.getProvider(), userInfo.getProviderId()) val entity =
newUser.toEntity(
provider = provider,
providerId = userInfo.getProviderId(),
)
userRepository.save(entity.toDomain())
} }
} }
} }
@@ -3,9 +3,8 @@ package com.project.movienight.adapters.security
import com.project.movienight.application.ports.input.security.OAuth2UserInfo import com.project.movienight.application.ports.input.security.OAuth2UserInfo
class GoogleOAuth2UserInfo( class GoogleOAuth2UserInfo(
private val attributes: Map<String, Any> private val attributes: Map<String, Any>,
) : OAuth2UserInfo { ) : OAuth2UserInfo {
override fun getProviderId(): String = attributes["sub"] as String override fun getProviderId(): String = attributes["sub"] as String
override fun getEmail(): String = attributes["email"] as String override fun getEmail(): String = attributes["email"] as String
@@ -5,8 +5,10 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException
import org.springframework.security.oauth2.core.user.OAuth2User import org.springframework.security.oauth2.core.user.OAuth2User
object OAuth2UserInfoFactory { object OAuth2UserInfoFactory {
fun getOAuth2UserInfo(
fun getOAuth2UserInfo(registrationId: String, user: OAuth2User): OAuth2UserInfo { registrationId: String,
user: OAuth2User,
): OAuth2UserInfo {
val attributes = user.attributes val attributes = user.attributes
return when (registrationId.lowercase()) { return when (registrationId.lowercase()) {
@@ -10,19 +10,17 @@ import java.util.UUID
class UserPrincipal( class UserPrincipal(
private val user: User, private val user: User,
private val attributes: Map<String, Any>? = null, private val attributes: Map<String, Any>? = null,
) : OAuth2User, UserDetails { ) : OAuth2User,
UserDetails {
fun getId(): UUID = user.id fun getId(): UUID = user.id
override fun getName(): String = user.name override fun getName(): String = user.name
override fun getAttributes(): Map<String, Any> = attributes ?: emptyMap() override fun getAttributes(): Map<String, Any> = attributes ?: emptyMap()
override fun getAuthorities(): Collection<GrantedAuthority> { override fun getAuthorities(): Collection<GrantedAuthority> = listOf(SimpleGrantedAuthority("ROLE_USER"))
return listOf(SimpleGrantedAuthority("ROLE_USER"))
}
override fun getPassword(): String = user.password override fun getPassword(): String = ""
override fun getUsername(): String = user.email override fun getUsername(): String = user.email
@@ -35,8 +33,9 @@ class UserPrincipal(
override fun isEnabled(): Boolean = true override fun isEnabled(): Boolean = true
companion object { companion object {
fun create(user: User, attributes: Map<String, Any>? = null): UserPrincipal { fun create(
return UserPrincipal(user, attributes) user: User,
} attributes: Map<String, Any>? = null,
): UserPrincipal = UserPrincipal(user, attributes)
} }
} }
@@ -3,16 +3,14 @@ package com.project.movienight.adapters.security
import com.project.movienight.application.ports.input.security.OAuth2UserInfo import com.project.movienight.application.ports.input.security.OAuth2UserInfo
class VkOAuth2UserInfo( class VkOAuth2UserInfo(
private val attributes: Map<String, Any> private val attributes: Map<String, Any>,
) : OAuth2UserInfo { ) : OAuth2UserInfo {
override fun getProviderId(): String =
override fun getProviderId(): String { (attributes["response"] as? List<*>)
return (attributes["response"] as? List<*>)
?.firstOrNull() ?.firstOrNull()
?.let { it as? Map<*, *> } ?.let { it as? Map<*, *> }
?.get("id") ?.get("id")
?.toString() ?: "" ?.toString() ?: ""
}
override fun getEmail(): String = attributes["email"]?.toString() ?: "" override fun getEmail(): String = attributes["email"]?.toString() ?: ""
@@ -3,18 +3,16 @@ package com.project.movienight.adapters.security
import com.project.movienight.application.ports.input.security.OAuth2UserInfo import com.project.movienight.application.ports.input.security.OAuth2UserInfo
class YandexOAuth2UserInfo( class YandexOAuth2UserInfo(
private val attributes: Map<String, Any> private val attributes: Map<String, Any>,
) : OAuth2UserInfo { ) : OAuth2UserInfo {
override fun getProviderId(): String = attributes["id"]?.toString() ?: "" override fun getProviderId(): String = attributes["id"]?.toString() ?: ""
override fun getEmail(): String { override fun getEmail(): String =
return (attributes["emails"] as? List<*>) (attributes["emails"] as? List<*>)
?.firstOrNull() ?.firstOrNull()
?.let { it as? Map<*, *> } ?.let { it as? Map<*, *> }
?.get("value") ?.get("value")
?.toString() ?: "" ?.toString() ?: ""
}
override fun getName(): String = attributes["display_name"]?.toString() ?: "" override fun getName(): String = attributes["display_name"]?.toString() ?: ""
@@ -56,10 +56,11 @@ class FilmController(
FilmResponse.fromDomain( FilmResponse.fromDomain(
editFilmUseCase.edit( editFilmUseCase.edit(
id = id, id = id,
command = EditFilmCommand( command =
title = request.title, EditFilmCommand(
description = request.description, title = request.title,
), description = request.description,
),
), ),
) )
@@ -72,16 +73,13 @@ class FilmController(
@GetMapping("/{id}") @GetMapping("/{id}")
fun getById( fun getById(
@PathVariable id: UUID, @PathVariable id: UUID,
): FilmResponse = ): FilmResponse = FilmResponse.fromDomain(getFilmByIdUseCase.getById(id))
FilmResponse.fromDomain(getFilmByIdUseCase.getById(id))
@GetMapping("/search") @GetMapping("/search")
fun searchByTitle( fun searchByTitle(
@RequestParam title: String, @RequestParam title: String,
): FilmResponse? = ): FilmResponse? = searchFilmByTitleUseCase.searchByTitle(title)?.let { FilmResponse.fromDomain(it) }
searchFilmByTitleUseCase.searchByTitle(title)?.let { FilmResponse.fromDomain(it) }
@GetMapping @GetMapping
fun getAll(): List<FilmResponse> = fun getAll(): List<FilmResponse> = getAllFilmsUseCase.getAll().map { FilmResponse.fromDomain(it) }
getAllFilmsUseCase.getAll().map { FilmResponse.fromDomain(it) }
} }
@@ -63,9 +63,10 @@ class FilmLibraryController(
fun getAllFilmsInLibrary( fun getAllFilmsInLibrary(
@PathVariable userId: UUID, @PathVariable userId: UUID,
): List<FilmResponse> { ): List<FilmResponse> {
val library = getFilmLibraryUseCase.getLibrary( val library =
GetFilmLibraryQuery(userId = userId) getFilmLibraryUseCase.getLibrary(
) GetFilmLibraryQuery(userId = userId),
)
val film = getFilmByIdUseCase.getById(library.filmId) val film = getFilmByIdUseCase.getById(library.filmId)
@@ -103,9 +104,10 @@ class FilmLibraryController(
fun getAvailableFilms( fun getAvailableFilms(
@PathVariable userId: UUID, @PathVariable userId: UUID,
): List<FilmResponse> { ): List<FilmResponse> {
val userLibrary = getFilmLibraryUseCase.getLibrary( val userLibrary =
GetFilmLibraryQuery(userId = userId) getFilmLibraryUseCase.getLibrary(
) GetFilmLibraryQuery(userId = userId),
)
val allFilms = getAllFilmsUseCase.getAll() val allFilms = getAllFilmsUseCase.getAll()
@@ -46,14 +46,12 @@ class UserController(
) )
@GetMapping @GetMapping
fun getAll(): List<UserResponse> = fun getAll(): List<UserResponse> = getAllUsersUseCase.getAll().map { UserResponse.fromDomain(it) }
getAllUsersUseCase.getAll().map { UserResponse.fromDomain(it) }
@GetMapping("/{id}") @GetMapping("/{id}")
fun getById( fun getById(
@PathVariable id: UUID, @PathVariable id: UUID,
): UserResponse = ): UserResponse = UserResponse.fromDomain(getUserByIdUseCase.getById(id))
UserResponse.fromDomain(getUserByIdUseCase.getById(id))
@PatchMapping("/{id}") @PatchMapping("/{id}")
fun edit( fun edit(
@@ -63,9 +61,10 @@ class UserController(
UserResponse.fromDomain( UserResponse.fromDomain(
editUserUseCase.edit( editUserUseCase.edit(
id = id, id = id,
command = EditUserCommand( command =
name = request.name, EditUserCommand(
), name = request.name,
),
), ),
) )
@@ -2,8 +2,12 @@ package com.project.movienight.application.ports.input.security
interface OAuth2UserInfo { interface OAuth2UserInfo {
fun getProviderId(): String fun getProviderId(): String
fun getEmail(): String fun getEmail(): String
fun getName(): String fun getName(): String
fun getProvider(): String fun getProvider(): String
fun getAttributes(): Map<String, Any> fun getAttributes(): Map<String, Any>
} }
@@ -7,10 +7,6 @@ import java.util.UUID
interface UserRepositoryPort { interface UserRepositoryPort {
fun save(user: User): User 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 findById(id: UUID): User?
fun findByEmail(email: String): User? fun findByEmail(email: String): User?
@@ -28,7 +28,6 @@ class FilmService(
GetFilmByIdUseCase, GetFilmByIdUseCase,
GetAllFilmsUseCase, GetAllFilmsUseCase,
SearchFilmByTitleUseCase { SearchFilmByTitleUseCase {
override fun create(command: CreateFilmCommand): Film { override fun create(command: CreateFilmCommand): Film {
if (filmConfig.isBlocked(command.title)) { if (filmConfig.isBlocked(command.title)) {
throw BlockedValueException(target = "Film", field = "title") throw BlockedValueException(target = "Film", field = "title")
@@ -37,15 +36,19 @@ class FilmService(
throw BlockedValueException(target = "Film", field = "description") throw BlockedValueException(target = "Film", field = "description")
} }
val film = Film( val film =
id = idGenerator.generateId(), Film(
title = command.title, id = idGenerator.generateId(),
description = command.description, title = command.title,
) description = command.description,
)
return filmRepository.save(film) return filmRepository.save(film)
} }
override fun edit(id: UUID, command: EditFilmCommand): Film { override fun edit(
id: UUID,
command: EditFilmCommand,
): Film {
if (filmConfig.isBlocked(command.title)) { if (filmConfig.isBlocked(command.title)) {
throw BlockedValueException(target = "Film", field = "title") throw BlockedValueException(target = "Film", field = "title")
} }
@@ -63,9 +66,8 @@ class FilmService(
filmRepository.deleteById(id) filmRepository.deleteById(id)
} }
override fun getById(id: UUID): Film { override fun getById(id: UUID): Film =
return filmRepository.findById(id) ?: throw EntityNotFoundException(entity = "Film", id = id.toString()) filmRepository.findById(id) ?: throw EntityNotFoundException(entity = "Film", id = id.toString())
}
override fun getAll(): List<Film> = filmRepository.findAll() override fun getAll(): List<Film> = filmRepository.findAll()
@@ -26,7 +26,6 @@ class UserService(
DeleteUserUseCase, DeleteUserUseCase,
GetUserByIdUseCase, GetUserByIdUseCase,
GetAllUsersUseCase { GetAllUsersUseCase {
override fun create(command: CreateUserCommand): User { override fun create(command: CreateUserCommand): User {
if (userConfig.isBlocked(command.name)) { if (userConfig.isBlocked(command.name)) {
throw BlockedValueException(target = "User", field = "name") throw BlockedValueException(target = "User", field = "name")
@@ -37,7 +36,6 @@ class UserService(
id = idGenerator.generateId(), id = idGenerator.generateId(),
name = command.name, name = command.name,
email = command.email, email = command.email,
password = "",
library = null, library = null,
) )
return userRepository.save(user) return userRepository.save(user)
@@ -61,9 +59,8 @@ class UserService(
userRepository.deleteById(id) userRepository.deleteById(id)
} }
override fun getById(id: UUID): User { override fun getById(id: UUID): User =
return userRepository.findById(id) ?: throw EntityNotFoundException(entity = "User", id = id.toString()) userRepository.findById(id) ?: throw EntityNotFoundException(entity = "User", id = id.toString())
}
override fun getAll(): List<User> = userRepository.findAll() override fun getAll(): List<User> = userRepository.findAll()
} }
@@ -6,6 +6,5 @@ data class User(
val id: UUID, val id: UUID,
val name: String, val name: String,
val email: String, val email: String,
val password: String,
val library: FilmLibrary?, val library: FilmLibrary?,
) )
+3 -1
View File
@@ -2,7 +2,9 @@ CREATE TABLE IF NOT EXISTS public.users (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
email VARCHAR(320) NOT NULL UNIQUE, email VARCHAR(320) NOT NULL UNIQUE,
password VARCHAR(255) provider VARCHAR(64),
provider_id VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
); );
CREATE TABLE IF NOT EXISTS public.films ( CREATE TABLE IF NOT EXISTS public.films (
@@ -1,9 +0,0 @@
ALTER TABLE public.users ADD COLUMN provider VARCHAR(20);
ALTER TABLE public.users ADD COLUMN provider_id VARCHAR(255);
ALTER TABLE public.users ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
CREATE UNIQUE INDEX idx_users_provider_provider_id
ON public.users(provider, provider_id);
CREATE INDEX idx_users_email ON public.users(email);
@@ -9,17 +9,17 @@ import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNull
class UserEntityMappingTest { class UserEntityMappingTest {
@Test @Test
fun `toDomain maps UserEntity correctly`() { fun `toDomain maps UserEntity correctly`() {
val entity = UserEntity( val entity =
id = UUID.randomUUID(), UserEntity(
name = "John Pork", id = UUID.randomUUID(),
email = "john@email.com", name = "John Pork",
provider = "GOOGLE", email = "john@email.com",
providerId = "google1234", provider = "GOOGLE",
createdAt = LocalDateTime.now(), providerId = "google1234",
) createdAt = LocalDateTime.now(),
)
val user = entity.toDomain() val user = entity.toDomain()
assertEquals(entity.id, user.id) assertEquals(entity.id, user.id)
@@ -30,12 +30,13 @@ class UserEntityMappingTest {
@Test @Test
fun `toEntity maps User with OAuth provider`() { fun `toEntity maps User with OAuth provider`() {
val user = User( val user =
id = UUID.randomUUID(), User(
name = "Jane", id = UUID.randomUUID(),
email = "jane@mail.com", name = "Jane",
library = null email = "jane@mail.com",
) library = null,
)
val entity = user.toEntity(AuthProvider.YANDEX, "yandex456") val entity = user.toEntity(AuthProvider.YANDEX, "yandex456")
@@ -48,12 +49,13 @@ class UserEntityMappingTest {
@Test @Test
fun `toEntity maps User without OAuth provider`() { fun `toEntity maps User without OAuth provider`() {
val user = User( val user =
id = UUID.randomUUID(), User(
name = "Bob", id = UUID.randomUUID(),
email = "bob@mail.com", name = "Bob",
library = null email = "bob@mail.com",
) library = null,
)
val entity = user.toEntity() val entity = user.toEntity()
@@ -63,12 +65,13 @@ class UserEntityMappingTest {
@Test @Test
fun `mapping is reversible for basic fields`() { fun `mapping is reversible for basic fields`() {
val original = User( val original =
id = UUID.randomUUID(), User(
name = "Alice", id = UUID.randomUUID(),
email = "alice@email.com", name = "Alice",
library = null email = "alice@email.com",
) library = null,
)
val mapped = original.toEntity().toDomain() val mapped = original.toEntity().toDomain()
@@ -76,6 +79,4 @@ class UserEntityMappingTest {
assertEquals(original.name, mapped.name) assertEquals(original.name, mapped.name)
assertEquals(original.email, mapped.email) assertEquals(original.email, mapped.email)
} }
} }