Compare commits

...

4 Commits

19 changed files with 103 additions and 112 deletions

View File

@ -10,8 +10,6 @@ plugins {
} }
dependencies { dependencies {
implementation(project(":simplenotes-persistance"))
implementation(project(":simplenotes-search"))
implementation(project(":simplenotes-domain")) implementation(project(":simplenotes-domain"))
implementation(project(":simplenotes-types")) implementation(project(":simplenotes-types"))
implementation(project(":simplenotes-config")) implementation(project(":simplenotes-config"))
@ -25,7 +23,6 @@ dependencies {
implementation(Libs.javaxServlet) implementation(Libs.javaxServlet)
implementation(Libs.kotlinxSerializationJson) implementation(Libs.kotlinxSerializationJson)
implementation(Libs.logbackClassic) implementation(Libs.logbackClassic)
implementation(Libs.ktormCore)
implementation(Libs.micronaut) implementation(Libs.micronaut)
kapt(Libs.micronautProcessor) kapt(Libs.micronautProcessor)

View File

@ -1,7 +1,6 @@
package be.simplenotes.app.api package be.simplenotes.app.api
import be.simplenotes.app.extensions.auto import be.simplenotes.app.extensions.auto
import be.simplenotes.app.utils.parseSearchTerms
import be.simplenotes.domain.usecases.NoteService import be.simplenotes.domain.usecases.NoteService
import be.simplenotes.types.LoggedInUser import be.simplenotes.types.LoggedInUser
import be.simplenotes.types.PersistedNote import be.simplenotes.types.PersistedNote
@ -58,8 +57,7 @@ class ApiNoteController(
fun search(request: Request, loggedInUser: LoggedInUser): Response { fun search(request: Request, loggedInUser: LoggedInUser): Response {
val query = searchContentLens(request) val query = searchContentLens(request)
val terms = parseSearchTerms(query) val notes = noteService.search(loggedInUser.userId, query)
val notes = noteService.search(loggedInUser.userId, terms)
return persistedNotesMetadataLens(notes, Response(OK)) return persistedNotesMetadataLens(notes, Response(OK))
} }

View File

@ -1,6 +1,6 @@
package be.simplenotes.app.controllers package be.simplenotes.app.controllers
import be.simplenotes.persistance.DbHealthCheck import be.simplenotes.domain.usecases.HealthCheckService
import org.http4k.core.Request import org.http4k.core.Request
import org.http4k.core.Response import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK import org.http4k.core.Status.Companion.OK
@ -8,7 +8,7 @@ import org.http4k.core.Status.Companion.SERVICE_UNAVAILABLE
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class HealthCheckController(private val dbHealthCheck: DbHealthCheck) { class HealthCheckController(private val healthCheckService: HealthCheckService) {
fun healthCheck(@Suppress("UNUSED_PARAMETER") request: Request) = fun healthCheck(@Suppress("UNUSED_PARAMETER") request: Request) =
if (dbHealthCheck.isOk()) Response(OK) else Response(SERVICE_UNAVAILABLE) if (healthCheckService.isOk()) Response(OK) else Response(SERVICE_UNAVAILABLE)
} }

View File

@ -2,7 +2,6 @@ package be.simplenotes.app.controllers
import be.simplenotes.app.extensions.html import be.simplenotes.app.extensions.html
import be.simplenotes.app.extensions.redirect import be.simplenotes.app.extensions.redirect
import be.simplenotes.app.utils.parseSearchTerms
import be.simplenotes.domain.usecases.NoteService import be.simplenotes.domain.usecases.NoteService
import be.simplenotes.domain.usecases.markdown.InvalidMeta import be.simplenotes.domain.usecases.markdown.InvalidMeta
import be.simplenotes.domain.usecases.markdown.MissingMeta import be.simplenotes.domain.usecases.markdown.MissingMeta
@ -69,8 +68,7 @@ class NoteController(
fun search(request: Request, loggedInUser: LoggedInUser): Response { fun search(request: Request, loggedInUser: LoggedInUser): Response {
val query = request.form("search") ?: "" val query = request.form("search") ?: ""
val terms = parseSearchTerms(query) val notes = noteService.search(loggedInUser.userId, query)
val notes = noteService.search(loggedInUser.userId, terms)
val deletedCount = noteService.countDeleted(loggedInUser.userId) val deletedCount = noteService.countDeleted(loggedInUser.userId)
return Response(OK).html(view.search(loggedInUser, notes, query, deletedCount)) return Response(OK).html(view.search(loggedInUser, notes, query, deletedCount))
} }

View File

@ -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)
}
}
}

View File

@ -2,10 +2,8 @@ package be.simplenotes.app.routes
import be.simplenotes.app.api.ApiNoteController import be.simplenotes.app.api.ApiNoteController
import be.simplenotes.app.api.ApiUserController 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.RequiredAuthFilter
import be.simplenotes.app.filters.auth.RequiredAuthLens import be.simplenotes.app.filters.auth.RequiredAuthLens
import org.http4k.core.Filter
import org.http4k.core.Method.* import org.http4k.core.Method.*
import org.http4k.core.Request import org.http4k.core.Request
import org.http4k.core.then import org.http4k.core.then
@ -21,16 +19,11 @@ import javax.inject.Singleton
class ApiRoutes( class ApiRoutes(
private val apiUserController: ApiUserController, private val apiUserController: ApiUserController,
private val apiNoteController: ApiNoteController, private val apiNoteController: ApiNoteController,
private val transaction: TransactionFilter,
@Named("api") private val auth: RequiredAuthFilter, @Named("api") private val auth: RequiredAuthFilter,
@Named("required") private val authLens: RequiredAuthLens, @Named("required") private val authLens: RequiredAuthLens,
) : Supplier<RoutingHttpHandler> { ) : Supplier<RoutingHttpHandler> {
override fun get(): RoutingHttpHandler { override fun get(): RoutingHttpHandler {
fun Filter.then(next: ProtectedHandler) = then { req: Request ->
next(req, authLens(req))
}
infix fun PathMethod.to(action: ProtectedHandler) = infix fun PathMethod.to(action: ProtectedHandler) =
this to { req: Request -> action(req, authLens(req)) } this to { req: Request -> action(req, authLens(req)) }
@ -41,10 +34,10 @@ class ApiRoutes(
auth.then( auth.then(
routes( routes(
"/" bind GET to ::notes, "/" bind GET to ::notes,
"/" bind POST to transaction.then(::createNote), "/" bind POST to ::createNote,
"/search" bind POST to ::search, "/search" bind POST to ::search,
"/{uuid}" bind GET to ::note, "/{uuid}" bind GET to ::note,
"/{uuid}" bind PUT to transaction.then(::update), "/{uuid}" bind PUT to ::update,
) )
).withBasePath("/notes") ).withBasePath("/notes")
} }

View File

@ -5,11 +5,9 @@ import be.simplenotes.app.controllers.HealthCheckController
import be.simplenotes.app.controllers.NoteController import be.simplenotes.app.controllers.NoteController
import be.simplenotes.app.controllers.UserController import be.simplenotes.app.controllers.UserController
import be.simplenotes.app.filters.ImmutableFilter 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.OptionalAuthFilter
import be.simplenotes.app.filters.auth.OptionalAuthLens import be.simplenotes.app.filters.auth.OptionalAuthLens
import org.http4k.core.ContentType import org.http4k.core.ContentType
import org.http4k.core.Filter
import org.http4k.core.Method.GET import org.http4k.core.Method.GET
import org.http4k.core.Method.POST import org.http4k.core.Method.POST
import org.http4k.core.Request import org.http4k.core.Request
@ -27,15 +25,10 @@ class BasicRoutes(
private val noteCtrl: NoteController, private val noteCtrl: NoteController,
@Named("optional") private val authLens: OptionalAuthLens, @Named("optional") private val authLens: OptionalAuthLens,
private val auth: OptionalAuthFilter, private val auth: OptionalAuthFilter,
private val transactionFilter: TransactionFilter,
) : Supplier<RoutingHttpHandler> { ) : Supplier<RoutingHttpHandler> {
override fun get(): RoutingHttpHandler { override fun get(): RoutingHttpHandler {
fun Filter.then(next: PublicHandler) = then { req: Request ->
next(req, authLens(req))
}
infix fun PathMethod.to(action: PublicHandler) = infix fun PathMethod.to(action: PublicHandler) =
this to { req: Request -> action(req, authLens(req)) } this to { req: Request -> action(req, authLens(req)) }
@ -52,7 +45,7 @@ class BasicRoutes(
routes( routes(
"/" bind GET to baseCtrl::index, "/" bind GET to baseCtrl::index,
"/register" bind GET to userCtrl::register, "/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 GET to userCtrl::login,
"/login" bind POST to userCtrl::login, "/login" bind POST to userCtrl::login,
"/logout" bind POST to userCtrl::logout, "/logout" bind POST to userCtrl::logout,

View File

@ -1,10 +1,8 @@
package be.simplenotes.app.routes package be.simplenotes.app.routes
import be.simplenotes.app.controllers.NoteController 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.RequiredAuthFilter
import be.simplenotes.app.filters.auth.RequiredAuthLens import be.simplenotes.app.filters.auth.RequiredAuthLens
import org.http4k.core.Filter
import org.http4k.core.Method.GET import org.http4k.core.Method.GET
import org.http4k.core.Method.POST import org.http4k.core.Method.POST
import org.http4k.core.Request import org.http4k.core.Request
@ -20,16 +18,11 @@ import javax.inject.Singleton
@Singleton @Singleton
class NoteRoutes( class NoteRoutes(
private val noteCtrl: NoteController, private val noteCtrl: NoteController,
private val transaction: TransactionFilter,
private val auth: RequiredAuthFilter, private val auth: RequiredAuthFilter,
@Named("required") private val authLens: RequiredAuthLens, @Named("required") private val authLens: RequiredAuthLens,
) : Supplier<RoutingHttpHandler> { ) : Supplier<RoutingHttpHandler> {
override fun get(): RoutingHttpHandler { override fun get(): RoutingHttpHandler {
fun Filter.then(next: ProtectedHandler) = then { req: Request ->
next(req, authLens(req))
}
infix fun PathMethod.to(action: ProtectedHandler) = infix fun PathMethod.to(action: ProtectedHandler) =
this to { req: Request -> action(req, authLens(req)) } this to { req: Request -> action(req, authLens(req)) }
@ -39,13 +32,13 @@ class NoteRoutes(
"/" bind GET to ::list, "/" bind GET to ::list,
"/" bind POST to ::search, "/" bind POST to ::search,
"/new" bind GET to ::new, "/new" bind GET to ::new,
"/new" bind POST to transaction.then(::new), "/new" bind POST to ::new,
"/trash" bind GET to ::trash, "/trash" bind GET to ::trash,
"/{uuid}" bind GET to ::note, "/{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 GET to ::edit,
"/{uuid}/edit" bind POST to transaction.then(::edit), "/{uuid}/edit" bind POST to ::edit,
"/deleted/{uuid}" bind POST to transaction.then(::deleted), "/deleted/{uuid}" bind POST to ::deleted,
).withBasePath("/notes") ).withBasePath("/notes")
} }
) )

View File

@ -1,10 +1,8 @@
package be.simplenotes.app.routes package be.simplenotes.app.routes
import be.simplenotes.app.controllers.SettingsController 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.RequiredAuthFilter
import be.simplenotes.app.filters.auth.RequiredAuthLens import be.simplenotes.app.filters.auth.RequiredAuthLens
import org.http4k.core.Filter
import org.http4k.core.Method.GET import org.http4k.core.Method.GET
import org.http4k.core.Method.POST import org.http4k.core.Method.POST
import org.http4k.core.Request import org.http4k.core.Request
@ -20,23 +18,18 @@ import javax.inject.Singleton
@Singleton @Singleton
class SettingsRoutes( class SettingsRoutes(
private val settingsController: SettingsController, private val settingsController: SettingsController,
private val transaction: TransactionFilter,
private val auth: RequiredAuthFilter, private val auth: RequiredAuthFilter,
@Named("required") private val authLens: RequiredAuthLens, @Named("required") private val authLens: RequiredAuthLens,
) : Supplier<RoutingHttpHandler> { ) : Supplier<RoutingHttpHandler> {
override fun get(): RoutingHttpHandler { override fun get(): RoutingHttpHandler {
fun Filter.then(next: ProtectedHandler) = then { req: Request ->
next(req, authLens(req))
}
infix fun PathMethod.to(action: ProtectedHandler) = infix fun PathMethod.to(action: ProtectedHandler) =
this to { req: Request -> action(req, authLens(req)) } this to { req: Request -> action(req, authLens(req)) }
return auth.then( return auth.then(
routes( routes(
"/settings" bind GET to settingsController::settings, "/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, "/export" bind POST to settingsController::export,
) )
) )

View File

@ -0,0 +1,13 @@
package be.simplenotes.domain.usecases
import be.simplenotes.persistance.DbHealthCheck
import javax.inject.Singleton
interface HealthCheckService {
fun isOk(): Boolean
}
@Singleton
class HealthCheckServiceImpl(private val dbHealthCheck: DbHealthCheck) : HealthCheckService {
override fun isOk() = dbHealthCheck.isOk()
}

View File

@ -4,10 +4,11 @@ import arrow.core.computations.either
import be.simplenotes.domain.security.HtmlSanitizer import be.simplenotes.domain.security.HtmlSanitizer
import be.simplenotes.domain.usecases.markdown.MarkdownConverter import be.simplenotes.domain.usecases.markdown.MarkdownConverter
import be.simplenotes.domain.usecases.markdown.MarkdownParsingError import be.simplenotes.domain.usecases.markdown.MarkdownParsingError
import be.simplenotes.domain.usecases.search.parseSearchTerms
import be.simplenotes.persistance.repositories.NoteRepository import be.simplenotes.persistance.repositories.NoteRepository
import be.simplenotes.persistance.repositories.UserRepository import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.persistance.transactions.TransactionService
import be.simplenotes.search.NoteSearcher import be.simplenotes.search.NoteSearcher
import be.simplenotes.search.SearchTerms
import be.simplenotes.types.LoggedInUser import be.simplenotes.types.LoggedInUser
import be.simplenotes.types.Note import be.simplenotes.types.Note
import be.simplenotes.types.PersistedNote import be.simplenotes.types.PersistedNote
@ -24,30 +25,35 @@ class NoteService(
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val searcher: NoteSearcher, private val searcher: NoteSearcher,
private val htmlSanitizer: HtmlSanitizer, private val htmlSanitizer: HtmlSanitizer,
private val transaction: TransactionService,
) { ) {
fun create(user: LoggedInUser, markdownText: String) = either.eager<MarkdownParsingError, PersistedNote> { fun create(user: LoggedInUser, markdownText: String) = transaction.use {
val persistedNote = !markdownConverter.renderDocument(markdownText) either.eager<MarkdownParsingError, PersistedNote> {
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) } val persistedNote = !markdownConverter.renderDocument(markdownText)
.map { Note(it.metadata, markdown = markdownText, html = it.html) } .map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
.map { noteRepository.create(user.userId, it) } .map { Note(it.metadata, markdown = markdownText, html = it.html) }
.map { noteRepository.create(user.userId, it) }
searcher.indexNote(user.userId, persistedNote) searcher.indexNote(user.userId, persistedNote)
persistedNote persistedNote
}
} }
fun update( fun update(
user: LoggedInUser, user: LoggedInUser,
uuid: UUID, uuid: UUID,
markdownText: String, markdownText: String,
) = either.eager<MarkdownParsingError, PersistedNote?> { ) = transaction.use {
val persistedNote = !markdownConverter.renderDocument(markdownText) either.eager<MarkdownParsingError, PersistedNote?> {
.map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) } val persistedNote = !markdownConverter.renderDocument(markdownText)
.map { Note(it.metadata, markdown = markdownText, html = it.html) } .map { it.copy(html = htmlSanitizer.sanitize(user, it.html)) }
.map { noteRepository.update(user.userId, uuid, it) } .map { Note(it.metadata, markdown = markdownText, html = it.html) }
.map { noteRepository.update(user.userId, uuid, it) }
persistedNote?.let { searcher.updateIndex(user.userId, it) } persistedNote?.let { searcher.updateIndex(user.userId, it) }
persistedNote persistedNote
}
} }
fun paginatedNotes( fun paginatedNotes(
@ -66,22 +72,22 @@ class NoteService(
fun find(userId: Int, uuid: UUID) = noteRepository.find(userId, uuid) 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) val res = noteRepository.delete(userId, uuid, permanent = false)
if (res) searcher.deleteIndex(userId, uuid) 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) val res = noteRepository.restore(userId, uuid)
if (res) find(userId, uuid)?.let { note -> searcher.indexNote(userId, note) } 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) val res = noteRepository.delete(userId, uuid, permanent = true)
if (res) searcher.deleteIndex(userId, uuid) if (res) searcher.deleteIndex(userId, uuid)
return res res
} }
fun countDeleted(userId: Int) = noteRepository.count(userId, deleted = true) fun countDeleted(userId: Int) = noteRepository.count(userId, deleted = true)
@ -96,13 +102,13 @@ class NoteService(
} }
} }
fun search(userId: Int, searchTerms: SearchTerms) = searcher.search(userId, searchTerms) fun search(userId: Int, searchInput: String) = searcher.search(userId, parseSearchTerms(searchInput))
@PreDestroy @PreDestroy
fun dropAllIndexes() = searcher.dropAll() fun dropAllIndexes() = searcher.dropAll()
fun makePublic(userId: Int, uuid: UUID) = noteRepository.makePublic(userId, uuid) fun makePublic(userId: Int, uuid: UUID) = transaction.use { noteRepository.makePublic(userId, uuid) }
fun makePrivate(userId: Int, uuid: UUID) = noteRepository.makePrivate(userId, uuid) fun makePrivate(userId: Int, uuid: UUID) = transaction.use { noteRepository.makePrivate(userId, uuid) }
fun findPublic(uuid: UUID) = noteRepository.findPublic(uuid) fun findPublic(uuid: UUID) = noteRepository.findPublic(uuid)
} }

View File

@ -1,4 +1,4 @@
package be.simplenotes.app.utils package be.simplenotes.domain.usecases.search
import be.simplenotes.search.SearchTerms import be.simplenotes.search.SearchTerms
@ -16,7 +16,7 @@ private val outerTagRe = outerRegex("tag")
private val contentRe = innerRegex("content") private val contentRe = innerRegex("content")
private val outerContentRe = outerRegex("content") private val outerContentRe = outerRegex("content")
fun parseSearchTerms(input: String): SearchTerms { internal fun parseSearchTerms(input: String): SearchTerms {
var c: String = input var c: String = input
fun extract(innerRegex: Regex, outerRegex: Regex): String? { fun extract(innerRegex: Regex, outerRegex: Regex): String? {

View File

@ -6,6 +6,7 @@ import arrow.core.rightIfNotNull
import be.simplenotes.domain.security.PasswordHash import be.simplenotes.domain.security.PasswordHash
import be.simplenotes.domain.validation.UserValidations import be.simplenotes.domain.validation.UserValidations
import be.simplenotes.persistance.repositories.UserRepository import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.persistance.transactions.TransactionService
import be.simplenotes.search.NoteSearcher import be.simplenotes.search.NoteSearcher
import io.micronaut.context.annotation.Primary import io.micronaut.context.annotation.Primary
import javax.inject.Singleton import javax.inject.Singleton
@ -16,16 +17,19 @@ internal class DeleteUseCaseImpl(
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val passwordHash: PasswordHash, private val passwordHash: PasswordHash,
private val searcher: NoteSearcher, private val searcher: NoteSearcher,
private val transactionService: TransactionService,
) : DeleteUseCase { ) : DeleteUseCase {
override fun delete(form: DeleteForm) = either.eager<DeleteError, Unit> { override fun delete(form: DeleteForm) = transactionService.use {
val user = !UserValidations.validateDelete(form) either.eager<DeleteError, Unit> {
val persistedUser = !userRepository.find(user.username).rightIfNotNull { DeleteError.Unregistered } val user = !UserValidations.validateDelete(form)
!Either.conditionally( val persistedUser = !userRepository.find(user.username).rightIfNotNull { DeleteError.Unregistered }
passwordHash.verify(user.password, persistedUser.password), !Either.conditionally(
{ DeleteError.WrongPassword }, passwordHash.verify(user.password, persistedUser.password),
{ Unit } { DeleteError.WrongPassword },
) { Unit }
!Either.conditionally(userRepository.delete(persistedUser.id), { DeleteError.Unregistered }, { Unit }) )
searcher.dropIndex(persistedUser.id) !Either.conditionally(userRepository.delete(persistedUser.id), { DeleteError.Unregistered }, { Unit })
searcher.dropIndex(persistedUser.id)
}
} }
} }

View File

@ -6,6 +6,7 @@ import arrow.core.leftIfNull
import be.simplenotes.domain.security.PasswordHash import be.simplenotes.domain.security.PasswordHash
import be.simplenotes.domain.validation.UserValidations import be.simplenotes.domain.validation.UserValidations
import be.simplenotes.persistance.repositories.UserRepository import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.persistance.transactions.TransactionService
import be.simplenotes.types.PersistedUser import be.simplenotes.types.PersistedUser
import io.micronaut.context.annotation.Primary import io.micronaut.context.annotation.Primary
import javax.inject.Singleton import javax.inject.Singleton
@ -14,10 +15,11 @@ import javax.inject.Singleton
@Singleton @Singleton
internal class RegisterUseCaseImpl( internal class RegisterUseCaseImpl(
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val passwordHash: PasswordHash private val passwordHash: PasswordHash,
private val transactionService: TransactionService,
) : RegisterUseCase { ) : RegisterUseCase {
override fun register(form: RegisterForm): Either<RegisterError, PersistedUser> { override fun register(form: RegisterForm): Either<RegisterError, PersistedUser> = transactionService.use {
return UserValidations.validateRegister(form) UserValidations.validateRegister(form)
.filterOrElse({ !userRepository.exists(it.username) }, { UserExists }) .filterOrElse({ !userRepository.exists(it.username) }, { UserExists })
.map { it.copy(password = passwordHash.crypt(it.password)) } .map { it.copy(password = passwordHash.crypt(it.password)) }
.map { userRepository.create(it) } .map { userRepository.create(it) }

View File

@ -1,7 +1,8 @@
package be.simplenotes.app.utils package be.simplenotes.domain.usecases.search
import be.simplenotes.search.SearchTerms import be.simplenotes.search.SearchTerms
import org.assertj.core.api.Assertions.assertThat import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.equalTo
import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream import java.util.stream.Stream
@ -13,7 +14,7 @@ internal class SearchTermsParserKtTest {
title: String? = null, title: String? = null,
tag: String? = null, tag: String? = null,
content: String? = null, content: String? = null,
all: String? = null all: String? = null,
): Pair<String, SearchTerms> = input to SearchTerms(title, tag, content, all) ): Pair<String, SearchTerms> = input to SearchTerms(title, tag, content, all)
@Suppress("Unused") @Suppress("Unused")
@ -39,6 +40,6 @@ internal class SearchTermsParserKtTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("results") @MethodSource("results")
fun `valid search parser`(case: Pair<String, SearchTerms>) { fun `valid search parser`(case: Pair<String, SearchTerms>) {
assertThat(parseSearchTerms(case.first)).isEqualTo(case.second) assertThat(parseSearchTerms(case.first), equalTo(case.second))
} }
} }

View File

@ -4,6 +4,7 @@ import be.simplenotes.domain.security.BcryptPasswordHash
import be.simplenotes.domain.testutils.isLeftOfType import be.simplenotes.domain.testutils.isLeftOfType
import be.simplenotes.domain.testutils.isRight import be.simplenotes.domain.testutils.isRight
import be.simplenotes.persistance.repositories.UserRepository import be.simplenotes.persistance.repositories.UserRepository
import be.simplenotes.persistance.transactions.TransactionService
import be.simplenotes.types.PersistedUser import be.simplenotes.types.PersistedUser
import com.natpryce.hamkrest.assertion.assertThat import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.equalTo import com.natpryce.hamkrest.equalTo
@ -16,7 +17,10 @@ internal class RegisterUseCaseImplTest {
// region setup // region setup
private val mockUserRepository = mockk<UserRepository>() private val mockUserRepository = mockk<UserRepository>()
private val passwordHash = BcryptPasswordHash(test = true) 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 @BeforeEach
fun resetMocks() { fun resetMocks() {

View File

@ -6,8 +6,7 @@ import be.simplenotes.persistance.utils.type
import me.liuwj.ktorm.database.Database import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.database.asIterable import me.liuwj.ktorm.database.asIterable
import me.liuwj.ktorm.database.use import me.liuwj.ktorm.database.use
import java.io.EOFException import java.sql.SQLException
import java.sql.SQLTransientException
import javax.inject.Singleton import javax.inject.Singleton
interface DbHealthCheck { interface DbHealthCheck {
@ -26,9 +25,7 @@ internal class DbHealthCheckImpl(
it.executeQuery().asIterable().map { it.getString(1) } it.executeQuery().asIterable().map { it.getString(1) }
} }
}.any { it in dataSourceConfig.jdbcUrl } }.any { it in dataSourceConfig.jdbcUrl }
} catch (e: SQLTransientException) { } catch (e: SQLException) {
false
} catch (e: EOFException) {
false false
} }
} }

View File

@ -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() }
}

View File

@ -0,0 +1,5 @@
package be.simplenotes.persistance.transactions
interface TransactionService {
fun <T> use(block: () -> T): T
}