Move transactions to domain layer
This commit is contained in:
parent
11caff1634
commit
bf56314473
@ -25,7 +25,6 @@ dependencies {
|
||||
implementation(Libs.javaxServlet)
|
||||
implementation(Libs.kotlinxSerializationJson)
|
||||
implementation(Libs.logbackClassic)
|
||||
implementation(Libs.ktormCore)
|
||||
|
||||
implementation(Libs.micronaut)
|
||||
kapt(Libs.micronautProcessor)
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
package be.simplenotes.app.filters
|
||||
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.HttpHandler
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TransactionFilter(private val db: Database) : Filter {
|
||||
override fun invoke(next: HttpHandler): HttpHandler = { request ->
|
||||
db.useTransaction {
|
||||
next(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,10 +2,8 @@ package be.simplenotes.app.routes
|
||||
|
||||
import be.simplenotes.app.api.ApiNoteController
|
||||
import be.simplenotes.app.api.ApiUserController
|
||||
import be.simplenotes.app.filters.TransactionFilter
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthFilter
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthLens
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.Method.*
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.then
|
||||
@ -21,16 +19,11 @@ import javax.inject.Singleton
|
||||
class ApiRoutes(
|
||||
private val apiUserController: ApiUserController,
|
||||
private val apiNoteController: ApiNoteController,
|
||||
private val transaction: TransactionFilter,
|
||||
@Named("api") private val auth: RequiredAuthFilter,
|
||||
@Named("required") private val authLens: RequiredAuthLens,
|
||||
) : Supplier<RoutingHttpHandler> {
|
||||
override fun get(): RoutingHttpHandler {
|
||||
|
||||
fun Filter.then(next: ProtectedHandler) = then { req: Request ->
|
||||
next(req, authLens(req))
|
||||
}
|
||||
|
||||
infix fun PathMethod.to(action: ProtectedHandler) =
|
||||
this to { req: Request -> action(req, authLens(req)) }
|
||||
|
||||
@ -41,10 +34,10 @@ class ApiRoutes(
|
||||
auth.then(
|
||||
routes(
|
||||
"/" bind GET to ::notes,
|
||||
"/" bind POST to transaction.then(::createNote),
|
||||
"/" bind POST to ::createNote,
|
||||
"/search" bind POST to ::search,
|
||||
"/{uuid}" bind GET to ::note,
|
||||
"/{uuid}" bind PUT to transaction.then(::update),
|
||||
"/{uuid}" bind PUT to ::update,
|
||||
)
|
||||
).withBasePath("/notes")
|
||||
}
|
||||
|
||||
@ -5,11 +5,9 @@ import be.simplenotes.app.controllers.HealthCheckController
|
||||
import be.simplenotes.app.controllers.NoteController
|
||||
import be.simplenotes.app.controllers.UserController
|
||||
import be.simplenotes.app.filters.ImmutableFilter
|
||||
import be.simplenotes.app.filters.TransactionFilter
|
||||
import be.simplenotes.app.filters.auth.OptionalAuthFilter
|
||||
import be.simplenotes.app.filters.auth.OptionalAuthLens
|
||||
import org.http4k.core.ContentType
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.Method.GET
|
||||
import org.http4k.core.Method.POST
|
||||
import org.http4k.core.Request
|
||||
@ -27,15 +25,10 @@ class BasicRoutes(
|
||||
private val noteCtrl: NoteController,
|
||||
@Named("optional") private val authLens: OptionalAuthLens,
|
||||
private val auth: OptionalAuthFilter,
|
||||
private val transactionFilter: TransactionFilter,
|
||||
) : Supplier<RoutingHttpHandler> {
|
||||
|
||||
override fun get(): RoutingHttpHandler {
|
||||
|
||||
fun Filter.then(next: PublicHandler) = then { req: Request ->
|
||||
next(req, authLens(req))
|
||||
}
|
||||
|
||||
infix fun PathMethod.to(action: PublicHandler) =
|
||||
this to { req: Request -> action(req, authLens(req)) }
|
||||
|
||||
@ -52,7 +45,7 @@ class BasicRoutes(
|
||||
routes(
|
||||
"/" bind GET to baseCtrl::index,
|
||||
"/register" bind GET to userCtrl::register,
|
||||
"/register" bind POST to transactionFilter.then(userCtrl::register),
|
||||
"/register" bind POST to userCtrl::register,
|
||||
"/login" bind GET to userCtrl::login,
|
||||
"/login" bind POST to userCtrl::login,
|
||||
"/logout" bind POST to userCtrl::logout,
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
package be.simplenotes.app.routes
|
||||
|
||||
import be.simplenotes.app.controllers.NoteController
|
||||
import be.simplenotes.app.filters.TransactionFilter
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthFilter
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthLens
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.Method.GET
|
||||
import org.http4k.core.Method.POST
|
||||
import org.http4k.core.Request
|
||||
@ -20,16 +18,11 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class NoteRoutes(
|
||||
private val noteCtrl: NoteController,
|
||||
private val transaction: TransactionFilter,
|
||||
private val auth: RequiredAuthFilter,
|
||||
@Named("required") private val authLens: RequiredAuthLens,
|
||||
) : Supplier<RoutingHttpHandler> {
|
||||
override fun get(): RoutingHttpHandler {
|
||||
|
||||
fun Filter.then(next: ProtectedHandler) = then { req: Request ->
|
||||
next(req, authLens(req))
|
||||
}
|
||||
|
||||
infix fun PathMethod.to(action: ProtectedHandler) =
|
||||
this to { req: Request -> action(req, authLens(req)) }
|
||||
|
||||
@ -39,13 +32,13 @@ class NoteRoutes(
|
||||
"/" bind GET to ::list,
|
||||
"/" bind POST to ::search,
|
||||
"/new" bind GET to ::new,
|
||||
"/new" bind POST to transaction.then(::new),
|
||||
"/new" bind POST to ::new,
|
||||
"/trash" bind GET to ::trash,
|
||||
"/{uuid}" bind GET to ::note,
|
||||
"/{uuid}" bind POST to transaction.then(::note),
|
||||
"/{uuid}" bind POST to ::note,
|
||||
"/{uuid}/edit" bind GET to ::edit,
|
||||
"/{uuid}/edit" bind POST to transaction.then(::edit),
|
||||
"/deleted/{uuid}" bind POST to transaction.then(::deleted),
|
||||
"/{uuid}/edit" bind POST to ::edit,
|
||||
"/deleted/{uuid}" bind POST to ::deleted,
|
||||
).withBasePath("/notes")
|
||||
}
|
||||
)
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
package be.simplenotes.app.routes
|
||||
|
||||
import be.simplenotes.app.controllers.SettingsController
|
||||
import be.simplenotes.app.filters.TransactionFilter
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthFilter
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthLens
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.Method.GET
|
||||
import org.http4k.core.Method.POST
|
||||
import org.http4k.core.Request
|
||||
@ -20,23 +18,18 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class SettingsRoutes(
|
||||
private val settingsController: SettingsController,
|
||||
private val transaction: TransactionFilter,
|
||||
private val auth: RequiredAuthFilter,
|
||||
@Named("required") private val authLens: RequiredAuthLens,
|
||||
) : Supplier<RoutingHttpHandler> {
|
||||
override fun get(): RoutingHttpHandler {
|
||||
|
||||
fun Filter.then(next: ProtectedHandler) = then { req: Request ->
|
||||
next(req, authLens(req))
|
||||
}
|
||||
|
||||
infix fun PathMethod.to(action: ProtectedHandler) =
|
||||
this to { req: Request -> action(req, authLens(req)) }
|
||||
|
||||
return auth.then(
|
||||
routes(
|
||||
"/settings" bind GET to settingsController::settings,
|
||||
"/settings" bind POST to transaction.then(settingsController::settings),
|
||||
"/settings" bind POST to settingsController::settings,
|
||||
"/export" bind POST to settingsController::export,
|
||||
)
|
||||
)
|
||||
|
||||
@ -6,6 +6,7 @@ import be.simplenotes.domain.usecases.markdown.MarkdownConverter
|
||||
import be.simplenotes.domain.usecases.markdown.MarkdownParsingError
|
||||
import be.simplenotes.persistance.repositories.NoteRepository
|
||||
import be.simplenotes.persistance.repositories.UserRepository
|
||||
import be.simplenotes.persistance.transactions.TransactionService
|
||||
import be.simplenotes.search.NoteSearcher
|
||||
import be.simplenotes.search.SearchTerms
|
||||
import be.simplenotes.types.LoggedInUser
|
||||
@ -24,30 +25,35 @@ class NoteService(
|
||||
private val userRepository: UserRepository,
|
||||
private val searcher: NoteSearcher,
|
||||
private val htmlSanitizer: HtmlSanitizer,
|
||||
private val transaction: TransactionService,
|
||||
) {
|
||||
|
||||
fun create(user: LoggedInUser, markdownText: String) = either.eager<MarkdownParsingError, PersistedNote> {
|
||||
val persistedNote = !markdownConverter.renderDocument(markdownText)
|
||||
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
|
||||
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
|
||||
.map { noteRepository.create(user.userId, it) }
|
||||
fun create(user: LoggedInUser, markdownText: String) = transaction.use {
|
||||
either.eager<MarkdownParsingError, PersistedNote> {
|
||||
val persistedNote = !markdownConverter.renderDocument(markdownText)
|
||||
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
|
||||
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
|
||||
.map { noteRepository.create(user.userId, it) }
|
||||
|
||||
searcher.indexNote(user.userId, persistedNote)
|
||||
persistedNote
|
||||
searcher.indexNote(user.userId, persistedNote)
|
||||
persistedNote
|
||||
}
|
||||
}
|
||||
|
||||
fun update(
|
||||
user: LoggedInUser,
|
||||
uuid: UUID,
|
||||
markdownText: String,
|
||||
) = either.eager<MarkdownParsingError, PersistedNote?> {
|
||||
val persistedNote = !markdownConverter.renderDocument(markdownText)
|
||||
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
|
||||
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
|
||||
.map { noteRepository.update(user.userId, uuid, it) }
|
||||
) = transaction.use {
|
||||
either.eager<MarkdownParsingError, PersistedNote?> {
|
||||
val persistedNote = !markdownConverter.renderDocument(markdownText)
|
||||
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
|
||||
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
|
||||
.map { noteRepository.update(user.userId, uuid, it) }
|
||||
|
||||
persistedNote?.let { searcher.updateIndex(user.userId, it) }
|
||||
persistedNote
|
||||
persistedNote?.let { searcher.updateIndex(user.userId, it) }
|
||||
persistedNote
|
||||
}
|
||||
}
|
||||
|
||||
fun paginatedNotes(
|
||||
@ -66,22 +72,22 @@ class NoteService(
|
||||
|
||||
fun find(userId: Int, uuid: UUID) = noteRepository.find(userId, uuid)
|
||||
|
||||
fun trash(userId: Int, uuid: UUID): Boolean {
|
||||
fun trash(userId: Int, uuid: UUID): Boolean = transaction.use {
|
||||
val res = noteRepository.delete(userId, uuid, permanent = false)
|
||||
if (res) searcher.deleteIndex(userId, uuid)
|
||||
return res
|
||||
res
|
||||
}
|
||||
|
||||
fun restore(userId: Int, uuid: UUID): Boolean {
|
||||
fun restore(userId: Int, uuid: UUID): Boolean = transaction.use {
|
||||
val res = noteRepository.restore(userId, uuid)
|
||||
if (res) find(userId, uuid)?.let { note -> searcher.indexNote(userId, note) }
|
||||
return res
|
||||
res
|
||||
}
|
||||
|
||||
fun delete(userId: Int, uuid: UUID): Boolean {
|
||||
fun delete(userId: Int, uuid: UUID): Boolean = transaction.use {
|
||||
val res = noteRepository.delete(userId, uuid, permanent = true)
|
||||
if (res) searcher.deleteIndex(userId, uuid)
|
||||
return res
|
||||
res
|
||||
}
|
||||
|
||||
fun countDeleted(userId: Int) = noteRepository.count(userId, deleted = true)
|
||||
@ -101,8 +107,8 @@ class NoteService(
|
||||
@PreDestroy
|
||||
fun dropAllIndexes() = searcher.dropAll()
|
||||
|
||||
fun makePublic(userId: Int, uuid: UUID) = noteRepository.makePublic(userId, uuid)
|
||||
fun makePrivate(userId: Int, uuid: UUID) = noteRepository.makePrivate(userId, uuid)
|
||||
fun makePublic(userId: Int, uuid: UUID) = transaction.use { noteRepository.makePublic(userId, uuid) }
|
||||
fun makePrivate(userId: Int, uuid: UUID) = transaction.use { noteRepository.makePrivate(userId, uuid) }
|
||||
fun findPublic(uuid: UUID) = noteRepository.findPublic(uuid)
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import arrow.core.rightIfNotNull
|
||||
import be.simplenotes.domain.security.PasswordHash
|
||||
import be.simplenotes.domain.validation.UserValidations
|
||||
import be.simplenotes.persistance.repositories.UserRepository
|
||||
import be.simplenotes.persistance.transactions.TransactionService
|
||||
import be.simplenotes.search.NoteSearcher
|
||||
import io.micronaut.context.annotation.Primary
|
||||
import javax.inject.Singleton
|
||||
@ -16,16 +17,19 @@ internal class DeleteUseCaseImpl(
|
||||
private val userRepository: UserRepository,
|
||||
private val passwordHash: PasswordHash,
|
||||
private val searcher: NoteSearcher,
|
||||
private val transactionService: TransactionService,
|
||||
) : DeleteUseCase {
|
||||
override fun delete(form: DeleteForm) = either.eager<DeleteError, Unit> {
|
||||
val user = !UserValidations.validateDelete(form)
|
||||
val persistedUser = !userRepository.find(user.username).rightIfNotNull { DeleteError.Unregistered }
|
||||
!Either.conditionally(
|
||||
passwordHash.verify(user.password, persistedUser.password),
|
||||
{ DeleteError.WrongPassword },
|
||||
{ Unit }
|
||||
)
|
||||
!Either.conditionally(userRepository.delete(persistedUser.id), { DeleteError.Unregistered }, { Unit })
|
||||
searcher.dropIndex(persistedUser.id)
|
||||
override fun delete(form: DeleteForm) = transactionService.use {
|
||||
either.eager<DeleteError, Unit> {
|
||||
val user = !UserValidations.validateDelete(form)
|
||||
val persistedUser = !userRepository.find(user.username).rightIfNotNull { DeleteError.Unregistered }
|
||||
!Either.conditionally(
|
||||
passwordHash.verify(user.password, persistedUser.password),
|
||||
{ DeleteError.WrongPassword },
|
||||
{ Unit }
|
||||
)
|
||||
!Either.conditionally(userRepository.delete(persistedUser.id), { DeleteError.Unregistered }, { Unit })
|
||||
searcher.dropIndex(persistedUser.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import arrow.core.leftIfNull
|
||||
import be.simplenotes.domain.security.PasswordHash
|
||||
import be.simplenotes.domain.validation.UserValidations
|
||||
import be.simplenotes.persistance.repositories.UserRepository
|
||||
import be.simplenotes.persistance.transactions.TransactionService
|
||||
import be.simplenotes.types.PersistedUser
|
||||
import io.micronaut.context.annotation.Primary
|
||||
import javax.inject.Singleton
|
||||
@ -14,10 +15,11 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
internal class RegisterUseCaseImpl(
|
||||
private val userRepository: UserRepository,
|
||||
private val passwordHash: PasswordHash
|
||||
private val passwordHash: PasswordHash,
|
||||
private val transactionService: TransactionService,
|
||||
) : RegisterUseCase {
|
||||
override fun register(form: RegisterForm): Either<RegisterError, PersistedUser> {
|
||||
return UserValidations.validateRegister(form)
|
||||
override fun register(form: RegisterForm): Either<RegisterError, PersistedUser> = transactionService.use {
|
||||
UserValidations.validateRegister(form)
|
||||
.filterOrElse({ !userRepository.exists(it.username) }, { UserExists })
|
||||
.map { it.copy(password = passwordHash.crypt(it.password)) }
|
||||
.map { userRepository.create(it) }
|
||||
|
||||
@ -4,6 +4,7 @@ import be.simplenotes.domain.security.BcryptPasswordHash
|
||||
import be.simplenotes.domain.testutils.isLeftOfType
|
||||
import be.simplenotes.domain.testutils.isRight
|
||||
import be.simplenotes.persistance.repositories.UserRepository
|
||||
import be.simplenotes.persistance.transactions.TransactionService
|
||||
import be.simplenotes.types.PersistedUser
|
||||
import com.natpryce.hamkrest.assertion.assertThat
|
||||
import com.natpryce.hamkrest.equalTo
|
||||
@ -16,7 +17,10 @@ internal class RegisterUseCaseImplTest {
|
||||
// region setup
|
||||
private val mockUserRepository = mockk<UserRepository>()
|
||||
private val passwordHash = BcryptPasswordHash(test = true)
|
||||
private val registerUseCase = RegisterUseCaseImpl(mockUserRepository, passwordHash)
|
||||
private val noopTransactionService = object : TransactionService {
|
||||
override fun <T> use(block: () -> T) = block()
|
||||
}
|
||||
private val registerUseCase = RegisterUseCaseImpl(mockUserRepository, passwordHash, noopTransactionService)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package be.simplenotes.persistance.transactions
|
||||
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class KtormTransactionService(private val database: Database) : TransactionService {
|
||||
override fun <T> use(block: () -> T) = database.useTransaction { block() }
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package be.simplenotes.persistance.transactions
|
||||
|
||||
interface TransactionService {
|
||||
fun <T> use(block: () -> T): T
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user