Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 32337ec308 |
@@ -125,8 +125,8 @@ data/
|
|||||||
letsencrypt/
|
letsencrypt/
|
||||||
|
|
||||||
# generated resources
|
# generated resources
|
||||||
simplenotes-app/src/main/resources/css-manifest.json
|
app/src/main/resources/css-manifest.json
|
||||||
simplenotes-app/src/main/resources/static/styles*
|
app/src/main/resources/static/styles*
|
||||||
|
|
||||||
# h2 db
|
# h2 db
|
||||||
*.db
|
*.db
|
||||||
|
|||||||
@@ -4,23 +4,19 @@ WORKDIR /tmp
|
|||||||
|
|
||||||
# Cache dependencies
|
# Cache dependencies
|
||||||
COPY pom.xml .
|
COPY pom.xml .
|
||||||
COPY simplenotes-test-resources/pom.xml simplenotes-test-resources/pom.xml
|
COPY app/pom.xml app/pom.xml
|
||||||
COPY simplenotes-types/pom.xml simplenotes-types/pom.xml
|
COPY domain/pom.xml domain/pom.xml
|
||||||
COPY simplenotes-config/pom.xml simplenotes-config/pom.xml
|
COPY persistance/pom.xml persistance/pom.xml
|
||||||
COPY simplenotes-persistance/pom.xml simplenotes-persistance/pom.xml
|
COPY shared/pom.xml shared/pom.xml
|
||||||
COPY simplenotes-search/pom.xml simplenotes-search/pom.xml
|
COPY search/pom.xml search/pom.xml
|
||||||
COPY simplenotes-domain/pom.xml simplenotes-domain/pom.xml
|
|
||||||
COPY simplenotes-app/pom.xml simplenotes-app/pom.xml
|
|
||||||
|
|
||||||
RUN mvn verify clean --fail-never
|
RUN mvn verify clean --fail-never
|
||||||
|
|
||||||
COPY simplenotes-test-resources/src simplenotes-test-resources/src
|
COPY app/src app/src
|
||||||
COPY simplenotes-types/src simplenotes-types/src
|
COPY domain/src domain/src
|
||||||
COPY simplenotes-config/src simplenotes-config/src
|
COPY persistance/src persistance/src
|
||||||
COPY simplenotes-persistance/src simplenotes-persistance/src
|
COPY shared/src shared/src
|
||||||
COPY simplenotes-search/src simplenotes-search/src
|
COPY search/src search/src
|
||||||
COPY simplenotes-domain/src simplenotes-domain/src
|
|
||||||
COPY simplenotes-app/src simplenotes-app/src
|
|
||||||
|
|
||||||
RUN mvn -Dstyle.color=always package
|
RUN mvn -Dstyle.color=always package
|
||||||
|
|
||||||
@@ -46,8 +42,8 @@ RUN chown -R $APPLICATION_USER /app
|
|||||||
|
|
||||||
USER $APPLICATION_USER
|
USER $APPLICATION_USER
|
||||||
|
|
||||||
COPY --from=builder /tmp/simplenotes-app/target/simplenotes-app-*.jar /app/simplenotes.jar
|
COPY --from=builder /tmp/app/target/app-*.jar /app/app.jar
|
||||||
COPY --from=jdkbuilder /myjdk /myjdk
|
COPY --from=jdkbuilder /myjdk /myjdk
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
CMD ["/myjdk/bin/java", "-server", "-XX:+UnlockExperimentalVMOptions", "-Xms64m", "-Xmx256m", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "simplenotes.jar"]
|
CMD ["/myjdk/bin/java", "-server", "-XX:+UnlockExperimentalVMOptions", "-Xms64m", "-Xmx256m", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "app.jar"]
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>simplenotes-parent</artifactId>
|
<artifactId>parent</artifactId>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>simplenotes-app</artifactId>
|
<artifactId>app</artifactId>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<http4k.version>3.268.0</http4k.version>
|
<http4k.version>3.268.0</http4k.version>
|
||||||
@@ -17,58 +17,52 @@
|
|||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>simplenotes-persistance</artifactId>
|
<artifactId>persistance</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>simplenotes-search</artifactId>
|
<artifactId>search</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>simplenotes-domain</artifactId>
|
<artifactId>domain</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>simplenotes-config</artifactId>
|
<artifactId>shared</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.http4k</groupId>
|
<groupId>org.http4k</groupId>
|
||||||
<artifactId>http4k-core</artifactId>
|
<artifactId>http4k-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!--
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.http4k</groupId>
|
<groupId>org.http4k</groupId>
|
||||||
<artifactId>http4k-server-jetty</artifactId>
|
<artifactId>http4k-server-jetty</artifactId>
|
||||||
</dependency>
|
<exclusions>
|
||||||
-->
|
<exclusion>
|
||||||
<dependency>
|
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<artifactId>javax-websocket-server-impl</artifactId>
|
||||||
<artifactId>jetty-server</artifactId>
|
</exclusion>
|
||||||
<version>9.4.32.v20200930</version>
|
</exclusions>
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.jetbrains.kotlinx</groupId>
|
||||||
<artifactId>jetty-servlet</artifactId>
|
<artifactId>kotlinx-html-jvm</artifactId>
|
||||||
<version>9.4.32.v20200930</version>
|
<version>0.7.1</version>
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>javax.servlet</groupId>
|
|
||||||
<artifactId>javax.servlet-api</artifactId>
|
|
||||||
<version>4.0.1</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</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>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ocpsoft.prettytime</groupId>
|
||||||
|
<artifactId>prettytime</artifactId>
|
||||||
|
<version>4.0.5.Final</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
@@ -87,7 +81,7 @@
|
|||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>simplenotes-test-resources</artifactId>
|
<artifactId>shared</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
<type>test-jar</type>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
@@ -102,12 +96,6 @@
|
|||||||
<groupId>me.liuwj.ktorm</groupId>
|
<groupId>me.liuwj.ktorm</groupId>
|
||||||
<artifactId>ktorm-core</artifactId>
|
<artifactId>ktorm-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>be.simplenotes</groupId>
|
|
||||||
<artifactId>simplenotes-views</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package be.simplenotes.config
|
package be.simplenotes.app
|
||||||
|
|
||||||
|
import be.simplenotes.shared.config.DataSourceConfig
|
||||||
|
import be.simplenotes.shared.config.JwtConfig
|
||||||
|
import be.simplenotes.shared.config.ServerConfig
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class ConfigLoader {
|
class Config {
|
||||||
//region Config loading
|
//region Config loading
|
||||||
private val properties: Properties = javaClass
|
private val properties: Properties = javaClass
|
||||||
.getResource("/application.properties")
|
.getResource("/application.properties")
|
||||||
@@ -2,7 +2,7 @@ package be.simplenotes.app
|
|||||||
|
|
||||||
import org.http4k.server.Http4kServer
|
import org.http4k.server.Http4kServer
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig
|
import be.simplenotes.shared.config.ServerConfig as SimpleNotesServerConfig
|
||||||
|
|
||||||
class Server(
|
class Server(
|
||||||
private val config: SimpleNotesServerConfig,
|
private val config: SimpleNotesServerConfig,
|
||||||
@@ -2,12 +2,10 @@ package be.simplenotes.app
|
|||||||
|
|
||||||
import be.simplenotes.app.extensions.addShutdownHook
|
import be.simplenotes.app.extensions.addShutdownHook
|
||||||
import be.simplenotes.app.modules.*
|
import be.simplenotes.app.modules.*
|
||||||
import be.simplenotes.config.configModule
|
|
||||||
import be.simplenotes.domain.domainModule
|
import be.simplenotes.domain.domainModule
|
||||||
import be.simplenotes.persistance.migrationModule
|
import be.simplenotes.persistance.migrationModule
|
||||||
import be.simplenotes.persistance.persistanceModule
|
import be.simplenotes.persistance.persistanceModule
|
||||||
import be.simplenotes.search.searchModule
|
import be.simplenotes.search.searchModule
|
||||||
import be.simplenotes.views.viewModule
|
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
import org.koin.core.context.unloadKoinModules
|
import org.koin.core.context.unloadKoinModules
|
||||||
|
|
||||||
@@ -18,8 +16,10 @@ fun main() {
|
|||||||
persistanceModule,
|
persistanceModule,
|
||||||
migrationModule,
|
migrationModule,
|
||||||
configModule,
|
configModule,
|
||||||
viewModule,
|
baseModule,
|
||||||
controllerModule,
|
userModule,
|
||||||
|
noteModule,
|
||||||
|
settingsModule,
|
||||||
domainModule,
|
domainModule,
|
||||||
searchModule,
|
searchModule,
|
||||||
apiModule,
|
apiModule,
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package be.simplenotes.app.api
|
||||||
|
|
||||||
|
import be.simplenotes.app.extensions.json
|
||||||
|
import be.simplenotes.app.utils.parseSearchTerms
|
||||||
|
import be.simplenotes.domain.model.PersistedNote
|
||||||
|
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
|
import be.simplenotes.domain.usecases.NoteService
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.http4k.core.Request
|
||||||
|
import org.http4k.core.Response
|
||||||
|
import org.http4k.core.Status.Companion.BAD_REQUEST
|
||||||
|
import org.http4k.core.Status.Companion.NOT_FOUND
|
||||||
|
import org.http4k.core.Status.Companion.OK
|
||||||
|
import org.http4k.routing.path
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ApiNoteController(private val noteService: NoteService, private val json: Json) {
|
||||||
|
|
||||||
|
fun createNote(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
|
val content = json.decodeFromString(NoteContent.serializer(), request.bodyString()).content
|
||||||
|
return noteService.create(jwtPayload.userId, content).fold(
|
||||||
|
{
|
||||||
|
Response(BAD_REQUEST)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Response(OK).json(json.encodeToString(UuidContent.serializer(), UuidContent(it.uuid)))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notes(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
|
val notes = noteService.paginatedNotes(jwtPayload.userId, page = 1).notes
|
||||||
|
val json = json.encodeToString(ListSerializer(PersistedNoteMetadata.serializer()), notes)
|
||||||
|
return Response(OK).json(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun note(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
|
val uuid = request.path("uuid")!!
|
||||||
|
|
||||||
|
return noteService.find(jwtPayload.userId, UUID.fromString(uuid))
|
||||||
|
?.let { Response(OK).json(json.encodeToString(PersistedNote.serializer(), it)) }
|
||||||
|
?: Response(NOT_FOUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
|
val uuid = UUID.fromString(request.path("uuid")!!)
|
||||||
|
val content = json.decodeFromString(NoteContent.serializer(), request.bodyString()).content
|
||||||
|
return noteService.update(jwtPayload.userId, uuid, content).fold({
|
||||||
|
Response(BAD_REQUEST)
|
||||||
|
}, {
|
||||||
|
if (it == null) Response(NOT_FOUND)
|
||||||
|
else Response(OK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun search(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
|
val query = json.decodeFromString(SearchContent.serializer(), request.bodyString()).query
|
||||||
|
val terms = parseSearchTerms(query)
|
||||||
|
val notes = noteService.search(jwtPayload.userId, terms)
|
||||||
|
val json = json.encodeToString(ListSerializer(PersistedNoteMetadata.serializer()), notes)
|
||||||
|
return Response(OK).json(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class NoteContent(val content: String)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UuidContent(@Contextual val uuid: UUID)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchContent(@Contextual val query: String)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package be.simplenotes.app.api
|
||||||
|
|
||||||
|
import be.simplenotes.app.extensions.json
|
||||||
|
import be.simplenotes.domain.usecases.UserService
|
||||||
|
import be.simplenotes.domain.usecases.users.login.LoginForm
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.http4k.core.Request
|
||||||
|
import org.http4k.core.Response
|
||||||
|
import org.http4k.core.Status
|
||||||
|
|
||||||
|
class ApiUserController(private val userService: UserService, private val json: Json) {
|
||||||
|
|
||||||
|
fun login(request: Request): Response {
|
||||||
|
val form = json.decodeFromString(LoginForm.serializer(), request.bodyString())
|
||||||
|
val result = userService.login(form)
|
||||||
|
return result.fold({
|
||||||
|
Response(Status.BAD_REQUEST)
|
||||||
|
}, {
|
||||||
|
Response(Status.OK).json(json.encodeToString(Token.serializer(), Token(it)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Token(val token: String)
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package be.simplenotes.app.controllers
|
package be.simplenotes.app.controllers
|
||||||
|
|
||||||
import be.simplenotes.app.extensions.html
|
import be.simplenotes.app.extensions.html
|
||||||
import be.simplenotes.types.LoggedInUser
|
import be.simplenotes.app.views.BaseView
|
||||||
import be.simplenotes.views.BaseView
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
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
|
||||||
|
|
||||||
class BaseController(private val view: BaseView) {
|
class BaseController(private val view: BaseView) {
|
||||||
fun index(@Suppress("UNUSED_PARAMETER") request: Request, loggedInUser: LoggedInUser?) =
|
fun index(@Suppress("UNUSED_PARAMETER") request: Request, jwtPayload: JwtPayload?) =
|
||||||
Response(OK).html(view.renderHome(loggedInUser))
|
Response(OK).html(view.renderHome(jwtPayload))
|
||||||
}
|
}
|
||||||
@@ -3,12 +3,12 @@ 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.app.utils.parseSearchTerms
|
||||||
import be.simplenotes.views.NoteView
|
import be.simplenotes.app.views.NoteView
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
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
|
||||||
import be.simplenotes.domain.usecases.markdown.ValidationError
|
import be.simplenotes.domain.usecases.markdown.ValidationError
|
||||||
import be.simplenotes.types.LoggedInUser
|
|
||||||
import org.http4k.core.Method
|
import org.http4k.core.Method
|
||||||
import org.http4k.core.Request
|
import org.http4k.core.Request
|
||||||
import org.http4k.core.Response
|
import org.http4k.core.Response
|
||||||
@@ -25,18 +25,18 @@ class NoteController(
|
|||||||
private val noteService: NoteService,
|
private val noteService: NoteService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun new(request: Request, loggedInUser: LoggedInUser): Response {
|
fun new(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
if (request.method == Method.GET) return Response(OK).html(view.noteEditor(loggedInUser))
|
if (request.method == Method.GET) return Response(OK).html(view.noteEditor(jwtPayload))
|
||||||
|
|
||||||
val markdownForm = request.form("markdown") ?: ""
|
val markdownForm = request.form("markdown") ?: ""
|
||||||
|
|
||||||
return noteService.create(loggedInUser.userId, markdownForm).fold(
|
return noteService.create(jwtPayload.userId, markdownForm).fold(
|
||||||
{
|
{
|
||||||
val html = when (it) {
|
val html = when (it) {
|
||||||
MissingMeta -> view.noteEditor(loggedInUser, error = "Missing note metadata", textarea = markdownForm)
|
MissingMeta -> view.noteEditor(jwtPayload, error = "Missing note metadata", textarea = markdownForm)
|
||||||
InvalidMeta -> view.noteEditor(loggedInUser, error = "Invalid note metadata", textarea = markdownForm)
|
InvalidMeta -> view.noteEditor(jwtPayload, error = "Invalid note metadata", textarea = markdownForm)
|
||||||
is ValidationError -> view.noteEditor(
|
is ValidationError -> view.noteEditor(
|
||||||
loggedInUser,
|
jwtPayload,
|
||||||
validationErrors = it.validationErrors,
|
validationErrors = it.validationErrors,
|
||||||
textarea = markdownForm
|
textarea = markdownForm
|
||||||
)
|
)
|
||||||
@@ -49,66 +49,66 @@ class NoteController(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun list(request: Request, loggedInUser: LoggedInUser): Response {
|
fun list(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val currentPage = request.query("page")?.toIntOrNull()?.let(::abs) ?: 1
|
val currentPage = request.query("page")?.toIntOrNull()?.let(::abs) ?: 1
|
||||||
val tag = request.query("tag")
|
val tag = request.query("tag")
|
||||||
val (pages, notes) = noteService.paginatedNotes(loggedInUser.userId, currentPage, tag = tag)
|
val (pages, notes) = noteService.paginatedNotes(jwtPayload.userId, currentPage, tag = tag)
|
||||||
val deletedCount = noteService.countDeleted(loggedInUser.userId)
|
val deletedCount = noteService.countDeleted(jwtPayload.userId)
|
||||||
return Response(OK).html(view.notes(loggedInUser, notes, currentPage, pages, deletedCount, tag = tag))
|
return Response(OK).html(view.notes(jwtPayload, notes, currentPage, pages, deletedCount, tag = tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(request: Request, loggedInUser: LoggedInUser): Response {
|
fun search(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val query = request.form("search") ?: ""
|
val query = request.form("search") ?: ""
|
||||||
val terms = parseSearchTerms(query)
|
val terms = parseSearchTerms(query)
|
||||||
val notes = noteService.search(loggedInUser.userId, terms)
|
val notes = noteService.search(jwtPayload.userId, terms)
|
||||||
val deletedCount = noteService.countDeleted(loggedInUser.userId)
|
val deletedCount = noteService.countDeleted(jwtPayload.userId)
|
||||||
return Response(OK).html(view.search(loggedInUser, notes, query, deletedCount))
|
return Response(OK).html(view.search(jwtPayload, notes, query, deletedCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun note(request: Request, loggedInUser: LoggedInUser): Response {
|
fun note(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
||||||
|
|
||||||
if (request.method == Method.POST) {
|
if (request.method == Method.POST) {
|
||||||
if (request.form("delete") != null) {
|
if (request.form("delete") != null) {
|
||||||
return if (noteService.trash(loggedInUser.userId, noteUuid))
|
return if (noteService.trash(jwtPayload.userId, noteUuid))
|
||||||
Response.redirect("/notes") // TODO: flash cookie to show success ?
|
Response.redirect("/notes") // TODO: flash cookie to show success ?
|
||||||
else
|
else
|
||||||
Response(NOT_FOUND) // TODO: show an error
|
Response(NOT_FOUND) // TODO: show an error
|
||||||
}
|
}
|
||||||
if (request.form("public") != null) {
|
if (request.form("public") != null) {
|
||||||
if (!noteService.makePublic(loggedInUser.userId, noteUuid)) return Response(NOT_FOUND)
|
if (!noteService.makePublic(jwtPayload.userId, noteUuid)) return Response(NOT_FOUND)
|
||||||
} else if (request.form("private") != null) {
|
} else if (request.form("private") != null) {
|
||||||
if (!noteService.makePrivate(loggedInUser.userId, noteUuid)) return Response(NOT_FOUND)
|
if (!noteService.makePrivate(jwtPayload.userId, noteUuid)) return Response(NOT_FOUND)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val note = noteService.find(loggedInUser.userId, noteUuid) ?: return Response(NOT_FOUND)
|
val note = noteService.find(jwtPayload.userId, noteUuid) ?: return Response(NOT_FOUND)
|
||||||
return Response(OK).html(view.renderedNote(loggedInUser, note, shared = false))
|
return Response(OK).html(view.renderedNote(jwtPayload, note, shared = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun public(request: Request, loggedInUser: LoggedInUser?): Response {
|
fun public(request: Request, jwtPayload: JwtPayload?): Response {
|
||||||
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
||||||
val note = noteService.findPublic(noteUuid) ?: return Response(NOT_FOUND)
|
val note = noteService.findPublic(noteUuid) ?: return Response(NOT_FOUND)
|
||||||
return Response(OK).html(view.renderedNote(loggedInUser, note, shared = true))
|
return Response(OK).html(view.renderedNote(jwtPayload, note, shared = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun edit(request: Request, loggedInUser: LoggedInUser): Response {
|
fun edit(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
||||||
val note = noteService.find(loggedInUser.userId, noteUuid) ?: return Response(NOT_FOUND)
|
val note = noteService.find(jwtPayload.userId, noteUuid) ?: return Response(NOT_FOUND)
|
||||||
|
|
||||||
if (request.method == Method.GET) {
|
if (request.method == Method.GET) {
|
||||||
return Response(OK).html(view.noteEditor(loggedInUser, textarea = note.markdown))
|
return Response(OK).html(view.noteEditor(jwtPayload, textarea = note.markdown))
|
||||||
}
|
}
|
||||||
|
|
||||||
val markdownForm = request.form("markdown") ?: ""
|
val markdownForm = request.form("markdown") ?: ""
|
||||||
|
|
||||||
return noteService.update(loggedInUser.userId, note.uuid, markdownForm).fold(
|
return noteService.update(jwtPayload.userId, note.uuid, markdownForm).fold(
|
||||||
{
|
{
|
||||||
val html = when (it) {
|
val html = when (it) {
|
||||||
MissingMeta -> view.noteEditor(loggedInUser, error = "Missing note metadata", textarea = markdownForm)
|
MissingMeta -> view.noteEditor(jwtPayload, error = "Missing note metadata", textarea = markdownForm)
|
||||||
InvalidMeta -> view.noteEditor(loggedInUser, error = "Invalid note metadata", textarea = markdownForm)
|
InvalidMeta -> view.noteEditor(jwtPayload, error = "Invalid note metadata", textarea = markdownForm)
|
||||||
is ValidationError -> view.noteEditor(
|
is ValidationError -> view.noteEditor(
|
||||||
loggedInUser,
|
jwtPayload,
|
||||||
validationErrors = it.validationErrors,
|
validationErrors = it.validationErrors,
|
||||||
textarea = markdownForm
|
textarea = markdownForm
|
||||||
)
|
)
|
||||||
@@ -121,21 +121,21 @@ class NoteController(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun trash(request: Request, loggedInUser: LoggedInUser): Response {
|
fun trash(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val currentPage = request.query("page")?.toIntOrNull()?.let(::abs) ?: 1
|
val currentPage = request.query("page")?.toIntOrNull()?.let(::abs) ?: 1
|
||||||
val tag = request.query("tag")
|
val tag = request.query("tag")
|
||||||
val (pages, notes) = noteService.paginatedNotes(loggedInUser.userId, currentPage, tag = tag, deleted = true)
|
val (pages, notes) = noteService.paginatedNotes(jwtPayload.userId, currentPage, tag = tag, deleted = true)
|
||||||
return Response(OK).html(view.trash(loggedInUser, notes, currentPage, pages))
|
return Response(OK).html(view.trash(jwtPayload, notes, currentPage, pages))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleted(request: Request, loggedInUser: LoggedInUser): Response {
|
fun deleted(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val uuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
val uuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
||||||
return if (request.form("delete") != null)
|
return if (request.form("delete") != null)
|
||||||
if (noteService.delete(loggedInUser.userId, uuid))
|
if (noteService.delete(jwtPayload.userId, uuid))
|
||||||
Response.redirect("/notes/trash")
|
Response.redirect("/notes/trash")
|
||||||
else
|
else
|
||||||
Response(NOT_FOUND)
|
Response(NOT_FOUND)
|
||||||
else if (noteService.restore(loggedInUser.userId, uuid))
|
else if (noteService.restore(jwtPayload.userId, uuid))
|
||||||
Response.redirect("/notes/$uuid")
|
Response.redirect("/notes/$uuid")
|
||||||
else
|
else
|
||||||
Response(NOT_FOUND)
|
Response(NOT_FOUND)
|
||||||
@@ -2,11 +2,11 @@ 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.views.SettingView
|
import be.simplenotes.app.views.SettingView
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import be.simplenotes.domain.usecases.UserService
|
import be.simplenotes.domain.usecases.UserService
|
||||||
import be.simplenotes.domain.usecases.users.delete.DeleteError
|
import be.simplenotes.domain.usecases.users.delete.DeleteError
|
||||||
import be.simplenotes.domain.usecases.users.delete.DeleteForm
|
import be.simplenotes.domain.usecases.users.delete.DeleteForm
|
||||||
import be.simplenotes.types.LoggedInUser
|
|
||||||
import org.http4k.core.*
|
import org.http4k.core.*
|
||||||
import org.http4k.core.body.form
|
import org.http4k.core.body.form
|
||||||
import org.http4k.core.cookie.invalidateCookie
|
import org.http4k.core.cookie.invalidateCookie
|
||||||
@@ -15,11 +15,11 @@ class SettingsController(
|
|||||||
private val userService: UserService,
|
private val userService: UserService,
|
||||||
private val settingView: SettingView,
|
private val settingView: SettingView,
|
||||||
) {
|
) {
|
||||||
fun settings(request: Request, loggedInUser: LoggedInUser): Response {
|
fun settings(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
if (request.method == Method.GET)
|
if (request.method == Method.GET)
|
||||||
return Response(Status.OK).html(settingView.settings(loggedInUser))
|
return Response(Status.OK).html(settingView.settings(jwtPayload))
|
||||||
|
|
||||||
val deleteForm = request.deleteForm(loggedInUser)
|
val deleteForm = request.deleteForm(jwtPayload)
|
||||||
val result = userService.delete(deleteForm)
|
val result = userService.delete(deleteForm)
|
||||||
|
|
||||||
return result.fold(
|
return result.fold(
|
||||||
@@ -28,13 +28,13 @@ class SettingsController(
|
|||||||
DeleteError.Unregistered -> Response.redirect("/").invalidateCookie("Bearer")
|
DeleteError.Unregistered -> Response.redirect("/").invalidateCookie("Bearer")
|
||||||
DeleteError.WrongPassword -> Response(Status.OK).html(
|
DeleteError.WrongPassword -> Response(Status.OK).html(
|
||||||
settingView.settings(
|
settingView.settings(
|
||||||
loggedInUser,
|
jwtPayload,
|
||||||
error = "Wrong password"
|
error = "Wrong password"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
is DeleteError.InvalidForm -> Response(Status.OK).html(
|
is DeleteError.InvalidForm -> Response(Status.OK).html(
|
||||||
settingView.settings(
|
settingView.settings(
|
||||||
loggedInUser,
|
jwtPayload,
|
||||||
validationErrors = it.validationErrors
|
validationErrors = it.validationErrors
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -53,23 +53,23 @@ class SettingsController(
|
|||||||
.header("Content-Type", contentType)
|
.header("Content-Type", contentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun export(request: Request, loggedInUser: LoggedInUser): Response {
|
fun export(request: Request, jwtPayload: JwtPayload): Response {
|
||||||
val isDownload = request.form("download") != null
|
val isDownload = request.form("download") != null
|
||||||
|
|
||||||
return if (isDownload) {
|
return if (isDownload) {
|
||||||
val filename = "simplenotes-export-${loggedInUser.username}"
|
val filename = "simplenotes-export-${jwtPayload.username}"
|
||||||
if (request.form("format") == "zip") {
|
if (request.form("format") == "zip") {
|
||||||
val zip = userService.exportAsZip(loggedInUser.userId)
|
val zip = userService.exportAsZip(jwtPayload.userId)
|
||||||
Response(Status.OK)
|
Response(Status.OK)
|
||||||
.with(attachment("$filename.zip", "application/zip"))
|
.with(attachment("$filename.zip", "application/zip"))
|
||||||
.body(zip)
|
.body(zip)
|
||||||
} else
|
} else
|
||||||
Response(Status.OK)
|
Response(Status.OK)
|
||||||
.with(attachment("$filename.json", "application/json"))
|
.with(attachment("$filename.json", "application/json"))
|
||||||
.body(userService.exportAsJson(loggedInUser.userId))
|
.body(userService.exportAsJson(jwtPayload.userId))
|
||||||
} else Response(Status.OK).body(userService.exportAsJson(loggedInUser.userId)).header("Content-Type", "application/json")
|
} else Response(Status.OK).body(userService.exportAsJson(jwtPayload.userId)).header("Content-Type", "application/json")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Request.deleteForm(loggedInUser: LoggedInUser) =
|
private fun Request.deleteForm(jwtPayload: JwtPayload) =
|
||||||
DeleteForm(loggedInUser.username, form("password"), form("checked") != null)
|
DeleteForm(jwtPayload.username, form("password"), form("checked") != null)
|
||||||
}
|
}
|
||||||
@@ -3,14 +3,14 @@ package be.simplenotes.app.controllers
|
|||||||
import be.simplenotes.app.extensions.html
|
import be.simplenotes.app.extensions.html
|
||||||
import be.simplenotes.app.extensions.isSecure
|
import be.simplenotes.app.extensions.isSecure
|
||||||
import be.simplenotes.app.extensions.redirect
|
import be.simplenotes.app.extensions.redirect
|
||||||
import be.simplenotes.views.UserView
|
import be.simplenotes.app.views.UserView
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import be.simplenotes.domain.usecases.UserService
|
import be.simplenotes.domain.usecases.UserService
|
||||||
import be.simplenotes.domain.usecases.users.login.*
|
import be.simplenotes.domain.usecases.users.login.*
|
||||||
import be.simplenotes.domain.usecases.users.register.InvalidRegisterForm
|
import be.simplenotes.domain.usecases.users.register.InvalidRegisterForm
|
||||||
import be.simplenotes.domain.usecases.users.register.RegisterForm
|
import be.simplenotes.domain.usecases.users.register.RegisterForm
|
||||||
import be.simplenotes.domain.usecases.users.register.UserExists
|
import be.simplenotes.domain.usecases.users.register.UserExists
|
||||||
import be.simplenotes.config.JwtConfig
|
import be.simplenotes.shared.config.JwtConfig
|
||||||
import be.simplenotes.types.LoggedInUser
|
|
||||||
import org.http4k.core.Method.GET
|
import org.http4k.core.Method.GET
|
||||||
import org.http4k.core.Request
|
import org.http4k.core.Request
|
||||||
import org.http4k.core.Response
|
import org.http4k.core.Response
|
||||||
@@ -27,9 +27,9 @@ class UserController(
|
|||||||
private val userView: UserView,
|
private val userView: UserView,
|
||||||
private val jwtConfig: JwtConfig,
|
private val jwtConfig: JwtConfig,
|
||||||
) {
|
) {
|
||||||
fun register(request: Request, loggedInUser: LoggedInUser?): Response {
|
fun register(request: Request, jwtPayload: JwtPayload?): Response {
|
||||||
if (request.method == GET) return Response(OK).html(
|
if (request.method == GET) return Response(OK).html(
|
||||||
userView.register(loggedInUser)
|
userView.register(jwtPayload)
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = userService.register(request.registerForm())
|
val result = userService.register(request.registerForm())
|
||||||
@@ -38,12 +38,12 @@ class UserController(
|
|||||||
{
|
{
|
||||||
val html = when (it) {
|
val html = when (it) {
|
||||||
UserExists -> userView.register(
|
UserExists -> userView.register(
|
||||||
loggedInUser,
|
jwtPayload,
|
||||||
error = "User already exists"
|
error = "User already exists"
|
||||||
)
|
)
|
||||||
is InvalidRegisterForm ->
|
is InvalidRegisterForm ->
|
||||||
userView.register(
|
userView.register(
|
||||||
loggedInUser,
|
jwtPayload,
|
||||||
validationErrors = it.validationErrors
|
validationErrors = it.validationErrors
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -58,9 +58,9 @@ class UserController(
|
|||||||
private fun Request.registerForm() = RegisterForm(form("username"), form("password"))
|
private fun Request.registerForm() = RegisterForm(form("username"), form("password"))
|
||||||
private fun Request.loginForm(): LoginForm = registerForm()
|
private fun Request.loginForm(): LoginForm = registerForm()
|
||||||
|
|
||||||
fun login(request: Request, loggedInUser: LoggedInUser?): Response {
|
fun login(request: Request, jwtPayload: JwtPayload?): Response {
|
||||||
if (request.method == GET) return Response(OK).html(
|
if (request.method == GET) return Response(OK).html(
|
||||||
userView.login(loggedInUser)
|
userView.login(jwtPayload)
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = userService.login(request.loginForm())
|
val result = userService.login(request.loginForm())
|
||||||
@@ -70,17 +70,17 @@ class UserController(
|
|||||||
val html = when (it) {
|
val html = when (it) {
|
||||||
Unregistered ->
|
Unregistered ->
|
||||||
userView.login(
|
userView.login(
|
||||||
loggedInUser,
|
jwtPayload,
|
||||||
error = "User does not exist"
|
error = "User does not exist"
|
||||||
)
|
)
|
||||||
WrongPassword ->
|
WrongPassword ->
|
||||||
userView.login(
|
userView.login(
|
||||||
loggedInUser,
|
jwtPayload,
|
||||||
error = "Wrong password"
|
error = "Wrong password"
|
||||||
)
|
)
|
||||||
is InvalidLoginForm ->
|
is InvalidLoginForm ->
|
||||||
userView.login(
|
userView.login(
|
||||||
loggedInUser,
|
jwtPayload,
|
||||||
validationErrors = it.validationErrors
|
validationErrors = it.validationErrors
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package be.simplenotes.app.extensions
|
||||||
|
|
||||||
|
import org.http4k.core.Request
|
||||||
|
import org.http4k.core.Response
|
||||||
|
import org.http4k.core.Status.Companion.FOUND
|
||||||
|
import org.http4k.core.Status.Companion.MOVED_PERMANENTLY
|
||||||
|
|
||||||
|
fun Response.html(html: String) = body(html)
|
||||||
|
.header("Content-Type", "text/html; charset=utf-8")
|
||||||
|
.header("Cache-Control", "no-cache")
|
||||||
|
|
||||||
|
fun Response.json(json: String) = body(json).header("Content-Type", "application/json")
|
||||||
|
|
||||||
|
fun Response.Companion.redirect(url: String, permanent: Boolean = false) =
|
||||||
|
Response(if (permanent) MOVED_PERMANENTLY else FOUND).header("Location", url)
|
||||||
|
|
||||||
|
fun Request.isSecure() = header("X-Forwarded-Proto")?.contains("https") ?: false
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package be.simplenotes.views.extensions
|
package be.simplenotes.app.extensions
|
||||||
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
|
||||||
internal class SUMMARY(consumer: TagConsumer<*>) :
|
class SUMMARY(consumer: TagConsumer<*>) :
|
||||||
HTMLTag(
|
HTMLTag(
|
||||||
"summary", consumer, emptyMap(),
|
"summary", consumer, emptyMap(),
|
||||||
inlineTag = true,
|
inlineTag = true,
|
||||||
@@ -10,6 +10,6 @@ internal class SUMMARY(consumer: TagConsumer<*>) :
|
|||||||
),
|
),
|
||||||
HtmlInlineTag
|
HtmlInlineTag
|
||||||
|
|
||||||
internal fun DETAILS.summary(block: SUMMARY.() -> Unit = {}) {
|
fun DETAILS.summary(block: SUMMARY.() -> Unit = {}) {
|
||||||
SUMMARY(consumer).visit(block)
|
SUMMARY(consumer).visit(block)
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package be.simplenotes.app.filters
|
package be.simplenotes.app.filters
|
||||||
|
|
||||||
import be.simplenotes.app.extensions.redirect
|
import be.simplenotes.app.extensions.redirect
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import be.simplenotes.domain.security.JwtPayloadExtractor
|
import be.simplenotes.domain.security.JwtPayloadExtractor
|
||||||
import be.simplenotes.types.LoggedInUser
|
|
||||||
import org.http4k.core.*
|
import org.http4k.core.*
|
||||||
import org.http4k.core.Status.Companion.UNAUTHORIZED
|
import org.http4k.core.Status.Companion.UNAUTHORIZED
|
||||||
import org.http4k.core.cookie.cookie
|
import org.http4k.core.cookie.cookie
|
||||||
@@ -40,7 +40,7 @@ class AuthFilter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Request.jwtPayload(ctx: RequestContexts): LoggedInUser? = ctx[this][authKey]
|
fun Request.jwtPayload(ctx: RequestContexts): JwtPayload? = ctx[this][authKey]
|
||||||
|
|
||||||
enum class JwtSource {
|
enum class JwtSource {
|
||||||
Header, Cookie
|
Header, Cookie
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package be.simplenotes.app.filters
|
package be.simplenotes.app.filters
|
||||||
|
|
||||||
import be.simplenotes.app.extensions.html
|
import be.simplenotes.app.extensions.html
|
||||||
import be.simplenotes.views.ErrorView
|
import be.simplenotes.app.views.ErrorView
|
||||||
import be.simplenotes.views.ErrorView.Type.*
|
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.INTERNAL_SERVER_ERROR
|
||||||
import org.http4k.core.Status.Companion.NOT_FOUND
|
import org.http4k.core.Status.Companion.NOT_FOUND
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package be.simplenotes.app.modules
|
||||||
|
|
||||||
|
import be.simplenotes.app.Config
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val configModule = module {
|
||||||
|
single { Config() }
|
||||||
|
single { get<Config>().dataSourceConfig }
|
||||||
|
single { get<Config>().jwtConfig }
|
||||||
|
single { get<Config>().serverConfig }
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package be.simplenotes.app.modules
|
||||||
|
|
||||||
|
import be.simplenotes.app.controllers.*
|
||||||
|
import be.simplenotes.app.views.BaseView
|
||||||
|
import be.simplenotes.app.views.NoteView
|
||||||
|
import be.simplenotes.app.views.SettingView
|
||||||
|
import be.simplenotes.app.views.UserView
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val userModule = module {
|
||||||
|
single { UserController(get(), get(), get()) }
|
||||||
|
single { UserView(get()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val baseModule = module {
|
||||||
|
single { HealthCheckController(get()) }
|
||||||
|
single { BaseController(get()) }
|
||||||
|
single { BaseView(get()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val noteModule = module {
|
||||||
|
single { NoteController(get(), get()) }
|
||||||
|
single { NoteView(get()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val settingsModule = module {
|
||||||
|
single { SettingsController(get(), get()) }
|
||||||
|
single { SettingView(get()) }
|
||||||
|
}
|
||||||
@@ -5,17 +5,17 @@ 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.filters.TransactionFilter
|
||||||
import be.simplenotes.app.jetty.ConnectorBuilder
|
|
||||||
import be.simplenotes.app.jetty.Jetty
|
|
||||||
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.views.ErrorView
|
import be.simplenotes.app.views.ErrorView
|
||||||
import be.simplenotes.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.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.Jetty
|
||||||
import org.http4k.server.asServer
|
import org.http4k.server.asServer
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.core.qualifier.qualifier
|
import org.koin.core.qualifier.qualifier
|
||||||
@@ -59,5 +59,5 @@ val serverModule = module {
|
|||||||
single<Filter>(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get()) }
|
single<Filter>(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get()) }
|
||||||
single { ErrorFilter(get()) }
|
single { ErrorFilter(get()) }
|
||||||
single { TransactionFilter(get()) }
|
single { TransactionFilter(get()) }
|
||||||
single(named("styles")) { get<StaticFileResolver>().resolve("styles.css") }
|
single { ErrorView(get()) }
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ import be.simplenotes.app.api.ApiNoteController
|
|||||||
import be.simplenotes.app.api.ApiUserController
|
import be.simplenotes.app.api.ApiUserController
|
||||||
import be.simplenotes.app.controllers.*
|
import be.simplenotes.app.controllers.*
|
||||||
import be.simplenotes.app.filters.*
|
import be.simplenotes.app.filters.*
|
||||||
import be.simplenotes.types.LoggedInUser
|
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.GZip
|
||||||
@@ -102,5 +102,5 @@ class Router(
|
|||||||
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)) }
|
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private typealias PublicHandler = (Request, LoggedInUser?) -> Response
|
private typealias PublicHandler = (Request, JwtPayload?) -> Response
|
||||||
private typealias ProtectedHandler = (Request, LoggedInUser) -> Response
|
private typealias ProtectedHandler = (Request, JwtPayload) -> Response
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package be.simplenotes.app.utils
|
||||||
|
|
||||||
|
import org.ocpsoft.prettytime.PrettyTime
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
private val prettyTime = PrettyTime()
|
||||||
|
|
||||||
|
fun LocalDateTime.toTimeAgo(): String = prettyTime.format(Date.from(atZone(ZoneId.systemDefault()).toInstant()))
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package be.simplenotes.app.utils
|
package be.simplenotes.app.utils
|
||||||
|
|
||||||
import be.simplenotes.search.SearchTerms
|
import be.simplenotes.domain.usecases.search.SearchTerms
|
||||||
|
|
||||||
private fun innerRegex(name: String) =
|
private fun innerRegex(name: String) =
|
||||||
"""$name:['"](.*?)['"]""".toRegex()
|
"""$name:['"](.*?)['"]""".toRegex()
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
package be.simplenotes.views
|
package be.simplenotes.app.views
|
||||||
|
|
||||||
import be.simplenotes.types.LoggedInUser
|
import be.simplenotes.app.utils.StaticFileResolver
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.html.ThScope.col
|
import kotlinx.html.ThScope.col
|
||||||
|
|
||||||
class BaseView(styles: String) : View(styles) {
|
class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
|
||||||
fun renderHome(loggedInUser: LoggedInUser?) = renderPage(
|
fun renderHome(jwtPayload: JwtPayload?) = renderPage(
|
||||||
title = "Home",
|
title = "Home",
|
||||||
description = "A fast and simple note taking website",
|
description = "A fast and simple note taking website",
|
||||||
loggedInUser = loggedInUser
|
jwtPayload = jwtPayload
|
||||||
) {
|
) {
|
||||||
section("text-center my-2 p-2") {
|
section("text-center my-2 p-2") {
|
||||||
h1("text-5xl casual") {
|
h1("text-5xl casual") {
|
||||||
span("text-teal-300") { +"SimpleNotes " }
|
span("text-teal-300") { +"Simplenotes " }
|
||||||
+"- access your notes anywhere"
|
+"- access your notes anywhere"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
package be.simplenotes.views
|
package be.simplenotes.app.views
|
||||||
|
|
||||||
import be.simplenotes.views.components.Alert
|
import be.simplenotes.app.utils.StaticFileResolver
|
||||||
import be.simplenotes.views.components.alert
|
import be.simplenotes.app.views.components.Alert
|
||||||
|
import be.simplenotes.app.views.components.alert
|
||||||
import kotlinx.html.a
|
import kotlinx.html.a
|
||||||
import kotlinx.html.div
|
import kotlinx.html.div
|
||||||
|
|
||||||
class ErrorView(styles: String) : View(styles) {
|
class ErrorView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
|
||||||
|
|
||||||
enum class Type(val title: String) {
|
enum class Type(val title: String) {
|
||||||
SqlTransientError("Database unavailable"),
|
SqlTransientError("Database unavailable"),
|
||||||
@@ -13,7 +14,7 @@ class ErrorView(styles: String) : View(styles) {
|
|||||||
Other("Error"),
|
Other("Error"),
|
||||||
}
|
}
|
||||||
|
|
||||||
fun error(errorType: Type) = renderPage(errorType.title, loggedInUser = null) {
|
fun error(errorType: Type) = renderPage(errorType.title, jwtPayload = null) {
|
||||||
div("container mx-auto p-4") {
|
div("container mx-auto p-4") {
|
||||||
when (errorType) {
|
when (errorType) {
|
||||||
Type.SqlTransientError -> alert(
|
Type.SqlTransientError -> alert(
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
package be.simplenotes.views
|
package be.simplenotes.app.views
|
||||||
|
|
||||||
import be.simplenotes.types.LoggedInUser
|
import be.simplenotes.app.utils.StaticFileResolver
|
||||||
import be.simplenotes.views.components.noteListHeader
|
import be.simplenotes.app.views.components.*
|
||||||
import be.simplenotes.types.PersistedNote
|
import be.simplenotes.domain.model.PersistedNote
|
||||||
import be.simplenotes.types.PersistedNoteMetadata
|
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||||
import be.simplenotes.views.components.*
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import io.konform.validation.ValidationError
|
import io.konform.validation.ValidationError
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
|
||||||
class NoteView(styles: String) : View(styles) {
|
class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
|
||||||
|
|
||||||
fun noteEditor(
|
fun noteEditor(
|
||||||
loggedInUser: LoggedInUser,
|
jwtPayload: JwtPayload,
|
||||||
error: String? = null,
|
error: String? = null,
|
||||||
textarea: String? = null,
|
textarea: String? = null,
|
||||||
validationErrors: List<ValidationError> = emptyList(),
|
validationErrors: List<ValidationError> = emptyList(),
|
||||||
) = renderPage(title = "New note", loggedInUser = loggedInUser) {
|
) = renderPage(title = "New note", jwtPayload = jwtPayload) {
|
||||||
div("container mx-auto p-4") {
|
div("container mx-auto p-4") {
|
||||||
error?.let { alert(Alert.Warning, error) }
|
error?.let { alert(Alert.Warning, error) }
|
||||||
validationErrors.forEach {
|
validationErrors.forEach {
|
||||||
@@ -46,13 +46,13 @@ class NoteView(styles: String) : View(styles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun notes(
|
fun notes(
|
||||||
loggedInUser: LoggedInUser,
|
jwtPayload: JwtPayload,
|
||||||
notes: List<PersistedNoteMetadata>,
|
notes: List<PersistedNoteMetadata>,
|
||||||
currentPage: Int,
|
currentPage: Int,
|
||||||
numberOfPages: Int,
|
numberOfPages: Int,
|
||||||
numberOfDeletedNotes: Int,
|
numberOfDeletedNotes: Int,
|
||||||
tag: String?,
|
tag: String?,
|
||||||
) = renderPage(title = "Notes", loggedInUser = loggedInUser) {
|
) = renderPage(title = "Notes", jwtPayload = jwtPayload) {
|
||||||
div("container mx-auto p-4") {
|
div("container mx-auto p-4") {
|
||||||
noteListHeader(numberOfDeletedNotes)
|
noteListHeader(numberOfDeletedNotes)
|
||||||
if (notes.isNotEmpty())
|
if (notes.isNotEmpty())
|
||||||
@@ -68,11 +68,11 @@ class NoteView(styles: String) : View(styles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun search(
|
fun search(
|
||||||
loggedInUser: LoggedInUser,
|
jwtPayload: JwtPayload,
|
||||||
notes: List<PersistedNoteMetadata>,
|
notes: List<PersistedNoteMetadata>,
|
||||||
query: String,
|
query: String,
|
||||||
numberOfDeletedNotes: Int,
|
numberOfDeletedNotes: Int,
|
||||||
) = renderPage("Notes", loggedInUser = loggedInUser) {
|
) = renderPage("Notes", jwtPayload = jwtPayload) {
|
||||||
div("container mx-auto p-4") {
|
div("container mx-auto p-4") {
|
||||||
noteListHeader(numberOfDeletedNotes, query)
|
noteListHeader(numberOfDeletedNotes, query)
|
||||||
noteTable(notes)
|
noteTable(notes)
|
||||||
@@ -80,11 +80,11 @@ class NoteView(styles: String) : View(styles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun trash(
|
fun trash(
|
||||||
loggedInUser: LoggedInUser,
|
jwtPayload: JwtPayload,
|
||||||
notes: List<PersistedNoteMetadata>,
|
notes: List<PersistedNoteMetadata>,
|
||||||
currentPage: Int,
|
currentPage: Int,
|
||||||
numberOfPages: Int,
|
numberOfPages: Int,
|
||||||
) = renderPage(title = "Notes", loggedInUser = loggedInUser) {
|
) = renderPage(title = "Notes", jwtPayload = jwtPayload) {
|
||||||
div("container mx-auto p-4") {
|
div("container mx-auto p-4") {
|
||||||
div("flex justify-between mb-4") {
|
div("flex justify-between mb-4") {
|
||||||
h1("text-2xl underline") { +"Deleted notes" }
|
h1("text-2xl underline") { +"Deleted notes" }
|
||||||
@@ -116,9 +116,9 @@ class NoteView(styles: String) : View(styles) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun renderedNote(loggedInUser: LoggedInUser?, note: PersistedNote, shared: Boolean) = renderPage(
|
fun renderedNote(jwtPayload: JwtPayload?, note: PersistedNote, shared: Boolean) = renderPage(
|
||||||
note.meta.title,
|
note.meta.title,
|
||||||
loggedInUser = loggedInUser,
|
jwtPayload = jwtPayload,
|
||||||
scripts = listOf("/highlight.10.1.2.js", "/init-highlight.0.0.1.js")
|
scripts = listOf("/highlight.10.1.2.js", "/init-highlight.0.0.1.js")
|
||||||
) {
|
) {
|
||||||
div("container mx-auto p-4") {
|
div("container mx-auto p-4") {
|
||||||
@@ -143,6 +143,7 @@ class NoteView(styles: String) : View(styles) {
|
|||||||
}
|
}
|
||||||
if (!shared) {
|
if (!shared) {
|
||||||
noteActionForm(note)
|
noteActionForm(note)
|
||||||
|
publicPrivateForm(note)
|
||||||
|
|
||||||
if (note.public) {
|
if (note.public) {
|
||||||
p("my-4") {
|
p("my-4") {
|
||||||
@@ -165,29 +166,12 @@ class NoteView(styles: String) : View(styles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun DIV.noteActionForm(note: PersistedNote) {
|
private fun DIV.noteActionForm(note: PersistedNote) {
|
||||||
form(method = FormMethod.post, classes = "inline flex space-x-2 justify-end mb-4") {
|
span("flex space-x-2 justify-end mb-4") {
|
||||||
a(
|
a(
|
||||||
href = "/notes/${note.uuid}/edit",
|
href = "/notes/${note.uuid}/edit",
|
||||||
classes = "btn btn-green"
|
classes = "btn btn-teal"
|
||||||
) { +"Edit" }
|
) { +"Edit" }
|
||||||
span {
|
form(method = FormMethod.post, classes = "inline") {
|
||||||
button(
|
|
||||||
type = ButtonType.submit,
|
|
||||||
name = if (note.public) "private" else "public",
|
|
||||||
classes = "font-semibold border-b-4 ${if (note.public) "border-teal-200" else "border-green-500"}" +
|
|
||||||
" p-2 rounded-l bg-teal-200 text-gray-800"
|
|
||||||
) {
|
|
||||||
+"Private"
|
|
||||||
}
|
|
||||||
button(
|
|
||||||
type = ButtonType.submit,
|
|
||||||
name = if (note.public) "private" else "public",
|
|
||||||
classes = "font-semibold border-b-4 ${if (!note.public) "border-teal-200" else "border-green-500"}" +
|
|
||||||
" p-2 rounded-r bg-teal-200 text-gray-800"
|
|
||||||
) {
|
|
||||||
+"Public"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button(
|
button(
|
||||||
type = ButtonType.submit,
|
type = ButtonType.submit,
|
||||||
name = "delete",
|
name = "delete",
|
||||||
@@ -195,4 +179,23 @@ class NoteView(styles: String) : View(styles) {
|
|||||||
) { +"Delete" }
|
) { +"Delete" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DIV.publicPrivateForm(note: PersistedNote) {
|
||||||
|
span("flex space-x-2 justify-end mb-4") {
|
||||||
|
|
||||||
|
form(method = FormMethod.post, classes = "ml-auto ") {
|
||||||
|
button(
|
||||||
|
type = ButtonType.submit,
|
||||||
|
name = if (note.public) "private" else "public",
|
||||||
|
classes = "btn btn-teal"
|
||||||
|
) {
|
||||||
|
if (note.public)
|
||||||
|
+"This note is public, do you want to make it private ?"
|
||||||
|
else
|
||||||
|
+"This note is private, do you want to make it public ?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,47 +1,47 @@
|
|||||||
package be.simplenotes.views
|
package be.simplenotes.app.views
|
||||||
|
|
||||||
import be.simplenotes.types.LoggedInUser
|
import be.simplenotes.app.extensions.summary
|
||||||
import be.simplenotes.views.components.Alert
|
import be.simplenotes.app.utils.StaticFileResolver
|
||||||
import be.simplenotes.views.components.alert
|
import be.simplenotes.app.views.components.Alert
|
||||||
import be.simplenotes.views.components.input
|
import be.simplenotes.app.views.components.alert
|
||||||
import be.simplenotes.views.extensions.summary
|
import be.simplenotes.app.views.components.input
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import io.konform.validation.ValidationError
|
import io.konform.validation.ValidationError
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.html.ButtonType.submit
|
import kotlinx.html.ButtonType.submit
|
||||||
|
|
||||||
class SettingView(styles: String) : View(styles) {
|
class SettingView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
|
||||||
|
|
||||||
fun settings(
|
fun settings(
|
||||||
loggedInUser: LoggedInUser,
|
jwtPayload: JwtPayload,
|
||||||
error: String? = null,
|
error: String? = null,
|
||||||
validationErrors: List<ValidationError> = emptyList(),
|
validationErrors: List<ValidationError> = emptyList(),
|
||||||
) = renderPage("Settings", loggedInUser = loggedInUser) {
|
) = renderPage("Settings", jwtPayload = jwtPayload) {
|
||||||
div("container mx-auto") {
|
div("container mx-auto") {
|
||||||
|
|
||||||
section("m-4 p-4 bg-gray-800 rounded") {
|
section("m-4 p-4 bg-gray-800 rounded") {
|
||||||
h1("text-xl") {
|
h1("text-xl") {
|
||||||
+"Welcome "
|
+"Welcome "
|
||||||
span("text-teal-200 font-semibold") { +loggedInUser.username }
|
span("text-teal-200 font-semibold") { +jwtPayload.username }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section("m-4 p-2 bg-gray-800 rounded flex flex-wrap justify-around items-end") {
|
section("m-4 p-4 bg-gray-800 rounded flex justify-around") {
|
||||||
|
|
||||||
form(classes = "m-2", method = FormMethod.post, action = "/export") {
|
form(method = FormMethod.post, action = "/export") {
|
||||||
button(name = "display",
|
button(name = "display",
|
||||||
classes = "inline btn btn-teal block",
|
classes = "inline btn btn-teal block",
|
||||||
type = submit) { +"Display my data" }
|
type = submit) { +"Display my data" }
|
||||||
}
|
}
|
||||||
|
|
||||||
form(classes = "m-2", method = FormMethod.post, action = "/export") {
|
form(method = FormMethod.post, action = "/export") {
|
||||||
|
|
||||||
div {
|
|
||||||
listOf("json", "zip").forEach { format ->
|
listOf("json", "zip").forEach { format ->
|
||||||
|
div {
|
||||||
radioInput(name = "format") {
|
radioInput(name = "format") {
|
||||||
id = format
|
id = format
|
||||||
attributes["value"] = format
|
attributes["value"] = format
|
||||||
if (format == "json") attributes["checked"] = ""
|
if(format == "json") attributes["checked"] = ""
|
||||||
else attributes["class"] = "ml-4"
|
|
||||||
}
|
}
|
||||||
label(classes = "ml-2") {
|
label(classes = "ml-2") {
|
||||||
attributes["for"] = format
|
attributes["for"] = format
|
||||||
@@ -1,22 +1,23 @@
|
|||||||
package be.simplenotes.views
|
package be.simplenotes.app.views
|
||||||
|
|
||||||
import be.simplenotes.types.LoggedInUser
|
import be.simplenotes.app.utils.StaticFileResolver
|
||||||
import be.simplenotes.views.components.Alert
|
import be.simplenotes.app.views.components.Alert
|
||||||
import be.simplenotes.views.components.alert
|
import be.simplenotes.app.views.components.alert
|
||||||
import be.simplenotes.views.components.input
|
import be.simplenotes.app.views.components.input
|
||||||
import be.simplenotes.views.components.submitButton
|
import be.simplenotes.app.views.components.submitButton
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import io.konform.validation.ValidationError
|
import io.konform.validation.ValidationError
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
|
||||||
class UserView(styles: String) : View(styles) {
|
class UserView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
|
||||||
fun register(
|
fun register(
|
||||||
loggedInUser: LoggedInUser?,
|
jwtPayload: JwtPayload?,
|
||||||
error: String? = null,
|
error: String? = null,
|
||||||
validationErrors: List<ValidationError> = emptyList(),
|
validationErrors: List<ValidationError> = emptyList(),
|
||||||
) = accountForm(
|
) = accountForm(
|
||||||
"Register",
|
"Register",
|
||||||
"Registration page",
|
"Registration page",
|
||||||
loggedInUser,
|
jwtPayload,
|
||||||
error,
|
error,
|
||||||
validationErrors,
|
validationErrors,
|
||||||
"Create an account",
|
"Create an account",
|
||||||
@@ -27,11 +28,11 @@ class UserView(styles: String) : View(styles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun login(
|
fun login(
|
||||||
loggedInUser: LoggedInUser?,
|
jwtPayload: JwtPayload?,
|
||||||
error: String? = null,
|
error: String? = null,
|
||||||
validationErrors: List<ValidationError> = emptyList(),
|
validationErrors: List<ValidationError> = emptyList(),
|
||||||
new: Boolean = false,
|
new: Boolean = false,
|
||||||
) = accountForm("Login", "Login page", loggedInUser, error, validationErrors, "Sign In", "Sign In", new) {
|
) = accountForm("Login", "Login page", jwtPayload, error, validationErrors, "Sign In", "Sign In", new) {
|
||||||
+"Don't have an account yet? "
|
+"Don't have an account yet? "
|
||||||
a(href = "/register", classes = "no-underline text-blue-500 hover:text-blue-400 font-bold") {
|
a(href = "/register", classes = "no-underline text-blue-500 hover:text-blue-400 font-bold") {
|
||||||
+"Create an account"
|
+"Create an account"
|
||||||
@@ -41,14 +42,14 @@ class UserView(styles: String) : View(styles) {
|
|||||||
private fun accountForm(
|
private fun accountForm(
|
||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
loggedInUser: LoggedInUser?,
|
jwtPayload: JwtPayload?,
|
||||||
error: String? = null,
|
error: String? = null,
|
||||||
validationErrors: List<ValidationError> = emptyList(),
|
validationErrors: List<ValidationError> = emptyList(),
|
||||||
h1: String,
|
h1: String,
|
||||||
submit: String,
|
submit: String,
|
||||||
new: Boolean = false,
|
new: Boolean = false,
|
||||||
footer: FlowContent.() -> Unit,
|
footer: FlowContent.() -> Unit,
|
||||||
) = renderPage(title = title, description, loggedInUser = loggedInUser) {
|
) = renderPage(title = title, description, jwtPayload = jwtPayload) {
|
||||||
div("centered container mx-auto flex justify-center items-center") {
|
div("centered container mx-auto flex justify-center items-center") {
|
||||||
div("w-full md:w-1/2 lg:w-1/3 m-4") {
|
div("w-full md:w-1/2 lg:w-1/3 m-4") {
|
||||||
div("p-8 mb-6") {
|
div("p-8 mb-6") {
|
||||||
@@ -1,16 +1,19 @@
|
|||||||
package be.simplenotes.views
|
package be.simplenotes.app.views
|
||||||
|
|
||||||
import be.simplenotes.types.LoggedInUser
|
import be.simplenotes.app.utils.StaticFileResolver
|
||||||
import be.simplenotes.views.components.navbar
|
import be.simplenotes.app.views.components.navbar
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.html.stream.appendHTML
|
import kotlinx.html.stream.appendHTML
|
||||||
|
|
||||||
abstract class View(private val styles: String) {
|
abstract class View(staticFileResolver: StaticFileResolver) {
|
||||||
|
|
||||||
|
private val styles = staticFileResolver.resolve("styles.css")!!
|
||||||
|
|
||||||
fun renderPage(
|
fun renderPage(
|
||||||
title: String,
|
title: String,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
loggedInUser: LoggedInUser?,
|
jwtPayload: JwtPayload?,
|
||||||
scripts: List<String> = emptyList(),
|
scripts: List<String> = emptyList(),
|
||||||
body: MAIN.() -> Unit = {},
|
body: MAIN.() -> Unit = {},
|
||||||
) = buildString {
|
) = buildString {
|
||||||
@@ -34,7 +37,7 @@ abstract class View(private val styles: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
body("bg-gray-900 text-white") {
|
body("bg-gray-900 text-white") {
|
||||||
navbar(loggedInUser)
|
navbar(jwtPayload)
|
||||||
main { body() }
|
main { body() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package be.simplenotes.views.components
|
package be.simplenotes.app.views.components
|
||||||
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
|
||||||
internal fun FlowContent.alert(type: Alert, title: String, details: String? = null, multiline: Boolean = false) {
|
fun FlowContent.alert(type: Alert, title: String, details: String? = null, multiline: Boolean = false) {
|
||||||
val colors = when (type) {
|
val colors = when (type) {
|
||||||
Alert.Success -> "bg-green-500 border border-green-400 text-gray-800"
|
Alert.Success -> "bg-green-500 border border-green-400 text-gray-800"
|
||||||
Alert.Warning -> "bg-red-500 border border-red-400 text-red-200"
|
Alert.Warning -> "bg-red-500 border border-red-400 text-red-200"
|
||||||
@@ -17,6 +17,6 @@ internal fun FlowContent.alert(type: Alert, title: String, details: String? = nu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum class Alert {
|
enum class Alert {
|
||||||
Success, Warning
|
Success, Warning
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package be.simplenotes.views.components
|
package be.simplenotes.app.views.components
|
||||||
|
|
||||||
import be.simplenotes.types.PersistedNoteMetadata
|
import be.simplenotes.app.utils.toTimeAgo
|
||||||
import be.simplenotes.views.utils.toTimeAgo
|
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.html.ButtonType.submit
|
import kotlinx.html.ButtonType.submit
|
||||||
import kotlinx.html.FormMethod.post
|
import kotlinx.html.FormMethod.post
|
||||||
import kotlinx.html.ThScope.col
|
import kotlinx.html.ThScope.col
|
||||||
|
|
||||||
internal fun FlowContent.deletedNoteTable(notes: List<PersistedNoteMetadata>) = div("overflow-x-auto") {
|
fun FlowContent.deletedNoteTable(notes: List<PersistedNoteMetadata>) = div("overflow-x-auto") {
|
||||||
table {
|
table {
|
||||||
id = "notes"
|
id = "notes"
|
||||||
thead {
|
thead {
|
||||||
@@ -25,8 +25,8 @@ internal fun FlowContent.deletedNoteTable(notes: List<PersistedNoteMetadata>) =
|
|||||||
td("text-center") { +updatedAt.toTimeAgo() }
|
td("text-center") { +updatedAt.toTimeAgo() }
|
||||||
td { tags(tags) }
|
td { tags(tags) }
|
||||||
td("text-center") {
|
td("text-center") {
|
||||||
form(method = post, action = "/notes/deleted/$uuid") {
|
form(classes = "inline", method = post, action = "/notes/deleted/$uuid") {
|
||||||
button(classes = "btn btn-red mb-2", type = submit, name = "delete") {
|
button(classes = "btn btn-red", type = submit, name = "delete") {
|
||||||
+"Delete permanently"
|
+"Delete permanently"
|
||||||
}
|
}
|
||||||
button(classes = "ml-2 btn btn-green", type = submit, name = "restore") {
|
button(classes = "ml-2 btn btn-green", type = submit, name = "restore") {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package be.simplenotes.views.components
|
package be.simplenotes.app.views.components
|
||||||
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.html.ButtonType.submit
|
import kotlinx.html.ButtonType.submit
|
||||||
|
|
||||||
internal fun FlowContent.input(
|
fun FlowContent.input(
|
||||||
type: InputType = InputType.text,
|
type: InputType = InputType.text,
|
||||||
placeholder: String,
|
placeholder: String,
|
||||||
id: String,
|
id: String,
|
||||||
@@ -26,7 +26,7 @@ internal fun FlowContent.input(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun FlowContent.submitButton(text: String) {
|
fun FlowContent.submitButton(text: String) {
|
||||||
div("flex items-center mt-6") {
|
div("flex items-center mt-6") {
|
||||||
button(
|
button(
|
||||||
type = submit,
|
type = submit,
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package be.simplenotes.views.components
|
package be.simplenotes.app.views.components
|
||||||
|
|
||||||
import be.simplenotes.types.LoggedInUser
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
|
|
||||||
internal fun BODY.navbar(loggedInUser: LoggedInUser?) {
|
fun BODY.navbar(jwtPayload: JwtPayload?) {
|
||||||
nav {
|
nav {
|
||||||
id = "navbar"
|
id = "navbar"
|
||||||
a("/") {
|
a("/") {
|
||||||
@@ -12,7 +12,7 @@ internal fun BODY.navbar(loggedInUser: LoggedInUser?) {
|
|||||||
}
|
}
|
||||||
ul("space-x-2") {
|
ul("space-x-2") {
|
||||||
id = "navigation"
|
id = "navigation"
|
||||||
if (loggedInUser != null) {
|
if (jwtPayload != null) {
|
||||||
val links = listOf(
|
val links = listOf(
|
||||||
"/notes" to "Notes",
|
"/notes" to "Notes",
|
||||||
"/settings" to "Settings",
|
"/settings" to "Settings",
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package be.simplenotes.views.components
|
package be.simplenotes.app.views.components
|
||||||
|
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.html.ButtonType.submit
|
import kotlinx.html.ButtonType.submit
|
||||||
import kotlinx.html.FormMethod.post
|
import kotlinx.html.FormMethod.post
|
||||||
|
|
||||||
internal fun DIV.noteListHeader(numberOfDeletedNotes: Int, query: String = "") {
|
fun DIV.noteListHeader(numberOfDeletedNotes: Int, query: String = "") {
|
||||||
div("flex justify-between mb-4") {
|
div("flex justify-between mb-4") {
|
||||||
h1("text-2xl underline") { +"Notes" }
|
h1("text-2xl underline") { +"Notes" }
|
||||||
span {
|
span {
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package be.simplenotes.views.components
|
package be.simplenotes.app.views.components
|
||||||
|
|
||||||
import be.simplenotes.types.PersistedNoteMetadata
|
import be.simplenotes.app.utils.toTimeAgo
|
||||||
import be.simplenotes.views.utils.toTimeAgo
|
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||||
import kotlinx.html.*
|
import kotlinx.html.*
|
||||||
import kotlinx.html.ThScope.col
|
import kotlinx.html.ThScope.col
|
||||||
|
|
||||||
internal fun FlowContent.noteTable(notes: List<PersistedNoteMetadata>) = div("overflow-x-auto") {
|
fun FlowContent.noteTable(notes: List<PersistedNoteMetadata>) = div("overflow-x-auto") {
|
||||||
table {
|
table {
|
||||||
id = "notes"
|
id = "notes"
|
||||||
thead {
|
thead {
|
||||||
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 814 B After Width: | Height: | Size: 814 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1 @@
|
|||||||
|
package be.simplenotes.app
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package be.simplenotes.app.filters
|
package be.simplenotes.app.filters
|
||||||
|
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import be.simplenotes.domain.security.JwtPayloadExtractor
|
import be.simplenotes.domain.security.JwtPayloadExtractor
|
||||||
import be.simplenotes.domain.security.SimpleJwt
|
import be.simplenotes.domain.security.SimpleJwt
|
||||||
import be.simplenotes.config.JwtConfig
|
import be.simplenotes.shared.config.JwtConfig
|
||||||
import be.simplenotes.types.LoggedInUser
|
|
||||||
import com.natpryce.hamkrest.assertion.assertThat
|
import com.natpryce.hamkrest.assertion.assertThat
|
||||||
import org.http4k.core.*
|
import org.http4k.core.*
|
||||||
import org.http4k.core.Method.GET
|
import org.http4k.core.Method.GET
|
||||||
@@ -58,7 +58,7 @@ internal class AuthFilterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `it should allow a valid token`() {
|
fun `it should allow a valid token`() {
|
||||||
val jwtPayload = LoggedInUser(1, "user")
|
val jwtPayload = JwtPayload(1, "user")
|
||||||
val token = simpleJwt.sign(jwtPayload)
|
val token = simpleJwt.sign(jwtPayload)
|
||||||
val response = app(Request(GET, "/optional").cookie("Bearer", token))
|
val response = app(Request(GET, "/optional").cookie("Bearer", token))
|
||||||
assertThat(response, hasStatus(OK))
|
assertThat(response, hasStatus(OK))
|
||||||
@@ -84,7 +84,7 @@ internal class AuthFilterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `it should allow a valid token"`() {
|
fun `it should allow a valid token"`() {
|
||||||
val jwtPayload = LoggedInUser(1, "user")
|
val jwtPayload = JwtPayload(1, "user")
|
||||||
val token = simpleJwt.sign(jwtPayload)
|
val token = simpleJwt.sign(jwtPayload)
|
||||||
val response = app(Request(GET, "/protected").cookie("Bearer", token))
|
val response = app(Request(GET, "/protected").cookie("Bearer", token))
|
||||||
assertThat(response, hasStatus(OK))
|
assertThat(response, hasStatus(OK))
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package be.simplenotes.app.utils
|
package be.simplenotes.app.utils
|
||||||
|
|
||||||
import be.simplenotes.search.SearchTerms
|
import be.simplenotes.domain.usecases.search.SearchTerms
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
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
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
"name": "css",
|
"name": "css",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"css": "NODE_ENV=dev MANIFEST=../simplenotes-app/src/main/resources/css-manifest.json postcss build src/styles.pcss --output ../simplenotes-app/src/main/resources/static/styles.css",
|
"css": "NODE_ENV=dev MANIFEST=../app/src/main/resources/css-manifest.json postcss build src/styles.pcss --output ../app/src/main/resources/static/styles.css",
|
||||||
"css-purge": "NODE_ENV=production MANIFEST=../simplenotes-app/src/main/resources/css-manifest.json postcss build src/styles.pcss --output ../simplenotes-app/src/main/resources/static/styles.css"
|
"css-purge": "NODE_ENV=production MANIFEST=../app/src/main/resources/css-manifest.json postcss build src/styles.pcss --output ../app/src/main/resources/static/styles.css"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autoprefixer": "^9.8.6",
|
"autoprefixer": "^9.8.6",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
purge: {
|
purge: {
|
||||||
content: [
|
content: [
|
||||||
'../simplenotes-app/src/main/kotlin/be/simplenotes/app/views/**/*.kt'
|
'../app/src/main/kotlin/views/**/*.kt'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
rm simplenotes-app/src/main/resources/css-manifest.json
|
rm app/src/main/resources/css-manifest.json
|
||||||
rm simplenotes-app/src/main/resources/static/styles*
|
rm app/src/main/resources/static/styles*
|
||||||
|
|
||||||
yarn --cwd css run css-purge \
|
yarn --cwd css run css-purge \
|
||||||
&& docker build -t hubv/simplenotes:latest . \
|
&& docker build -t hubv/simplenotes:latest . \
|
||||||
|
|||||||
@@ -2,38 +2,36 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>simplenotes-parent</artifactId>
|
<artifactId>parent</artifactId>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>simplenotes-domain</artifactId>
|
<artifactId>domain</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>simplenotes-config</artifactId>
|
<artifactId>shared</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>simplenotes-test-resources</artifactId>
|
<artifactId>shared</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
<type>test-jar</type>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.koin</groupId>
|
|
||||||
<artifactId>koin-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.arrow-kt</groupId>
|
<groupId>io.arrow-kt</groupId>
|
||||||
<artifactId>arrow-core</artifactId>
|
<artifactId>arrow-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.koin</groupId>
|
||||||
|
<artifactId>koin-core</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.natpryce</groupId>
|
<groupId>com.natpryce</groupId>
|
||||||
<artifactId>hamkrest</artifactId>
|
<artifactId>hamkrest</artifactId>
|
||||||
@@ -85,29 +83,15 @@
|
|||||||
<artifactId>owasp-java-html-sanitizer</artifactId>
|
<artifactId>owasp-java-html-sanitizer</artifactId>
|
||||||
<version>20200713.1</version>
|
<version>20200713.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlinx</groupId>
|
||||||
|
<artifactId>kotlinx-serialization-json-jvm</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-compress</artifactId>
|
<artifactId>commons-compress</artifactId>
|
||||||
<version>1.20</version>
|
<version>1.20</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>be.simplenotes</groupId>
|
|
||||||
<artifactId>simplenotes-types</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>be.simplenotes</groupId>
|
|
||||||
<artifactId>simplenotes-persistance</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>be.simplenotes</groupId>
|
|
||||||
<artifactId>simplenotes-search</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package be.simplenotes.types
|
package be.simplenotes.domain.model
|
||||||
|
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package be.simplenotes.domain.model
|
||||||
|
|
||||||
|
data class User(val username: String, val password: String)
|
||||||
|
data class PersistedUser(val username: String, val password: String, val id: Int)
|
||||||
@@ -2,7 +2,7 @@ package be.simplenotes.domain.security
|
|||||||
|
|
||||||
import org.owasp.html.HtmlPolicyBuilder
|
import org.owasp.html.HtmlPolicyBuilder
|
||||||
|
|
||||||
internal object HtmlSanitizer {
|
object HtmlSanitizer {
|
||||||
private val htmlPolicy = HtmlPolicyBuilder()
|
private val htmlPolicy = HtmlPolicyBuilder()
|
||||||
.allowElements("a")
|
.allowElements("a")
|
||||||
.allowCommonBlockElements()
|
.allowCommonBlockElements()
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
package be.simplenotes.domain.security
|
package be.simplenotes.domain.security
|
||||||
|
|
||||||
import be.simplenotes.types.LoggedInUser
|
import be.simplenotes.domain.model.PersistedUser
|
||||||
import com.auth0.jwt.exceptions.JWTVerificationException
|
import com.auth0.jwt.exceptions.JWTVerificationException
|
||||||
|
|
||||||
|
data class JwtPayload(val userId: Int, val username: String) {
|
||||||
|
constructor(user: PersistedUser) : this(user.id, user.username)
|
||||||
|
}
|
||||||
|
|
||||||
class JwtPayloadExtractor(private val jwt: SimpleJwt) {
|
class JwtPayloadExtractor(private val jwt: SimpleJwt) {
|
||||||
operator fun invoke(token: String): LoggedInUser? = try {
|
operator fun invoke(token: String): JwtPayload? = try {
|
||||||
val decodedJWT = jwt.verifier.verify(token)
|
val decodedJWT = jwt.verifier.verify(token)
|
||||||
val id = decodedJWT.getClaim(userIdField).asInt() ?: null
|
val id = decodedJWT.getClaim(userIdField).asInt() ?: null
|
||||||
val username = decodedJWT.getClaim(usernameField).asString() ?: null
|
val username = decodedJWT.getClaim(usernameField).asString() ?: null
|
||||||
id?.let { username?.let { LoggedInUser(id, username) } }
|
id?.let { username?.let { JwtPayload(id, username) } }
|
||||||
} catch (e: JWTVerificationException) {
|
} catch (e: JWTVerificationException) {
|
||||||
null
|
null
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package be.simplenotes.domain.security
|
package be.simplenotes.domain.security
|
||||||
|
|
||||||
import be.simplenotes.config.JwtConfig
|
import be.simplenotes.shared.config.JwtConfig
|
||||||
import be.simplenotes.types.LoggedInUser
|
|
||||||
import com.auth0.jwt.JWT
|
import com.auth0.jwt.JWT
|
||||||
import com.auth0.jwt.JWTVerifier
|
import com.auth0.jwt.JWTVerifier
|
||||||
import com.auth0.jwt.algorithms.Algorithm
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
@@ -16,9 +15,9 @@ class SimpleJwt(jwtConfig: JwtConfig) {
|
|||||||
private val algorithm = Algorithm.HMAC256(jwtConfig.secret)
|
private val algorithm = Algorithm.HMAC256(jwtConfig.secret)
|
||||||
|
|
||||||
val verifier: JWTVerifier = JWT.require(algorithm).build()
|
val verifier: JWTVerifier = JWT.require(algorithm).build()
|
||||||
fun sign(loggedInUser: LoggedInUser): String = JWT.create()
|
fun sign(jwtPayload: JwtPayload): String = JWT.create()
|
||||||
.withClaim(userIdField, loggedInUser.userId)
|
.withClaim(userIdField, jwtPayload.userId)
|
||||||
.withClaim(usernameField, loggedInUser.username)
|
.withClaim(usernameField, jwtPayload.username)
|
||||||
.withExpiresAt(getExpiration())
|
.withExpiresAt(getExpiration())
|
||||||
.sign(algorithm)
|
.sign(algorithm)
|
||||||
|
|
||||||
@@ -2,16 +2,16 @@ package be.simplenotes.domain.usecases
|
|||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.extensions.fx
|
import arrow.core.extensions.fx
|
||||||
import be.simplenotes.types.Note
|
import be.simplenotes.domain.model.Note
|
||||||
import be.simplenotes.types.PersistedNote
|
import be.simplenotes.domain.model.PersistedNote
|
||||||
import be.simplenotes.types.PersistedNoteMetadata
|
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||||
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.persistance.repositories.NoteRepository
|
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
||||||
import be.simplenotes.persistance.repositories.UserRepository
|
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||||
import be.simplenotes.search.NoteSearcher
|
import be.simplenotes.domain.usecases.search.NoteSearcher
|
||||||
import be.simplenotes.search.SearchTerms
|
import be.simplenotes.domain.usecases.search.SearchTerms
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class NoteService(
|
class NoteService(
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package be.simplenotes.domain.usecases.export
|
package be.simplenotes.domain.usecases.export
|
||||||
|
|
||||||
import be.simplenotes.types.ExportedNote
|
import be.simplenotes.domain.model.ExportedNote
|
||||||
import be.simplenotes.persistance.repositories.NoteRepository
|
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
||||||
@@ -4,7 +4,7 @@ import arrow.core.Either
|
|||||||
import arrow.core.extensions.fx
|
import arrow.core.extensions.fx
|
||||||
import arrow.core.left
|
import arrow.core.left
|
||||||
import arrow.core.right
|
import arrow.core.right
|
||||||
import be.simplenotes.types.NoteMetadata
|
import be.simplenotes.domain.model.NoteMetadata
|
||||||
import be.simplenotes.domain.validation.NoteValidations
|
import be.simplenotes.domain.validation.NoteValidations
|
||||||
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
|
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
|
||||||
import com.vladsch.flexmark.html.HtmlRenderer
|
import com.vladsch.flexmark.html.HtmlRenderer
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package be.simplenotes.persistance.repositories
|
package be.simplenotes.domain.usecases.repositories
|
||||||
|
|
||||||
import be.simplenotes.types.ExportedNote
|
import be.simplenotes.domain.model.ExportedNote
|
||||||
import be.simplenotes.types.Note
|
import be.simplenotes.domain.model.Note
|
||||||
import be.simplenotes.types.PersistedNote
|
import be.simplenotes.domain.model.PersistedNote
|
||||||
import be.simplenotes.types.PersistedNoteMetadata
|
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
interface NoteRepository {
|
interface NoteRepository {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package be.simplenotes.persistance.repositories
|
package be.simplenotes.domain.usecases.repositories
|
||||||
|
|
||||||
import be.simplenotes.types.PersistedUser
|
import be.simplenotes.domain.model.PersistedUser
|
||||||
import be.simplenotes.types.User
|
import be.simplenotes.domain.model.User
|
||||||
|
|
||||||
interface UserRepository {
|
interface UserRepository {
|
||||||
fun create(user: User): PersistedUser?
|
fun create(user: User): PersistedUser?
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package be.simplenotes.search
|
package be.simplenotes.domain.usecases.search
|
||||||
|
|
||||||
import be.simplenotes.types.PersistedNote
|
import be.simplenotes.domain.model.PersistedNote
|
||||||
import be.simplenotes.types.PersistedNoteMetadata
|
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class SearchTerms(val title: String?, val tag: String?, val content: String?, val all: String?)
|
data class SearchTerms(val title: String?, val tag: String?, val content: String?, val all: String?)
|
||||||
@@ -4,9 +4,9 @@ import arrow.core.Either
|
|||||||
import arrow.core.extensions.fx
|
import arrow.core.extensions.fx
|
||||||
import arrow.core.rightIfNotNull
|
import arrow.core.rightIfNotNull
|
||||||
import be.simplenotes.domain.security.PasswordHash
|
import be.simplenotes.domain.security.PasswordHash
|
||||||
import be.simplenotes.persistance.repositories.UserRepository
|
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||||
|
import be.simplenotes.domain.usecases.search.NoteSearcher
|
||||||
import be.simplenotes.domain.validation.UserValidations
|
import be.simplenotes.domain.validation.UserValidations
|
||||||
import be.simplenotes.search.NoteSearcher
|
|
||||||
|
|
||||||
internal class DeleteUseCaseImpl(
|
internal class DeleteUseCaseImpl(
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
@@ -4,11 +4,11 @@ import arrow.core.Either
|
|||||||
import arrow.core.extensions.fx
|
import arrow.core.extensions.fx
|
||||||
import arrow.core.filterOrElse
|
import arrow.core.filterOrElse
|
||||||
import arrow.core.rightIfNotNull
|
import arrow.core.rightIfNotNull
|
||||||
|
import be.simplenotes.domain.security.JwtPayload
|
||||||
import be.simplenotes.domain.security.PasswordHash
|
import be.simplenotes.domain.security.PasswordHash
|
||||||
import be.simplenotes.domain.security.SimpleJwt
|
import be.simplenotes.domain.security.SimpleJwt
|
||||||
|
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||||
import be.simplenotes.domain.validation.UserValidations
|
import be.simplenotes.domain.validation.UserValidations
|
||||||
import be.simplenotes.persistance.repositories.UserRepository
|
|
||||||
import be.simplenotes.types.LoggedInUser
|
|
||||||
|
|
||||||
internal class LoginUseCaseImpl(
|
internal class LoginUseCaseImpl(
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
@@ -20,6 +20,6 @@ internal class LoginUseCaseImpl(
|
|||||||
!userRepository.find(user.username)
|
!userRepository.find(user.username)
|
||||||
.rightIfNotNull { Unregistered }
|
.rightIfNotNull { Unregistered }
|
||||||
.filterOrElse({ passwordHash.verify(form.password!!, it.password) }, { WrongPassword })
|
.filterOrElse({ passwordHash.verify(form.password!!, it.password) }, { WrongPassword })
|
||||||
.map { jwt.sign(LoggedInUser(it)) }
|
.map { jwt.sign(JwtPayload(it)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,10 @@ package be.simplenotes.domain.usecases.users.register
|
|||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.filterOrElse
|
import arrow.core.filterOrElse
|
||||||
import arrow.core.leftIfNull
|
import arrow.core.leftIfNull
|
||||||
import be.simplenotes.types.PersistedUser
|
import be.simplenotes.domain.model.PersistedUser
|
||||||
import be.simplenotes.domain.security.PasswordHash
|
import be.simplenotes.domain.security.PasswordHash
|
||||||
|
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||||
import be.simplenotes.domain.validation.UserValidations
|
import be.simplenotes.domain.validation.UserValidations
|
||||||
import be.simplenotes.persistance.repositories.UserRepository
|
|
||||||
|
|
||||||
internal class RegisterUseCaseImpl(
|
internal class RegisterUseCaseImpl(
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package be.simplenotes.domain.usecases.users.register
|
package be.simplenotes.domain.usecases.users.register
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import be.simplenotes.types.PersistedUser
|
import be.simplenotes.domain.model.PersistedUser
|
||||||
import be.simplenotes.domain.usecases.users.login.LoginForm
|
import be.simplenotes.domain.usecases.users.login.LoginForm
|
||||||
import io.konform.validation.ValidationErrors
|
import io.konform.validation.ValidationErrors
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package be.simplenotes.domain.validation
|
package be.simplenotes.domain.validation
|
||||||
|
|
||||||
import arrow.core.*
|
import arrow.core.*
|
||||||
import be.simplenotes.types.NoteMetadata
|
import be.simplenotes.domain.model.NoteMetadata
|
||||||
import be.simplenotes.domain.usecases.markdown.ValidationError
|
import be.simplenotes.domain.usecases.markdown.ValidationError
|
||||||
import io.konform.validation.Validation
|
import io.konform.validation.Validation
|
||||||
import io.konform.validation.jsonschema.maxItems
|
import io.konform.validation.jsonschema.maxItems
|
||||||
@@ -3,7 +3,7 @@ package be.simplenotes.domain.validation
|
|||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import arrow.core.left
|
import arrow.core.left
|
||||||
import arrow.core.right
|
import arrow.core.right
|
||||||
import be.simplenotes.types.User
|
import be.simplenotes.domain.model.User
|
||||||
import be.simplenotes.domain.usecases.users.delete.DeleteError
|
import be.simplenotes.domain.usecases.users.delete.DeleteError
|
||||||
import be.simplenotes.domain.usecases.users.delete.DeleteForm
|
import be.simplenotes.domain.usecases.users.delete.DeleteForm
|
||||||
import be.simplenotes.domain.usecases.users.login.InvalidLoginForm
|
import be.simplenotes.domain.usecases.users.login.InvalidLoginForm
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package be.simplenotes.domain
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty file @see [root-package-declaration](https://discuss.kotlinlang.org/t/root-package-declaration-to-reduce-folder-clutter/2247/4)
|
||||||
|
*/
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
package be.simplenotes.domain.security
|
package be.simplenotes.domain.security
|
||||||
|
|
||||||
import be.simplenotes.domain.usecases.users.login.Token
|
import be.simplenotes.domain.usecases.users.login.Token
|
||||||
import be.simplenotes.config.JwtConfig
|
import be.simplenotes.shared.config.JwtConfig
|
||||||
import be.simplenotes.types.LoggedInUser
|
|
||||||
import com.auth0.jwt.JWT
|
import com.auth0.jwt.JWT
|
||||||
import com.auth0.jwt.algorithms.Algorithm
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
import com.natpryce.hamkrest.absent
|
import com.natpryce.hamkrest.absent
|
||||||
@@ -14,7 +13,7 @@ import org.junit.jupiter.params.provider.MethodSource
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
|
|
||||||
internal class LoggedInUserExtractorTest {
|
internal class JwtPayloadExtractorTest {
|
||||||
private val jwtConfig = JwtConfig("a secret", 1, TimeUnit.HOURS)
|
private val jwtConfig = JwtConfig("a secret", 1, TimeUnit.HOURS)
|
||||||
private val simpleJwt = SimpleJwt(jwtConfig)
|
private val simpleJwt = SimpleJwt(jwtConfig)
|
||||||
private val jwtPayloadExtractor = JwtPayloadExtractor(simpleJwt)
|
private val jwtPayloadExtractor = JwtPayloadExtractor(simpleJwt)
|
||||||
@@ -46,6 +45,6 @@ internal class LoggedInUserExtractorTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `parse valid token`() {
|
fun `parse valid token`() {
|
||||||
val token = createToken(username = "someone", id = 1)
|
val token = createToken(username = "someone", id = 1)
|
||||||
assertThat(jwtPayloadExtractor(token), equalTo(LoggedInUser(1, "someone")))
|
assertThat(jwtPayloadExtractor(token), equalTo(JwtPayload(1, "someone")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package be.simplenotes.domain.usecases.users.login
|
package be.simplenotes.domain.usecases.users.login
|
||||||
|
|
||||||
import be.simplenotes.types.PersistedUser
|
import be.simplenotes.domain.model.PersistedUser
|
||||||
import be.simplenotes.domain.security.BcryptPasswordHash
|
import be.simplenotes.domain.security.BcryptPasswordHash
|
||||||
import be.simplenotes.domain.security.SimpleJwt
|
import be.simplenotes.domain.security.SimpleJwt
|
||||||
import be.simplenotes.persistance.repositories.UserRepository
|
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||||
import be.simplenotes.config.JwtConfig
|
import be.simplenotes.shared.config.JwtConfig
|
||||||
import be.simplenotes.domain.testutils.isLeftOfType
|
import be.simplenotes.shared.testutils.assertions.isLeftOfType
|
||||||
import be.simplenotes.domain.testutils.isRight
|
import be.simplenotes.shared.testutils.assertions.isRight
|
||||||
import com.natpryce.hamkrest.assertion.assertThat
|
import com.natpryce.hamkrest.assertion.assertThat
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package be.simplenotes.domain.usecases.users.register
|
package be.simplenotes.domain.usecases.users.register
|
||||||
|
|
||||||
import be.simplenotes.types.PersistedUser
|
import be.simplenotes.domain.model.PersistedUser
|
||||||
import be.simplenotes.domain.security.BcryptPasswordHash
|
import be.simplenotes.domain.security.BcryptPasswordHash
|
||||||
import be.simplenotes.domain.testutils.isLeftOfType
|
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||||
import be.simplenotes.domain.testutils.isRight
|
import be.simplenotes.shared.testutils.assertions.isLeftOfType
|
||||||
import be.simplenotes.persistance.repositories.UserRepository
|
import be.simplenotes.shared.testutils.assertions.isRight
|
||||||
import com.natpryce.hamkrest.assertion.assertThat
|
import com.natpryce.hamkrest.assertion.assertThat
|
||||||
import com.natpryce.hamkrest.equalTo
|
import com.natpryce.hamkrest.equalTo
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package be.simplenotes.domain.validation
|
package be.simplenotes.domain.validation
|
||||||
|
|
||||||
import be.simplenotes.domain.testutils.isLeftOfType
|
|
||||||
import be.simplenotes.domain.testutils.isRight
|
|
||||||
import be.simplenotes.domain.usecases.users.login.InvalidLoginForm
|
import be.simplenotes.domain.usecases.users.login.InvalidLoginForm
|
||||||
import be.simplenotes.domain.usecases.users.login.LoginForm
|
import be.simplenotes.domain.usecases.users.login.LoginForm
|
||||||
import be.simplenotes.domain.usecases.users.register.RegisterForm
|
import be.simplenotes.domain.usecases.users.register.RegisterForm
|
||||||
|
import be.simplenotes.shared.testutils.assertions.isLeftOfType
|
||||||
|
import be.simplenotes.shared.testutils.assertions.isRight
|
||||||
import com.natpryce.hamkrest.assertion.assertThat
|
import com.natpryce.hamkrest.assertion.assertThat
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
import org.junit.jupiter.params.ParameterizedTest
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>simplenotes-parent</artifactId>
|
<artifactId>parent</artifactId>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>simplenotes-persistance</artifactId>
|
<artifactId>persistance</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -17,27 +17,22 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>simplenotes-types</artifactId>
|
<artifactId>domain</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>simplenotes-config</artifactId>
|
<artifactId>shared</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>be.simplenotes</groupId>
|
<groupId>be.simplenotes</groupId>
|
||||||
<artifactId>simplenotes-test-resources</artifactId>
|
<artifactId>shared</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
<type>test-jar</type>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.koin</groupId>
|
|
||||||
<artifactId>koin-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.assertj</groupId>
|
<groupId>org.assertj</groupId>
|
||||||
<artifactId>assertj-core</artifactId>
|
<artifactId>assertj-core</artifactId>
|
||||||
@@ -64,13 +59,11 @@
|
|||||||
<artifactId>flyway-core</artifactId>
|
<artifactId>flyway-core</artifactId>
|
||||||
<version>6.5.4</version>
|
<version>6.5.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.zaxxer</groupId>
|
<groupId>com.zaxxer</groupId>
|
||||||
<artifactId>HikariCP</artifactId>
|
<artifactId>HikariCP</artifactId>
|
||||||
<version>3.4.3</version>
|
<version>3.4.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>me.liuwj.ktorm</groupId>
|
<groupId>me.liuwj.ktorm</groupId>
|
||||||
<artifactId>ktorm-core</artifactId>
|
<artifactId>ktorm-core</artifactId>
|
||||||
@@ -2,7 +2,7 @@ package be.simplenotes.persistance
|
|||||||
|
|
||||||
import be.simplenotes.persistance.utils.DbType
|
import be.simplenotes.persistance.utils.DbType
|
||||||
import be.simplenotes.persistance.utils.type
|
import be.simplenotes.persistance.utils.type
|
||||||
import be.simplenotes.config.DataSourceConfig
|
import be.simplenotes.shared.config.DataSourceConfig
|
||||||
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 java.sql.SQLTransientException
|
import java.sql.SQLTransientException
|
||||||