Compare commits

..

No commits in common. "9467db2382c2aac4b1e2bb8ee592ef2ffbbffd54" and "ceb310bf02f52a155451be8477636c0baea25c0c" have entirely different histories.

17 changed files with 227 additions and 333 deletions

View File

@ -11,7 +11,7 @@
<artifactId>app</artifactId> <artifactId>app</artifactId>
<properties> <properties>
<http4k.version>3.268.0</http4k.version> <http4k.version>3.258.0</http4k.version>
</properties> </properties>
<dependencies> <dependencies>
@ -38,16 +38,12 @@
<dependency> <dependency>
<groupId>org.http4k</groupId> <groupId>org.http4k</groupId>
<artifactId>http4k-core</artifactId> <artifactId>http4k-core</artifactId>
<version>${http4k.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.http4k</groupId> <groupId>org.http4k</groupId>
<artifactId>http4k-server-jetty</artifactId> <artifactId>http4k-server-jetty</artifactId>
<exclusions> <version>${http4k.version}</version>
<exclusion>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>javax-websocket-server-impl</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlinx</groupId> <groupId>org.jetbrains.kotlinx</groupId>
@ -64,21 +60,6 @@
<version>4.0.5.Final</version> <version>4.0.5.Final</version>
</dependency> </dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>be.simplenotes</groupId> <groupId>be.simplenotes</groupId>
<artifactId>shared</artifactId> <artifactId>shared</artifactId>
@ -89,27 +70,11 @@
<dependency> <dependency>
<groupId>org.http4k</groupId> <groupId>org.http4k</groupId>
<artifactId>http4k-testing-hamkrest</artifactId> <artifactId>http4k-testing-hamkrest</artifactId>
<version>${http4k.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-core</artifactId>
</dependency>
</dependencies> </dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.http4k</groupId>
<artifactId>http4k-bom</artifactId>
<version>${http4k.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>

View File

@ -19,23 +19,25 @@ class AuthFilter(
private val ctx: RequestContexts, private val ctx: RequestContexts,
private val source: JwtSource = JwtSource.Cookie, private val source: JwtSource = JwtSource.Cookie,
private val redirect: Boolean = true, private val redirect: Boolean = true,
) : Filter { ) {
override fun invoke(next: HttpHandler): HttpHandler = { operator fun invoke() = Filter { next ->
val token = when (source) { {
JwtSource.Header -> it.bearerTokenHeader() val token = when (source) {
JwtSource.Cookie -> it.bearerTokenCookie() JwtSource.Header -> it.bearerTokenHeader()
} JwtSource.Cookie -> it.bearerTokenCookie()
val jwtPayload = token?.let { token -> extractor(token) }
when {
jwtPayload != null -> {
ctx[it][authKey] = jwtPayload
next(it)
} }
authType == AuthType.Required -> { val jwtPayload = token?.let { token -> extractor(token) }
if (redirect) Response.redirect("/login") when {
else Response(UNAUTHORIZED) jwtPayload != null -> {
ctx[it][authKey] = jwtPayload
next(it)
}
authType == AuthType.Required -> {
if (redirect) Response.redirect("/login")
else Response(UNAUTHORIZED)
}
else -> next(it)
} }
else -> next(it)
} }
} }
} }

View File

@ -2,44 +2,32 @@ package be.simplenotes.app.filters
import be.simplenotes.app.extensions.html import be.simplenotes.app.extensions.html
import be.simplenotes.app.views.ErrorView import be.simplenotes.app.views.ErrorView
import be.simplenotes.app.views.ErrorView.Type.*
import org.http4k.core.* import org.http4k.core.*
import org.http4k.core.Status.Companion.INTERNAL_SERVER_ERROR
import org.http4k.core.Status.Companion.NOT_FOUND
import org.http4k.core.Status.Companion.NOT_IMPLEMENTED
import org.http4k.core.Status.Companion.SERVICE_UNAVAILABLE
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.sql.SQLTransientException import java.sql.SQLTransientException
class ErrorFilter(private val errorView: ErrorView) : Filter { class ErrorFilter(private val errorView: ErrorView) {
private val logger = LoggerFactory.getLogger(javaClass) private val logger = LoggerFactory.getLogger(javaClass)
private fun errorResponse(status: Status): Response { operator fun invoke(): Filter = Filter { next ->
val type = when (status) { {
SERVICE_UNAVAILABLE -> SqlTransientError try {
NOT_FOUND -> NotFound val response = next(it)
NOT_IMPLEMENTED -> Other if (response.status == Status.NOT_FOUND) Response(Status.NOT_FOUND)
else -> Other .html(errorView.error(ErrorView.Type.NotFound))
} else response
} catch (e: Exception) {
return Response(status).html(errorView.error(type)).noCache() logger.error(e.stackTraceToString())
} if (e is SQLTransientException)
Response(Status.SERVICE_UNAVAILABLE).html(errorView.error(ErrorView.Type.SqlTransientError))
override fun invoke(next: HttpHandler): HttpHandler = { request -> .noCache()
try { else
val response = next(request) Response(Status.INTERNAL_SERVER_ERROR).html(errorView.error(ErrorView.Type.Other)).noCache()
if (response.status == NOT_FOUND) errorResponse(NOT_FOUND) } catch (e: NotImplementedError) {
else response logger.error(e.stackTraceToString())
} catch (e: SQLTransientException) { Response(Status.NOT_IMPLEMENTED).html(errorView.error(ErrorView.Type.Other)).noCache()
logger.error(e.stackTraceToString()) }
errorResponse(SERVICE_UNAVAILABLE)
} catch (e: Exception) {
logger.error(e.stackTraceToString())
errorResponse(INTERNAL_SERVER_ERROR)
} catch (e: NotImplementedError) {
logger.error(e.stackTraceToString())
errorResponse(NOT_IMPLEMENTED)
} }
} }
} }

View File

@ -2,10 +2,13 @@ package be.simplenotes.app.filters
import org.http4k.core.Filter import org.http4k.core.Filter
import org.http4k.core.HttpHandler import org.http4k.core.HttpHandler
import org.http4k.core.Method
import org.http4k.core.Request import org.http4k.core.Request
object ImmutableFilter : Filter { object ImmutableFilter {
override fun invoke(next: HttpHandler) = { request: Request -> operator fun invoke() = Filter { next: HttpHandler ->
next(request).header("Cache-Control", "public, max-age=31536000, immutable") { request: Request ->
next(request).header("Cache-Control", "public, max-age=31536000, immutable")
}
} }
} }

View File

@ -4,15 +4,17 @@ import org.http4k.core.Filter
import org.http4k.core.HttpHandler import org.http4k.core.HttpHandler
import org.http4k.core.Request import org.http4k.core.Request
object SecurityFilter : Filter { object SecurityFilter {
override fun invoke(next: HttpHandler): HttpHandler = { request: Request -> operator fun invoke() = Filter { next: HttpHandler ->
val response = next(request) { request: Request ->
.header("X-Content-Type-Options", "nosniff") val response = next(request)
.header("X-Content-Type-Options", "nosniff")
if (response.header("Content-Type")?.contains("text/html") == true) { if (response.header("Content-Type")?.contains("text/html") == true) {
response response
.header("Content-Security-Policy", "default-src 'self'") .header("Content-Security-Policy", "default-src 'self'")
.header("Referrer-Policy", "no-referrer") .header("Referrer-Policy", "no-referrer")
} else response } else response
}
} }
} }

View File

@ -1,13 +0,0 @@
package be.simplenotes.app.filters
import me.liuwj.ktorm.database.Database
import org.http4k.core.Filter
import org.http4k.core.HttpHandler
class TransactionFilter(private val db: Database) : Filter {
override fun invoke(next: HttpHandler): HttpHandler = { request ->
db.useTransaction {
next(request)
}
}
}

View File

@ -5,20 +5,19 @@ import be.simplenotes.app.api.ApiUserController
import be.simplenotes.app.filters.AuthFilter import be.simplenotes.app.filters.AuthFilter
import be.simplenotes.app.filters.AuthType import be.simplenotes.app.filters.AuthType
import be.simplenotes.app.filters.JwtSource import be.simplenotes.app.filters.JwtSource
import org.http4k.core.Filter
import org.koin.core.qualifier.named import org.koin.core.qualifier.named
import org.koin.dsl.module import org.koin.dsl.module
val apiModule = module { val apiModule = module {
single { ApiUserController(get(), get()) } single { ApiUserController(get(), get()) }
single { ApiNoteController(get(), get()) } single { ApiNoteController(get(), get()) }
single<Filter>(named("apiAuthFilter")) { single(named("apiAuthFilter")) {
AuthFilter( AuthFilter(
extractor = get(), extractor = get(),
authType = AuthType.Required, authType = AuthType.Required,
ctx = get(), ctx = get(),
source = JwtSource.Header, source = JwtSource.Header,
redirect = false redirect = false
) )()
} }
} }

View File

@ -4,14 +4,12 @@ import be.simplenotes.app.Server
import be.simplenotes.app.filters.AuthFilter import be.simplenotes.app.filters.AuthFilter
import be.simplenotes.app.filters.AuthType import be.simplenotes.app.filters.AuthType
import be.simplenotes.app.filters.ErrorFilter import be.simplenotes.app.filters.ErrorFilter
import be.simplenotes.app.filters.TransactionFilter
import be.simplenotes.app.routes.Router import be.simplenotes.app.routes.Router
import be.simplenotes.app.utils.StaticFileResolver import be.simplenotes.app.utils.StaticFileResolver
import be.simplenotes.app.utils.StaticFileResolverImpl import be.simplenotes.app.utils.StaticFileResolverImpl
import be.simplenotes.app.views.ErrorView import be.simplenotes.app.views.ErrorView
import be.simplenotes.shared.config.ServerConfig import be.simplenotes.shared.config.ServerConfig
import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.ServerConnector
import org.http4k.core.Filter
import org.http4k.core.RequestContexts import org.http4k.core.RequestContexts
import org.http4k.routing.RoutingHttpHandler import org.http4k.routing.RoutingHttpHandler
import org.http4k.server.ConnectorBuilder import org.http4k.server.ConnectorBuilder
@ -47,16 +45,14 @@ val serverModule = module {
get(), get(),
requiredAuth = get(AuthType.Required.qualifier), requiredAuth = get(AuthType.Required.qualifier),
optionalAuth = get(AuthType.Optional.qualifier), optionalAuth = get(AuthType.Optional.qualifier),
errorFilter = get(named("ErrorFilter")),
apiAuth = get(named("apiAuthFilter")), apiAuth = get(named("apiAuthFilter")),
get(), get()
get(),
get(),
)() )()
} }
single { RequestContexts() } single { RequestContexts() }
single<Filter>(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get()) } single(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get())() }
single<Filter>(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get()) } single(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get())() }
single { ErrorFilter(get()) } single(named("ErrorFilter")) { ErrorFilter(get())() }
single { TransactionFilter(get()) }
single { ErrorView(get()) } single { ErrorView(get()) }
} }

