Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
rinotc committed Feb 19, 2024
2 parents 8dba6ad + 7ecae31 commit 597ccfc
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import dev.tchiba.sdmt.core.boundedContext.BoundedContextId
import dev.tchiba.sdmt.core.domainmodel.{EnglishName, Knowledge, UbiquitousName}
import dev.tchiba.sdmt.usecase.domainmodel.create.CreateDomainModelInput
import interfaces.json.request.play.{PlayJsonRequest, PlayJsonRequestCompanion}
import interfaces.json.{JsonRequest, JsonValidator}
import play.api.libs.json.{Json, OFormat}
import play.api.mvc.{BodyParser, PlayBodyParsers}

import scala.concurrent.ExecutionContext

case class CreateDomainModelRequest(
ubiquitousName: String,
Expand Down
39 changes: 39 additions & 0 deletions app/interfaces/api/user/edit/email/EditUserEmailController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package interfaces.api.user.edit.email

import dev.tchiba.auth.usecase.user.edit.email.{ChangeEmailOutput, ChangeEmailUseCase}
import interfaces.api.SdmtApiController
import interfaces.api.user.json.UserResponse
import interfaces.security.UserAction
import play.api.mvc.{Action, ControllerComponents}

import javax.inject.Inject
import scala.concurrent.ExecutionContext

class EditUserEmailController @Inject() (
cc: ControllerComponents,
userAction: UserAction,
changeEmailUseCase: ChangeEmailUseCase
)(implicit ec: ExecutionContext)
extends SdmtApiController(cc) {

def action(): Action[EditUserEmailRequest] = userAction(EditUserEmailRequest.validateJson) { implicit request =>
val input = request.body.get(request.user.id)
changeEmailUseCase.handle(input) match {
case ChangeEmailOutput.Success(user) =>
val response = UserResponse(user)
Ok(response.json)
case ChangeEmailOutput.NotFoundUser(userId) =>
notFound(
code = "sdmt.user.edit.email.notFoundUser",
message = "not found target user.",
params = "userId" -> userId.value
)
case ChangeEmailOutput.EmailIsNotUnique(email) =>
badRequest(
code = "sdmt.user.edit.email.isNotUnique",
message = "input email address is not unique.",
params = "email" -> email.value
)
}
}
}
20 changes: 20 additions & 0 deletions app/interfaces/api/user/edit/email/EditUserEmailRequest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package interfaces.api.user.edit.email

import cats.data.ValidatedNel
import dev.tchiba.auth.core.user.UserId
import dev.tchiba.auth.usecase.user.edit.email.ChangeEmailInput
import dev.tchiba.sub.email.EmailAddress
import interfaces.json.request.play.{PlayJsonRequest, PlayJsonRequestCompanion}
import play.api.libs.json.{Json, OFormat}

case class EditUserEmailRequest(email: String) extends PlayJsonRequest {

override type VM = UserId => ChangeEmailInput

override def validateParameters: ValidatedNel[(String, String), VM] =
EmailAddress.validate(email).toValidated("email").map(e => ChangeEmailInput(_, e))
}

object EditUserEmailRequest extends PlayJsonRequestCompanion[EditUserEmailRequest] {
implicit val jsonFormat: OFormat[EditUserEmailRequest] = Json.format[EditUserEmailRequest]
}
4 changes: 2 additions & 2 deletions app/interfaces/api/user/json/UserResponse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ final class UserResponse(private val user: User) {

import UserResponse._

private val response = Response(user.id.string, user.name)
private val response = Response(user.id.string, user.name, user.email.value, user.avatarUrl.map(_.value))

def json: JsValue = Json.toJson(response)
}

object UserResponse {

private case class Response(id: String, name: Option[String])
private case class Response(id: String, name: Option[String], email: String, avatarUrl: Option[String])

implicit private val jsonFormat: OFormat[Response] = Json.format[Response]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dev.tchiba.auth.application.interactors.user.edit.email

import dev.tchiba.auth.core.user.UserRepository
import dev.tchiba.auth.core.user.specs.email.unique.UserEmailUniqueChecker
import dev.tchiba.auth.usecase.user.edit.email.{ChangeEmailInput, ChangeEmailOutput, ChangeEmailUseCase}

import javax.inject.Inject

class ChangeEmailInteractor @Inject() (
userEmailUniqueChecker: UserEmailUniqueChecker,
userRepository: UserRepository
) extends ChangeEmailUseCase {

import ChangeEmailOutput._
override def handle(input: ChangeEmailInput): ChangeEmailOutput = {
val result = for {
user <- userRepository.findById(input.userId).toRight(NotFoundUser(input.userId))
emailIsUniqueMessage <- userEmailUniqueChecker.check(input.email).left.map { _ => EmailIsNotUnique(input.email) }
} yield {
val emailUpdatedUser = user.changeEmail(emailIsUniqueMessage)
userRepository.update(emailUpdatedUser)
Success(emailUpdatedUser)
}

result.unwrap
}
}
23 changes: 21 additions & 2 deletions auth-core/src/main/scala/dev/tchiba/auth/core/user/User.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package dev.tchiba.auth.core.user

import dev.tchiba.arch.ddd.{Aggregate, Entity}
import dev.tchiba.auth.core.user.specs.email.unique.UserEmailUniqueChecker.EmailIsUnique
import dev.tchiba.sub.email.EmailAddress
import dev.tchiba.sub.url.Url

/**
* ユーザー
*
* @param id ユーザーID
* @param name 氏名
* @param id
* ユーザーID
* @param name
* 氏名
*/
final class User private (
val id: UserId,
Expand All @@ -17,9 +20,25 @@ final class User private (
val avatarUrl: Option[Url]
) extends Entity[UserId]
with Aggregate {

/**
* メールアドレスを変更する
* @param message
* 変更しようとしているメールアドレスがユニークであることを示すメッセージ
*/
def changeEmail(message: EmailIsUnique): User = copy(email = message.email)

override def canEqual(that: Any): Boolean = that.isInstanceOf[User]

override def toString = s"User(id=${id.value}, name=$name, email=${email.value}, avatarUrl=${avatarUrl.map(_.value)})"

private def copy(
id: UserId = this.id,
name: Option[String] = this.name,
email: EmailAddress = this.email,
avatarUrl: Option[Url] = this.avatarUrl
) =
new User(id, name, email, avatarUrl)
}

object User {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package dev.tchiba.auth.core.user.specs.email.unique

import dev.tchiba.arch.ddd.DomainService
import dev.tchiba.sub.email.EmailAddress

trait UserEmailUniqueChecker extends DomainService {
import UserEmailUniqueChecker._

/**
* メールアドレスがユニークなものであるか調べる
*
* @param email
* 調べるメールアドレス
* @return
* 引数のメールアドレスがまだDBに同じものが存在せず、ユニークである時、[[Right]]で[[EmailIsUnique]]メッセージを返す
*/
def check(email: EmailAddress): Either[Because, EmailIsUnique] =
Either.cond(isNotExist(email), new EmailIsUnique(email), EmailAlreadyExist)

/**
* 引数のメールアドレスを持つユーザーがまだDB上に存在しない
* @param email
* メールアドレス
* @return
* 存在する時: `false`, 存在しない時: `true`
*/
protected def isNotExist(email: EmailAddress): Boolean
}

object UserEmailUniqueChecker {

final class EmailIsUnique private[unique] (val email: EmailAddress)

sealed trait Because

case object EmailAlreadyExist extends Because
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ final class JdbcUserRepository extends UserRepository with UsersTranslator {
)
.where
.eq(c.userId, user.id.string)
}
}.update().apply()
}

override def delete(user: User): Unit = DB localTx { implicit session =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.tchiba.auth.infra.core.user.specs.email.unique

import dev.tchiba.auth.core.user.specs.email.unique.UserEmailUniqueChecker
import dev.tchiba.sdmt.infra.scalikejdbc.Users
import dev.tchiba.sub.email.EmailAddress
import scalikejdbc._
class JdbcUserEmailUniqueChecker extends UserEmailUniqueChecker {

private val u = Users.u
override protected def isNotExist(email: EmailAddress): Boolean = DB readOnly { implicit session =>
withSQL {
select(sqls.count)
.from(Users.as(u))
.where
.eq(u.emailAddress, email.value)
}.map(rs => rs.int(1)).single().apply().get == 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ import com.google.inject.AbstractModule
import dev.tchiba.auth.application.interactors.signIn.SignInInteractor
import dev.tchiba.auth.application.interactors.signOut.SignOutInteractor
import dev.tchiba.auth.application.interactors.signUp.SignUpInteractor
import dev.tchiba.auth.application.interactors.user.edit.email.ChangeEmailInteractor
import dev.tchiba.auth.application.interactors.user.verify.VerifyUserInteractor
import dev.tchiba.auth.application.services.AuthInfoUserLinker
import dev.tchiba.auth.core.accessToken.AccessTokenService
import dev.tchiba.auth.core.authInfo.AuthInfoRepository
import dev.tchiba.auth.core.user.specs.email.unique.UserEmailUniqueChecker
import dev.tchiba.auth.infra.application.services.JdbcAuthInfoUserLinker
import dev.tchiba.auth.infra.core.accessToken.MemcachedAccessTokenService
import dev.tchiba.auth.infra.core.authInfo.JdbcAuthInfoRepository
import dev.tchiba.auth.infra.core.user.specs.email.unique.JdbcUserEmailUniqueChecker
import dev.tchiba.auth.usecase.signIn.SignInUseCase
import dev.tchiba.auth.usecase.signOut.SignOutUseCase
import dev.tchiba.auth.usecase.signUp.SignUpUseCase
import dev.tchiba.auth.usecase.user.edit.email.ChangeEmailUseCase
import dev.tchiba.auth.usecase.user.verify.VerifyUserUseCase
import net.codingwell.scalaguice.ScalaModule

Expand All @@ -23,9 +27,12 @@ class AuthModule extends AbstractModule with ScalaModule {
bind[SignInUseCase].to[SignInInteractor]
bind[SignOutUseCase].to[SignOutInteractor]
bind[VerifyUserUseCase].to[VerifyUserInteractor]
bind[ChangeEmailUseCase].to[ChangeEmailInteractor]

bind[AuthInfoRepository].to[JdbcAuthInfoRepository]

bind[UserEmailUniqueChecker].to[JdbcUserEmailUniqueChecker]

bind[AccessTokenService].to[MemcachedAccessTokenService]
bind[AuthInfoUserLinker].to[JdbcAuthInfoUserLinker]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dev.tchiba.auth.usecase.user.edit.email

import dev.tchiba.arch.usecase.Input
import dev.tchiba.auth.core.user.UserId
import dev.tchiba.sub.email.EmailAddress

/**
* メールアドレス変更ユースケースの入力
*
* @param userId
* 変更対象のユーザーID
* @param email
* 置き換えるメールアドレス
*/
final case class ChangeEmailInput(userId: UserId, email: EmailAddress) extends Input[ChangeEmailOutput]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dev.tchiba.auth.usecase.user.edit.email

import dev.tchiba.arch.usecase.Output
import dev.tchiba.auth.core.user.{User, UserId}
import dev.tchiba.sub.email.EmailAddress

sealed abstract class ChangeEmailOutput extends Output

object ChangeEmailOutput {

final case class Success(user: User) extends ChangeEmailOutput

final case class NotFoundUser(userId: UserId) extends ChangeEmailOutput

final case class EmailIsNotUnique(email: EmailAddress) extends ChangeEmailOutput
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.tchiba.auth.usecase.user.edit.email

import dev.tchiba.arch.usecase.UseCase

/**
* ユーザーのメールアドレス変更ユースケース
*/
abstract class ChangeEmailUseCase extends UseCase[ChangeEmailInput, ChangeEmailOutput]
2 changes: 2 additions & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ POST /api/sign-out
GET /api/users interfaces.api.user.UserApiController.allUsers()
GET /api/users/:userId interfaces.api.user.UserApiController.findUser(userId: String)

PUT /api/users/self/edit-email interfaces.api.user.edit.email.EditUserEmailController.action()

GET /api/bounded-contexts interfaces.api.boundedContext.list.ListBoundedContextsApiController.action()
POST /api/bounded-contexts interfaces.api.boundedContext.create.CreateBoundedContextApiController.action()
GET /api/bounded-contexts/:idOrAlias interfaces.api.boundedContext.find.FindBoundedContextApiController.action(idOrAlias: String)
Expand Down

0 comments on commit 597ccfc

Please sign in to comment.