View File

@ -6,14 +6,15 @@ import be.simplenotes.app.controllers.BaseController
import be.simplenotes.app.controllers.NoteController import be.simplenotes.app.controllers.NoteController
import be.simplenotes.app.controllers.SettingsController import be.simplenotes.app.controllers.SettingsController
import be.simplenotes.app.controllers.UserController import be.simplenotes.app.controllers.UserController
import be.simplenotes.app.filters.* import be.simplenotes.app.filters.ImmutableFilter
import be.simplenotes.app.filters.SecurityFilter
import be.simplenotes.app.filters.jwtPayload
import be.simplenotes.domain.security.JwtPayload import be.simplenotes.domain.security.JwtPayload
import org.http4k.core.* import org.http4k.core.*
import org.http4k.core.Method.* import org.http4k.core.Method.*
import org.http4k.filter.ResponseFilters.GZip import org.http4k.filter.ResponseFilters
import org.http4k.filter.ServerFilters.InitialiseRequestContext import org.http4k.filter.ServerFilters.InitialiseRequestContext
import org.http4k.routing.* import org.http4k.routing.*
import org.http4k.routing.ResourceLoader.Companion.Classpath
class Router( class Router(
private val baseController: BaseController, private val baseController: BaseController,
@ -24,19 +25,24 @@ class Router(
private val apiNoteController: ApiNoteController, private val apiNoteController: ApiNoteController,
private val requiredAuth: Filter, private val requiredAuth: Filter,
private val optionalAuth: Filter, private val optionalAuth: Filter,
private val errorFilter: Filter,
private val apiAuth: Filter, private val apiAuth: Filter,
private val errorFilter: ErrorFilter,
private val transactionFilter: TransactionFilter,
private val contexts: RequestContexts, private val contexts: RequestContexts,
) { ) {
operator fun invoke(): RoutingHttpHandler { operator fun invoke(): RoutingHttpHandler {
val basicRoutes = ImmutableFilter.then(static(Classpath("/static"), "woff2" to ContentType("font/woff2"))) val resourceLoader = ResourceLoader.Classpath(("/static"))
val basicRoutes = routes(
ImmutableFilter().then(static(resourceLoader, "woff2" to ContentType("font/woff2"))),
)
val publicRoutes = routes( infix fun PathMethod.public(handler: PublicHandler) = this to { handler(it, it.jwtPayload(contexts)) }
infix fun PathMethod.protected(handler: ProtectedHandler) = this to { handler(it, it.jwtPayload(contexts)!!) }
val publicRoutes: RoutingHttpHandler = routes(
"/" bind GET public baseController::index, "/" bind GET public baseController::index,
"/register" bind GET public userController::register, "/register" bind GET public userController::register,
"/register" bind POST `public transactional` userController::register, "/register" bind POST public userController::register,
"/login" bind GET public userController::login, "/login" bind GET public userController::login,
"/login" bind POST public userController::login, "/login" bind POST public userController::login,
"/logout" bind POST to userController::logout, "/logout" bind POST to userController::logout,
@ -45,18 +51,18 @@ class Router(
val protectedRoutes = routes( val protectedRoutes = routes(
"/settings" bind GET protected settingsController::settings, "/settings" bind GET protected settingsController::settings,
"/settings" bind POST transactional settingsController::settings, "/settings" bind POST protected settingsController::settings,
"/export" bind POST protected settingsController::export, "/export" bind POST protected settingsController::export,
"/notes" bind GET protected noteController::list, "/notes" bind GET protected noteController::list,
"/notes" bind POST protected noteController::search, "/notes" bind POST protected noteController::search,
"/notes/new" bind GET protected noteController::new, "/notes/new" bind GET protected noteController::new,
"/notes/new" bind POST transactional noteController::new, "/notes/new" bind POST protected noteController::new,
"/notes/trash" bind GET protected noteController::trash, "/notes/trash" bind GET protected noteController::trash,
"/notes/{uuid}" bind GET protected noteController::note, "/notes/{uuid}" bind GET protected noteController::note,
"/notes/{uuid}" bind POST transactional noteController::note, "/notes/{uuid}" bind POST protected noteController::note,
"/notes/{uuid}/edit" bind GET protected noteController::edit, "/notes/{uuid}/edit" bind GET protected noteController::edit,
"/notes/{uuid}/edit" bind POST transactional noteController::edit, "/notes/{uuid}/edit" bind POST protected noteController::edit,
"/notes/deleted/{uuid}" bind POST transactional noteController::deleted, "/notes/deleted/{uuid}" bind POST protected noteController::deleted,
) )
val apiRoutes = routes( val apiRoutes = routes(
@ -65,10 +71,10 @@ class Router(
val protectedApiRoutes = routes( val protectedApiRoutes = routes(
"/api/notes" bind GET protected apiNoteController::notes, "/api/notes" bind GET protected apiNoteController::notes,
"/api/notes" bind POST transactional apiNoteController::createNote, "/api/notes" bind POST protected apiNoteController::createNote,
"/api/notes/search" bind POST transactional apiNoteController::search, "/api/notes/search" bind POST protected apiNoteController::search,
"/api/notes/{uuid}" bind GET protected apiNoteController::note, "/api/notes/{uuid}" bind GET protected apiNoteController::note,
"/api/notes/{uuid}" bind PUT transactional apiNoteController::update, "/api/notes/{uuid}" bind PUT protected apiNoteController::update,
) )
val routes = routes( val routes = routes(
@ -81,23 +87,11 @@ class Router(
val globalFilters = errorFilter val globalFilters = errorFilter
.then(InitialiseRequestContext(contexts)) .then(InitialiseRequestContext(contexts))
.then(SecurityFilter) .then(SecurityFilter())
.then(GZip()) .then(ResponseFilters.GZip())
return globalFilters.then(routes) return globalFilters.then(routes)
} }
private inline infix fun PathMethod.public(crossinline handler: PublicHandler) =
this to { handler(it, it.jwtPayload(contexts)) }
private inline infix fun PathMethod.protected(crossinline handler: ProtectedHandler) =
this to { handler(it, it.jwtPayload(contexts)!!) }
private inline infix fun PathMethod.transactional(crossinline handler: ProtectedHandler) =
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)!!) }
private inline infix fun PathMethod.`public transactional`(crossinline handler: PublicHandler) =
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)) }
} }
private typealias PublicHandler = (Request, JwtPayload?) -> Response private typealias PublicHandler = (Request, JwtPayload?) -> Response

View File

@ -27,8 +27,8 @@ internal class AuthFilterTest {
private val simpleJwt = SimpleJwt(jwtConfig) private val simpleJwt = SimpleJwt(jwtConfig)
private val extractor = JwtPayloadExtractor(simpleJwt) private val extractor = JwtPayloadExtractor(simpleJwt)
private val ctx = RequestContexts() private val ctx = RequestContexts()
private val requiredAuth = AuthFilter(extractor, AuthType.Required, ctx) private val requiredAuth = AuthFilter(extractor, AuthType.Required, ctx)()
private val optionalAuth = AuthFilter(extractor, AuthType.Optional, ctx) private val optionalAuth = AuthFilter(extractor, AuthType.Optional, ctx)()
private val echoJwtPayloadHandler = { request: Request -> Response(OK).body(request.jwtPayload(ctx).toString()) } private val echoJwtPayloadHandler = { request: Request -> Response(OK).body(request.jwtPayload(ctx).toString()) }

View File

@ -24,30 +24,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-core</artifactId>
</dependency>
<dependency>
<groupId>org.koin</groupId>
<artifactId>koin-core</artifactId>
</dependency>
<dependency>
<groupId>com.natpryce</groupId>
<artifactId>hamkrest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>io.konform</groupId> <groupId>io.konform</groupId>
<artifactId>konform-jvm</artifactId> <artifactId>konform-jvm</artifactId>

View File

@ -29,17 +29,6 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.mariadb.jdbc</groupId> <groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId> <artifactId>mariadb-java-client</artifactId>
@ -63,10 +52,12 @@
<dependency> <dependency>
<groupId>me.liuwj.ktorm</groupId> <groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-core</artifactId> <artifactId>ktorm-core</artifactId>
<version>3.0.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>me.liuwj.ktorm</groupId> <groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-support-mysql</artifactId> <artifactId>ktorm-support-mysql</artifactId>
<version>3.0.0</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -1,9 +1,6 @@
package be.simplenotes.persistance.notes package be.simplenotes.persistance.notes
import be.simplenotes.domain.model.ExportedNote import be.simplenotes.domain.model.*
import be.simplenotes.domain.model.Note
import be.simplenotes.domain.model.PersistedNote
import be.simplenotes.domain.model.PersistedNoteMetadata
import be.simplenotes.domain.usecases.repositories.NoteRepository import be.simplenotes.domain.usecases.repositories.NoteRepository
import me.liuwj.ktorm.database.Database import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.dsl.*
@ -62,12 +59,14 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
val entity = note.toEntity(uuid, userId).apply { val entity = note.toEntity(uuid, userId).apply {
this.updatedAt = LocalDateTime.now() this.updatedAt = LocalDateTime.now()
} }
db.notes.add(entity) db.useTransaction {
db.batchInsert(Tags) { db.notes.add(entity)
note.meta.tags.forEach { tagName -> db.batchInsert(Tags) {
item { note.meta.tags.forEach { tagName ->
it.noteUuid to uuid item {
it.name to tagName it.noteUuid to uuid
it.name to tagName
}
} }
} }
} }
@ -91,56 +90,64 @@ internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
} }
override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? { override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? {
val now = LocalDateTime.now() db.useTransaction {
val count = db.update(Notes) {
it.title to note.meta.title
it.markdown to note.markdown
it.html to note.html
it.updatedAt to now
where { (it.uuid eq uuid) and (it.userId eq userId) and (it.deleted eq false) }
}
if (count == 0) return null val now = LocalDateTime.now()
val count = db.update(Notes) {
// delete all tags it.title to note.meta.title
db.delete(Tags) { it.markdown to note.markdown
it.noteUuid eq uuid it.html to note.html
} it.updatedAt to now
where { (it.uuid eq uuid) and (it.userId eq userId) and (it.deleted eq false) }
// put new ones
note.meta.tags.forEach { tagName ->
db.insert(Tags) {
it.name to tagName
it.noteUuid to uuid
} }
}
return PersistedNote( if (count == 0) return null
meta = note.meta,
markdown = note.markdown, // delete all tags
html = note.html, db.delete(Tags) {
updatedAt = now, it.noteUuid eq uuid
uuid = uuid, }
public = false, // TODO
) // put new ones
note.meta.tags.forEach { tagName ->
db.insert(Tags) {
it.name to tagName
it.noteUuid to uuid
}
}
return PersistedNote(
meta = note.meta,
markdown = note.markdown,
html = note.html,
updatedAt = now,
uuid = uuid,
public = false, // TODO
)
}
} }
override fun delete(userId: Int, uuid: UUID, permanent: Boolean): Boolean { override fun delete(userId: Int, uuid: UUID, permanent: Boolean): Boolean {
return if (!permanent) { return if (!permanent) {
db.update(Notes) { db.useTransaction {
it.deleted to true db.update(Notes) {
it.updatedAt to LocalDateTime.now() it.deleted to true
where { it.userId eq userId and (it.uuid eq uuid) } it.updatedAt to LocalDateTime.now()
where { it.userId eq userId and (it.uuid eq uuid) }
}
} == 1 } == 1
} else } else db.useTransaction {
db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1 db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1
}
} }
override fun restore(userId: Int, uuid: UUID): Boolean { override fun restore(userId: Int, uuid: UUID): Boolean {
return db.update(Notes) { return db.useTransaction {
it.deleted to false db.update(Notes) {
where { (it.userId eq userId) and (it.uuid eq uuid) } it.deleted to false
} == 1 where { (it.userId eq userId) and (it.uuid eq uuid) }
} == 1
}
} }
override fun getTags(userId: Int): List<String> = override fun getTags(userId: Int): List<String> =

View File

@ -3,20 +3,21 @@ package be.simplenotes.persistance.users
import be.simplenotes.domain.model.PersistedUser import be.simplenotes.domain.model.PersistedUser
import be.simplenotes.domain.model.User import be.simplenotes.domain.model.User
import be.simplenotes.domain.usecases.repositories.UserRepository import be.simplenotes.domain.usecases.repositories.UserRepository
import me.liuwj.ktorm.database.Database import me.liuwj.ktorm.database.*
import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.any import me.liuwj.ktorm.entity.*
import me.liuwj.ktorm.entity.find
import java.sql.SQLIntegrityConstraintViolationException import java.sql.SQLIntegrityConstraintViolationException
internal class UserRepositoryImpl(private val db: Database) : UserRepository { internal class UserRepositoryImpl(private val db: Database) : UserRepository {
override fun create(user: User): PersistedUser? { override fun create(user: User): PersistedUser? {
return try { return try {
val id = db.insertAndGenerateKey(Users) { db.useTransaction {
it.username to user.username val id = db.insertAndGenerateKey(Users) {
it.password to user.password it.username to user.username
} as Int it.password to user.password
PersistedUser(user.username, user.password, id) } as Int
PersistedUser(user.username, user.password, id)
}
} catch (e: SQLIntegrityConstraintViolationException) { } catch (e: SQLIntegrityConstraintViolationException) {
null null
} }
@ -26,6 +27,6 @@ internal class UserRepositoryImpl(private val db: Database) : UserRepository {
override fun find(id: Int) = db.users.find { it.id eq id }?.toPersistedUser() override fun find(id: Int) = db.users.find { it.id eq id }?.toPersistedUser()
override fun exists(username: String) = db.users.any { it.username eq username } override fun exists(username: String) = db.users.any { it.username eq username }
override fun exists(id: Int) = db.users.any { it.id eq id } override fun exists(id: Int) = db.users.any { it.id eq id }
override fun delete(id: Int) = db.delete(Users) { it.id eq id } == 1 override fun delete(id: Int) = db.useTransaction { db.delete(Users) { it.id eq id } == 1 }
override fun findAll() = db.from(Users).select(Users.id).map { it[Users.id]!! } override fun findAll() = db.from(Users).select(Users.id).map { it[Users.id]!! }
} }

128
pom.xml
View File

@ -37,6 +37,55 @@
<artifactId>kotlin-stdlib-jdk8</artifactId> <artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version> <version>${kotlin.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.koin</groupId>
<artifactId>koin-core</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-core</artifactId>
<version>0.10.5</version>
</dependency>
<!-- region tests -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>1.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.koin</groupId>
<artifactId>koin-test</artifactId>
<version>2.1.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.natpryce</groupId>
<artifactId>hamkrest</artifactId>
<version>1.7.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.16.1</version>
<scope>test</scope>
</dependency>
<!-- endregion -->
</dependencies> </dependencies>
<build> <build>
@ -138,71 +187,34 @@
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.jetbrains.kotlin</groupId> <groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-bom</artifactId> <artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk7</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-common</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version> <version>${kotlin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jetbrains.kotlinx</groupId> <groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json-jvm</artifactId> <artifactId>kotlinx-serialization-json-jvm</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.koin</groupId>
<artifactId>koin-core</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-core</artifactId>
<version>0.10.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-support-mysql</artifactId>
<version>3.0.0</version>
</dependency>
<!-- region tests -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>com.natpryce</groupId>
<artifactId>hamkrest</artifactId>
<version>1.7.0.3</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.16.1</version>
</dependency>
<!-- endregion -->
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@ -37,22 +37,6 @@
<version>${lucene.version}</version> <version>${lucene.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>be.simplenotes</groupId> <groupId>be.simplenotes</groupId>
<artifactId>shared</artifactId> <artifactId>shared</artifactId>

View File

@ -10,19 +10,6 @@
<artifactId>shared</artifactId> <artifactId>shared</artifactId>
<dependencies>
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.natpryce</groupId>
<artifactId>hamkrest</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>