Compare commits
2 Commits
jigsaw
..
94b61f3de5
| Author | SHA1 | Date | |
|---|---|---|---|
| 94b61f3de5 | |||
| dcc70aab3a |
+2
-2
@@ -125,8 +125,8 @@ data/
|
||||
letsencrypt/
|
||||
|
||||
# generated resources
|
||||
simplenotes-app/src/main/resources/css-manifest.json
|
||||
simplenotes-app/src/main/resources/static/styles*
|
||||
app/src/main/resources/css-manifest.json
|
||||
app/src/main/resources/static/styles*
|
||||
|
||||
# h2 db
|
||||
*.db
|
||||
|
||||
+12
-18
@@ -4,23 +4,19 @@ WORKDIR /tmp
|
||||
|
||||
# Cache dependencies
|
||||
COPY pom.xml .
|
||||
COPY simplenotes-test-resources/pom.xml simplenotes-test-resources/pom.xml
|
||||
COPY simplenotes-types/pom.xml simplenotes-types/pom.xml
|
||||
COPY simplenotes-config/pom.xml simplenotes-config/pom.xml
|
||||
COPY simplenotes-persistance/pom.xml simplenotes-persistance/pom.xml
|
||||
COPY simplenotes-search/pom.xml simplenotes-search/pom.xml
|
||||
COPY simplenotes-domain/pom.xml simplenotes-domain/pom.xml
|
||||
COPY simplenotes-app/pom.xml simplenotes-app/pom.xml
|
||||
COPY app/pom.xml app/pom.xml
|
||||
COPY domain/pom.xml domain/pom.xml
|
||||
COPY persistance/pom.xml persistance/pom.xml
|
||||
COPY shared/pom.xml shared/pom.xml
|
||||
COPY search/pom.xml search/pom.xml
|
||||
|
||||
RUN mvn verify clean --fail-never
|
||||
|
||||
COPY simplenotes-test-resources/src simplenotes-test-resources/src
|
||||
COPY simplenotes-types/src simplenotes-types/src
|
||||
COPY simplenotes-config/src simplenotes-config/src
|
||||
COPY simplenotes-persistance/src simplenotes-persistance/src
|
||||
COPY simplenotes-search/src simplenotes-search/src
|
||||
COPY simplenotes-domain/src simplenotes-domain/src
|
||||
COPY simplenotes-app/src simplenotes-app/src
|
||||
COPY app/src app/src
|
||||
COPY domain/src domain/src
|
||||
COPY persistance/src persistance/src
|
||||
COPY shared/src shared/src
|
||||
COPY search/src search/src
|
||||
|
||||
RUN mvn -Dstyle.color=always package
|
||||
|
||||
@@ -36,8 +32,6 @@ RUN strip -p --strip-unneeded /myjdk/lib/server/libjvm.so
|
||||
|
||||
FROM alpine
|
||||
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
ENV APPLICATION_USER simplenotes
|
||||
RUN adduser -D -g '' $APPLICATION_USER
|
||||
|
||||
@@ -46,8 +40,8 @@ RUN chown -R $APPLICATION_USER /app
|
||||
|
||||
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
|
||||
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"]
|
||||
|
||||
@@ -1,70 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>simplenotes-parent</artifactId>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>simplenotes-app</artifactId>
|
||||
<artifactId>app</artifactId>
|
||||
|
||||
<properties>
|
||||
<http4k.version>3.268.0</http4k.version>
|
||||
<http4k.version>3.258.0</http4k.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>simplenotes-persistance</artifactId>
|
||||
<artifactId>persistance</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>simplenotes-search</artifactId>
|
||||
<artifactId>search</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>simplenotes-domain</artifactId>
|
||||
<artifactId>domain</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>simplenotes-config</artifactId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.http4k</groupId>
|
||||
<artifactId>http4k-core</artifactId>
|
||||
<version>${http4k.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>org.http4k</groupId>
|
||||
<artifactId>http4k-server-jetty</artifactId>
|
||||
<version>${http4k.version}</version>
|
||||
</dependency>
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>9.4.32.v20200930</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<version>9.4.32.v20200930</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-html-jvm</artifactId>
|
||||
@@ -72,7 +50,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-serialization-json-jvm</artifactId>
|
||||
<artifactId>kotlinx-serialization-runtime</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ocpsoft.prettytime</groupId>
|
||||
@@ -80,24 +58,9 @@
|
||||
<version>4.0.5.Final</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>simplenotes-test-resources</artifactId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
@@ -105,32 +68,17 @@
|
||||
<dependency>
|
||||
<groupId>org.http4k</groupId>
|
||||
<artifactId>http4k-testing-hamkrest</artifactId>
|
||||
<version>${http4k.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>me.liuwj.ktorm</groupId>
|
||||
<artifactId>ktorm-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.http4k</groupId>
|
||||
<artifactId>http4k-bom</artifactId>
|
||||
<version>${http4k.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
package be.simplenotes.app
|
||||
|
||||
import be.simplenotes.config.DataSourceConfig
|
||||
import be.simplenotes.config.JwtConfig
|
||||
import be.simplenotes.config.ServerConfig
|
||||
import be.simplenotes.shared.config.DataSourceConfig
|
||||
import be.simplenotes.shared.config.JwtConfig
|
||||
import be.simplenotes.shared.config.ServerConfig
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package be.simplenotes.app
|
||||
|
||||
import org.http4k.server.Http4kServer
|
||||
import org.slf4j.LoggerFactory
|
||||
import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig
|
||||
import be.simplenotes.shared.config.ServerConfig as SimpleNotesServerConfig
|
||||
|
||||
class Server(
|
||||
private val config: SimpleNotesServerConfig,
|
||||
+26
-23
@@ -1,46 +1,55 @@
|
||||
package be.simplenotes.app.api
|
||||
|
||||
import be.simplenotes.app.extensions.auto
|
||||
import be.simplenotes.app.extensions.json
|
||||
import be.simplenotes.app.utils.parseSearchTerms
|
||||
import be.simplenotes.types.PersistedNote
|
||||
import be.simplenotes.types.PersistedNoteMetadata
|
||||
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.lens.Path
|
||||
import org.http4k.lens.uuid
|
||||
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 = noteContentLens(request)
|
||||
val content = json.decodeFromString(NoteContent.serializer(), request.bodyString()).content
|
||||
return noteService.create(jwtPayload.userId, content).fold(
|
||||
{ Response(BAD_REQUEST) },
|
||||
{ uuidContentLens(UuidContent(it.uuid), Response(OK)) }
|
||||
{
|
||||
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
|
||||
return persistedNotesMetadataLens(notes, Response(OK))
|
||||
val json = json.encodeToString(ListSerializer(PersistedNoteMetadata.serializer()), notes)
|
||||
return Response(OK).json(json)
|
||||
}
|
||||
|
||||
fun note(request: Request, jwtPayload: JwtPayload): Response =
|
||||
noteService.find(jwtPayload.userId, uuidLens(request))
|
||||
?.let { persistedNoteLens(it, Response(OK)) }
|
||||
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 content = noteContentLens(request)
|
||||
return noteService.update(jwtPayload.userId, uuidLens(request), content).fold({
|
||||
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)
|
||||
@@ -49,19 +58,13 @@ class ApiNoteController(private val noteService: NoteService, private val json:
|
||||
}
|
||||
|
||||
fun search(request: Request, jwtPayload: JwtPayload): Response {
|
||||
val query = searchContentLens(request)
|
||||
val query = json.decodeFromString(SearchContent.serializer(), request.bodyString()).query
|
||||
val terms = parseSearchTerms(query)
|
||||
val notes = noteService.search(jwtPayload.userId, terms)
|
||||
return persistedNotesMetadataLens(notes, Response(OK))
|
||||
val json = json.encodeToString(ListSerializer(PersistedNoteMetadata.serializer()), notes)
|
||||
return Response(OK).json(json)
|
||||
}
|
||||
|
||||
private val uuidContentLens = json.auto<UuidContent>().toLens()
|
||||
private val noteContentLens = json.auto<NoteContent>().map { it.content }.toLens()
|
||||
private val searchContentLens = json.auto<SearchContent>().map { it.query }.toLens()
|
||||
private val persistedNotesMetadataLens = json.auto<List<PersistedNoteMetadata>>().toLens()
|
||||
private val persistedNoteLens = json.auto<PersistedNote>().toLens()
|
||||
private val uuidLens = Path.uuid().of("uuid")
|
||||
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -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
-1
@@ -10,7 +10,7 @@ import be.simplenotes.domain.usecases.users.login.*
|
||||
import be.simplenotes.domain.usecases.users.register.InvalidRegisterForm
|
||||
import be.simplenotes.domain.usecases.users.register.RegisterForm
|
||||
import be.simplenotes.domain.usecases.users.register.UserExists
|
||||
import be.simplenotes.config.JwtConfig
|
||||
import be.simplenotes.shared.config.JwtConfig
|
||||
import org.http4k.core.Method.GET
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.Response
|
||||
@@ -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
|
||||
+4
-2
@@ -19,8 +19,9 @@ class AuthFilter(
|
||||
private val ctx: RequestContexts,
|
||||
private val source: JwtSource = JwtSource.Cookie,
|
||||
private val redirect: Boolean = true,
|
||||
) : Filter {
|
||||
override fun invoke(next: HttpHandler): HttpHandler = {
|
||||
) {
|
||||
operator fun invoke() = Filter { next ->
|
||||
{
|
||||
val token = when (source) {
|
||||
JwtSource.Header -> it.bearerTokenHeader()
|
||||
JwtSource.Cookie -> it.bearerTokenCookie()
|
||||
@@ -38,6 +39,7 @@ class AuthFilter(
|
||||
else -> next(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Request.jwtPayload(ctx: RequestContexts): JwtPayload? = ctx[this][authKey]
|
||||
@@ -0,0 +1,33 @@
|
||||
package be.simplenotes.app.filters
|
||||
|
||||
import be.simplenotes.app.extensions.html
|
||||
import be.simplenotes.app.views.ErrorView
|
||||
import org.http4k.core.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.sql.SQLTransientException
|
||||
|
||||
class ErrorFilter(private val errorView: ErrorView) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
operator fun invoke(): Filter = Filter { next ->
|
||||
{
|
||||
try {
|
||||
val response = next(it)
|
||||
if (response.status == Status.NOT_FOUND) Response(Status.NOT_FOUND)
|
||||
.html(errorView.error(ErrorView.Type.NotFound))
|
||||
else response
|
||||
} catch (e: Exception) {
|
||||
logger.error(e.stackTraceToString())
|
||||
if (e is SQLTransientException)
|
||||
Response(Status.SERVICE_UNAVAILABLE).html(errorView.error(ErrorView.Type.SqlTransientError))
|
||||
.noCache()
|
||||
else
|
||||
Response(Status.INTERNAL_SERVER_ERROR).html(errorView.error(ErrorView.Type.Other)).noCache()
|
||||
} catch (e: NotImplementedError) {
|
||||
logger.error(e.stackTraceToString())
|
||||
Response(Status.NOT_IMPLEMENTED).html(errorView.error(ErrorView.Type.Other)).noCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package be.simplenotes.app.filters
|
||||
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.HttpHandler
|
||||
import org.http4k.core.Method
|
||||
import org.http4k.core.Request
|
||||
|
||||
object ImmutableFilter {
|
||||
operator fun invoke() = Filter { next: HttpHandler ->
|
||||
{ request: Request ->
|
||||
next(request).header("Cache-Control", "public, max-age=31536000, immutable")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package be.simplenotes.app.filters
|
||||
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.HttpHandler
|
||||
import org.http4k.core.Request
|
||||
|
||||
object SecurityFilter {
|
||||
operator fun invoke() = Filter { next: HttpHandler ->
|
||||
{ request: Request ->
|
||||
val response = next(request)
|
||||
.header("X-Content-Type-Options", "nosniff")
|
||||
|
||||
if (response.header("Content-Type")?.contains("text/html") == true) {
|
||||
response
|
||||
.header("Content-Security-Policy", "default-src 'self'")
|
||||
.header("Referrer-Policy", "no-referrer")
|
||||
} else response
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -5,20 +5,19 @@ import be.simplenotes.app.api.ApiUserController
|
||||
import be.simplenotes.app.filters.AuthFilter
|
||||
import be.simplenotes.app.filters.AuthType
|
||||
import be.simplenotes.app.filters.JwtSource
|
||||
import org.http4k.core.Filter
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
val apiModule = module {
|
||||
single { ApiUserController(get(), get()) }
|
||||
single { ApiNoteController(get(), get()) }
|
||||
single<Filter>(named("apiAuthFilter")) {
|
||||
single(named("apiAuthFilter")) {
|
||||
AuthFilter(
|
||||
extractor = get(),
|
||||
authType = AuthType.Required,
|
||||
ctx = get(),
|
||||
source = JwtSource.Header,
|
||||
redirect = false
|
||||
)
|
||||
)()
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -2,9 +2,16 @@ package be.simplenotes.app.modules
|
||||
|
||||
import be.simplenotes.app.Config
|
||||
import org.koin.dsl.module
|
||||
import org.koin.dsl.onClose
|
||||
|
||||
val configModule = module {
|
||||
single { Config() }
|
||||
single { Config() } onClose {
|
||||
println("Unloaded config")
|
||||
println("Unloaded config")
|
||||
println("Unloaded config")
|
||||
println("Unloaded config")
|
||||
}
|
||||
|
||||
single { get<Config>().dataSourceConfig }
|
||||
single { get<Config>().jwtConfig }
|
||||
single { get<Config>().serverConfig }
|
||||
+4
-2
@@ -1,6 +1,9 @@
|
||||
package be.simplenotes.app.modules
|
||||
|
||||
import be.simplenotes.app.controllers.*
|
||||
import be.simplenotes.app.controllers.BaseController
|
||||
import be.simplenotes.app.controllers.NoteController
|
||||
import be.simplenotes.app.controllers.SettingsController
|
||||
import be.simplenotes.app.controllers.UserController
|
||||
import be.simplenotes.app.views.BaseView
|
||||
import be.simplenotes.app.views.NoteView
|
||||
import be.simplenotes.app.views.SettingView
|
||||
@@ -13,7 +16,6 @@ val userModule = module {
|
||||
}
|
||||
|
||||
val baseModule = module {
|
||||
single { HealthCheckController(get()) }
|
||||
single { BaseController(get()) }
|
||||
single { BaseView(get()) }
|
||||
}
|
||||
+8
-13
@@ -4,18 +4,16 @@ import be.simplenotes.app.Server
|
||||
import be.simplenotes.app.filters.AuthFilter
|
||||
import be.simplenotes.app.filters.AuthType
|
||||
import be.simplenotes.app.filters.ErrorFilter
|
||||
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.utils.StaticFileResolver
|
||||
import be.simplenotes.app.utils.StaticFileResolverImpl
|
||||
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.http4k.core.Filter
|
||||
import org.http4k.core.RequestContexts
|
||||
import org.http4k.routing.RoutingHttpHandler
|
||||
import org.http4k.server.ConnectorBuilder
|
||||
import org.http4k.server.Jetty
|
||||
import org.http4k.server.asServer
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.core.qualifier.qualifier
|
||||
@@ -45,19 +43,16 @@ val serverModule = module {
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
requiredAuth = get(AuthType.Required.qualifier),
|
||||
optionalAuth = get(AuthType.Optional.qualifier),
|
||||
errorFilter = get(named("ErrorFilter")),
|
||||
apiAuth = get(named("apiAuthFilter")),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get()
|
||||
)()
|
||||
}
|
||||
single { RequestContexts() }
|
||||
single<Filter>(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get()) }
|
||||
single<Filter>(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get()) }
|
||||
single { ErrorFilter(get()) }
|
||||
single { TransactionFilter(get()) }
|
||||
single(AuthType.Optional.qualifier) { AuthFilter(get(), AuthType.Optional, get())() }
|
||||
single(AuthType.Required.qualifier) { AuthFilter(get(), AuthType.Required, get())() }
|
||||
single(named("ErrorFilter")) { ErrorFilter(get())() }
|
||||
single { ErrorView(get()) }
|
||||
}
|
||||
+27
-35
@@ -2,15 +2,19 @@ package be.simplenotes.app.routes
|
||||
|
||||
import be.simplenotes.app.api.ApiNoteController
|
||||
import be.simplenotes.app.api.ApiUserController
|
||||
import be.simplenotes.app.controllers.*
|
||||
import be.simplenotes.app.filters.*
|
||||
import be.simplenotes.app.controllers.BaseController
|
||||
import be.simplenotes.app.controllers.NoteController
|
||||
import be.simplenotes.app.controllers.SettingsController
|
||||
import be.simplenotes.app.controllers.UserController
|
||||
import be.simplenotes.app.filters.ImmutableFilter
|
||||
import be.simplenotes.app.filters.SecurityFilter
|
||||
import be.simplenotes.app.filters.jwtPayload
|
||||
import be.simplenotes.domain.security.JwtPayload
|
||||
import org.http4k.core.*
|
||||
import org.http4k.core.Method.*
|
||||
import org.http4k.filter.ResponseFilters.GZip
|
||||
import org.http4k.filter.ResponseFilters
|
||||
import org.http4k.filter.ServerFilters.InitialiseRequestContext
|
||||
import org.http4k.routing.*
|
||||
import org.http4k.routing.ResourceLoader.Companion.Classpath
|
||||
|
||||
class Router(
|
||||
private val baseController: BaseController,
|
||||
@@ -19,26 +23,26 @@ class Router(
|
||||
private val settingsController: SettingsController,
|
||||
private val apiUserController: ApiUserController,
|
||||
private val apiNoteController: ApiNoteController,
|
||||
private val healthCheckController: HealthCheckController,
|
||||
private val requiredAuth: Filter,
|
||||
private val optionalAuth: Filter,
|
||||
private val errorFilter: Filter,
|
||||
private val apiAuth: Filter,
|
||||
private val errorFilter: ErrorFilter,
|
||||
private val transactionFilter: TransactionFilter,
|
||||
private val contexts: RequestContexts,
|
||||
) {
|
||||
operator fun invoke(): RoutingHttpHandler {
|
||||
|
||||
val basicRoutes =
|
||||
routes(
|
||||
"/health" bind GET to healthCheckController::healthCheck,
|
||||
ImmutableFilter.then(static(Classpath("/static"), "woff2" to ContentType("font/woff2")))
|
||||
val resourceLoader = ResourceLoader.Classpath(("/static"))
|
||||
val basicRoutes = routes(
|
||||
ImmutableFilter().then(static(resourceLoader, "woff2" to ContentType("font/woff2"))),
|
||||
)
|
||||
|
||||
val publicRoutes = routes(
|
||||
infix fun PathMethod.public(handler: PublicHandler) = this to { handler(it, it.jwtPayload(contexts)) }
|
||||
infix fun PathMethod.protected(handler: ProtectedHandler) = this to { handler(it, it.jwtPayload(contexts)!!) }
|
||||
|
||||
val publicRoutes: RoutingHttpHandler = routes(
|
||||
"/" bind GET public baseController::index,
|
||||
"/register" bind GET public userController::register,
|
||||
"/register" bind POST `public transactional` userController::register,
|
||||
"/register" bind POST public userController::register,
|
||||
"/login" bind GET public userController::login,
|
||||
"/login" bind POST public userController::login,
|
||||
"/logout" bind POST to userController::logout,
|
||||
@@ -47,18 +51,18 @@ class Router(
|
||||
|
||||
val protectedRoutes = routes(
|
||||
"/settings" bind GET protected settingsController::settings,
|
||||
"/settings" bind POST transactional settingsController::settings,
|
||||
"/settings" bind POST protected settingsController::settings,
|
||||
"/export" bind POST protected settingsController::export,
|
||||
"/notes" bind GET protected noteController::list,
|
||||
"/notes" bind POST protected noteController::search,
|
||||
"/notes/new" bind GET protected noteController::new,
|
||||
"/notes/new" bind POST transactional noteController::new,
|
||||
"/notes/new" bind POST protected noteController::new,
|
||||
"/notes/trash" bind GET protected noteController::trash,
|
||||
"/notes/{uuid}" bind GET protected noteController::note,
|
||||
"/notes/{uuid}" bind POST transactional noteController::note,
|
||||
"/notes/{uuid}" bind POST protected noteController::note,
|
||||
"/notes/{uuid}/edit" bind GET protected noteController::edit,
|
||||
"/notes/{uuid}/edit" bind POST transactional noteController::edit,
|
||||
"/notes/deleted/{uuid}" bind POST transactional noteController::deleted,
|
||||
"/notes/{uuid}/edit" bind POST protected noteController::edit,
|
||||
"/notes/deleted/{uuid}" bind POST protected noteController::deleted,
|
||||
)
|
||||
|
||||
val apiRoutes = routes(
|
||||
@@ -67,10 +71,10 @@ class Router(
|
||||
|
||||
val protectedApiRoutes = routes(
|
||||
"/api/notes" bind GET protected apiNoteController::notes,
|
||||
"/api/notes" bind POST transactional apiNoteController::createNote,
|
||||
"/api/notes/search" bind POST transactional apiNoteController::search,
|
||||
"/api/notes" bind POST protected apiNoteController::createNote,
|
||||
"/api/notes/search" bind POST protected apiNoteController::search,
|
||||
"/api/notes/{uuid}" bind GET protected apiNoteController::note,
|
||||
"/api/notes/{uuid}" bind PUT transactional apiNoteController::update,
|
||||
"/api/notes/{uuid}" bind PUT protected apiNoteController::update,
|
||||
)
|
||||
|
||||
val routes = routes(
|
||||
@@ -83,23 +87,11 @@ class Router(
|
||||
|
||||
val globalFilters = errorFilter
|
||||
.then(InitialiseRequestContext(contexts))
|
||||
.then(SecurityFilter)
|
||||
.then(GZip())
|
||||
.then(SecurityFilter())
|
||||
.then(ResponseFilters.GZip())
|
||||
|
||||
return globalFilters.then(routes)
|
||||
}
|
||||
|
||||
private inline infix fun PathMethod.public(crossinline handler: PublicHandler) =
|
||||
this to { handler(it, it.jwtPayload(contexts)) }
|
||||
|
||||
private inline infix fun PathMethod.protected(crossinline handler: ProtectedHandler) =
|
||||
this to { handler(it, it.jwtPayload(contexts)!!) }
|
||||
|
||||
private inline infix fun PathMethod.transactional(crossinline handler: ProtectedHandler) =
|
||||
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)!!) }
|
||||
|
||||
private inline infix fun PathMethod.`public transactional`(crossinline handler: PublicHandler) =
|
||||
this to transactionFilter.then { handler(it, it.jwtPayload(contexts)) }
|
||||
}
|
||||
|
||||
private typealias PublicHandler = (Request, JwtPayload?) -> Response
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package be.simplenotes.app.utils
|
||||
|
||||
import be.simplenotes.search.SearchTerms
|
||||
import be.simplenotes.domain.usecases.search.SearchTerms
|
||||
|
||||
private fun innerRegex(name: String) =
|
||||
"""$name:['"](.*?)['"]""".toRegex()
|
||||
@@ -0,0 +1,115 @@
|
||||
package be.simplenotes.app.views
|
||||
|
||||
import be.simplenotes.app.utils.StaticFileResolver
|
||||
import be.simplenotes.domain.security.JwtPayload
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.div
|
||||
import org.intellij.lang.annotations.Language
|
||||
|
||||
class BaseView(staticFileResolver: StaticFileResolver) : View(staticFileResolver) {
|
||||
fun renderHome(jwtPayload: JwtPayload?) = renderPage(
|
||||
title = "Home",
|
||||
description = "A fast and simple note taking website",
|
||||
jwtPayload = jwtPayload
|
||||
) {
|
||||
section("text-center my-2 p-2") {
|
||||
h1("text-5xl casual") {
|
||||
span("text-teal-300") { +"Simplenotes " }
|
||||
+"- access your notes anywhere"
|
||||
}
|
||||
}
|
||||
|
||||
div("container mx-auto flex flex-wrap justify-center content-center") {
|
||||
|
||||
unsafe {
|
||||
@Language("html")
|
||||
val html =
|
||||
"""
|
||||
<div aria-label="demo" class="md:order-1 order-2 flipped p-4 my-10 w-full md:w-1/2">
|
||||
<div class="flex justify-between mb-4">
|
||||
<h1 class="text-2xl underline">Notes</h1>
|
||||
<span>
|
||||
<span class="btn btn-teal pointer-events-none">Trash (3)</span>
|
||||
<span class="ml-2 btn btn-green pointer-events-none">New</span>
|
||||
</span>
|
||||
</div>
|
||||
<form class="md:space-x-2" id="search">
|
||||
<input aria-label="demo-search" name="search" disabled="" value="tag:"demo"">
|
||||
<span id="buttons">
|
||||
<button type="button" disabled="" class="btn btn-green pointer-events-none">search</button>
|
||||
<span class="btn btn-red pointer-events-none">clear</span>
|
||||
</span>
|
||||
</form>
|
||||
<div class="overflow-x-auto">
|
||||
<table id="notes">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="w-1/2">Title</th>
|
||||
<th scope="col" class="w-1/4">Updated</th>
|
||||
<th scope="col" class="w-1/4">Tags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="text-blue-200 font-semibold underline">Formula 1</span></td>
|
||||
<td class="text-center">moments ago</td>
|
||||
<td>
|
||||
<ul class="inline flex flex-wrap justify-center">
|
||||
<li class="mx-2 my-1"><span class="tag disabled">#demo</span ></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="text-blue-200 font-semibold underline">Syntax highlighting</span></td>
|
||||
<td class="text-center">2 hours ago</td>
|
||||
<td>
|
||||
<ul class="inline flex flex-wrap justify-center">
|
||||
<li class="mx-2 my-1"><span class="tag disabled">#features</span></li>
|
||||
<li class="mx-2 my-1"><span class="tag disabled">#demo</span></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="text-blue-200 font-semibold underline">report</span></td>
|
||||
<td class="text-center">5 days ago</td>
|
||||
<td>
|
||||
<ul class="inline flex flex-wrap justify-center">
|
||||
<li class="mx-2 my-1"><span class="tag disabled">#study</span></li>
|
||||
<li class="mx-2 my-1"><span class="tag disabled">#demo</span></li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
""".trimIndent()
|
||||
|
||||
+html
|
||||
}
|
||||
|
||||
welcome()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun DIV.welcome() {
|
||||
div("w-full my-auto md:w-1/2 md:order-2 order-1 text-center") {
|
||||
div("m-4 rounded-lg p-6") {
|
||||
p("text-teal-400") {
|
||||
h2("text-3xl text-teal-400 underline") { +"Features:" }
|
||||
ul("list-disc text-lg list-inside") {
|
||||
li { +"Markdown support" }
|
||||
li { +"Full text search" }
|
||||
li { +"Structured search" }
|
||||
li { +"Code highlighting" }
|
||||
li { +"Fast and lightweight" }
|
||||
li { +"No tracking" }
|
||||
li { +"Works without javascript" }
|
||||
li { +"Data export" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-22
@@ -2,8 +2,8 @@ package be.simplenotes.app.views
|
||||
|
||||
import be.simplenotes.app.utils.StaticFileResolver
|
||||
import be.simplenotes.app.views.components.*
|
||||
import be.simplenotes.types.PersistedNote
|
||||
import be.simplenotes.types.PersistedNoteMetadata
|
||||
import be.simplenotes.domain.model.PersistedNote
|
||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||
import be.simplenotes.domain.security.JwtPayload
|
||||
import io.konform.validation.ValidationError
|
||||
import kotlinx.html.*
|
||||
@@ -143,6 +143,7 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
|
||||
}
|
||||
if (!shared) {
|
||||
noteActionForm(note)
|
||||
publicPrivateForm(note)
|
||||
|
||||
if (note.public) {
|
||||
p("my-4") {
|
||||
@@ -165,29 +166,12 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
|
||||
}
|
||||
|
||||
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(
|
||||
href = "/notes/${note.uuid}/edit",
|
||||
classes = "btn btn-green"
|
||||
classes = "btn btn-teal"
|
||||
) { +"Edit" }
|
||||
span {
|
||||
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"
|
||||
}
|
||||
}
|
||||
form(method = FormMethod.post, classes = "inline") {
|
||||
button(
|
||||
type = ButtonType.submit,
|
||||
name = "delete",
|
||||
@@ -195,4 +179,23 @@ class NoteView(staticFileResolver: StaticFileResolver) : View(staticFileResolver
|
||||
) { +"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 ?"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-6
@@ -26,23 +26,22 @@ class SettingView(staticFileResolver: StaticFileResolver) : View(staticFileResol
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
classes = "inline btn btn-teal block",
|
||||
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 ->
|
||||
div {
|
||||
radioInput(name = "format") {
|
||||
id = format
|
||||
attributes["value"] = format
|
||||
if (format == "json") attributes["checked"] = ""
|
||||
else attributes["class"] = "ml-4"
|
||||
if(format == "json") attributes["checked"] = ""
|
||||
}
|
||||
label(classes = "ml-2") {
|
||||
attributes["for"] = format
|
||||
+1
-12
@@ -31,7 +31,7 @@ abstract class View(staticFileResolver: StaticFileResolver) {
|
||||
attributes["crossorigin"] = "anonymous"
|
||||
}
|
||||
link(rel = "stylesheet", href = styles)
|
||||
icons()
|
||||
link(rel = "shortcut icon", href = "/favicon.ico", type = "image/x-icon")
|
||||
scripts.forEach { src ->
|
||||
script(src = src) {}
|
||||
}
|
||||
@@ -42,15 +42,4 @@ abstract class View(staticFileResolver: StaticFileResolver) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun HEAD.icons() {
|
||||
link(rel = "apple-touch-icon", href = "/apple-touch-icon.png") { attributes["sizes"] = "180x180" }
|
||||
link(rel = "icon", href = "/favicon-32x32.png", type = "image/png") { attributes["sizes"] = "32x32" }
|
||||
link(rel = "icon", href = "/favicon-16x16.png", type = "image/png") { attributes["sizes"] = "16x16" }
|
||||
link(rel = "manifest", href = "/site.webmanifest")
|
||||
link(rel = "mask-icon", href = "/safari-pinned-tab.svg") { attributes["color"] = "#2c7a7b" }
|
||||
meta(name = "msapplication-TileColor", content = "#00aba9")
|
||||
meta(name = "theme-color", content = "#2c7a7b")
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package be.simplenotes.app.views.components
|
||||
|
||||
import be.simplenotes.app.utils.toTimeAgo
|
||||
import be.simplenotes.types.PersistedNoteMetadata
|
||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.ButtonType.submit
|
||||
import kotlinx.html.FormMethod.post
|
||||
@@ -25,8 +25,8 @@ fun FlowContent.deletedNoteTable(notes: List<PersistedNoteMetadata>) = div("over
|
||||
td("text-center") { +updatedAt.toTimeAgo() }
|
||||
td { tags(tags) }
|
||||
td("text-center") {
|
||||
form(method = post, action = "/notes/deleted/$uuid") {
|
||||
button(classes = "btn btn-red mb-2", type = submit, name = "delete") {
|
||||
form(classes = "inline", method = post, action = "/notes/deleted/$uuid") {
|
||||
button(classes = "btn btn-red", type = submit, name = "delete") {
|
||||
+"Delete permanently"
|
||||
}
|
||||
button(classes = "ml-2 btn btn-green", type = submit, name = "restore") {
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package be.simplenotes.app.views.components
|
||||
|
||||
import be.simplenotes.app.utils.toTimeAgo
|
||||
import be.simplenotes.types.PersistedNoteMetadata
|
||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.ThScope.col
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1 @@
|
||||
package be.simplenotes.app
|
||||
+3
-3
@@ -3,7 +3,7 @@ package be.simplenotes.app.filters
|
||||
import be.simplenotes.domain.security.JwtPayload
|
||||
import be.simplenotes.domain.security.JwtPayloadExtractor
|
||||
import be.simplenotes.domain.security.SimpleJwt
|
||||
import be.simplenotes.config.JwtConfig
|
||||
import be.simplenotes.shared.config.JwtConfig
|
||||
import com.natpryce.hamkrest.assertion.assertThat
|
||||
import org.http4k.core.*
|
||||
import org.http4k.core.Method.GET
|
||||
@@ -27,8 +27,8 @@ internal class AuthFilterTest {
|
||||
private val simpleJwt = SimpleJwt(jwtConfig)
|
||||
private val extractor = JwtPayloadExtractor(simpleJwt)
|
||||
private val ctx = RequestContexts()
|
||||
private val requiredAuth = AuthFilter(extractor, AuthType.Required, ctx)
|
||||
private val optionalAuth = AuthFilter(extractor, AuthType.Optional, ctx)
|
||||
private val requiredAuth = AuthFilter(extractor, AuthType.Required, ctx)()
|
||||
private val optionalAuth = AuthFilter(extractor, AuthType.Optional, ctx)()
|
||||
|
||||
private val echoJwtPayloadHandler = { request: Request -> Response(OK).body(request.jwtPayload(ctx).toString()) }
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
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.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
+1
-5
@@ -11,11 +11,7 @@ simplenotes.be {
|
||||
import strict-transport
|
||||
header -Server
|
||||
|
||||
reverse_proxy http://localhost:8080 {
|
||||
health_path /health
|
||||
health_interval 5s
|
||||
health_timeout 200ms
|
||||
}
|
||||
reverse_proxy http://localhost:8080
|
||||
}
|
||||
|
||||
dev.simplenotes.be {
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
"name": "css",
|
||||
"version": "1.0.0",
|
||||
"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-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": "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=../app/src/main/resources/css-manifest.json postcss build src/styles.pcss --output ../app/src/main/resources/static/styles.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^9.8.6",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
purge: {
|
||||
content: [
|
||||
'../simplenotes-app/src/main/kotlin/be/simplenotes/app/views/**/*.kt'
|
||||
'../app/src/main/kotlin/views/**/*.kt'
|
||||
]
|
||||
},
|
||||
theme: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
rm simplenotes-app/src/main/resources/css-manifest.json
|
||||
rm simplenotes-app/src/main/resources/static/styles*
|
||||
rm app/src/main/resources/css-manifest.json
|
||||
rm app/src/main/resources/static/styles*
|
||||
|
||||
yarn --cwd css run css-purge \
|
||||
&& docker build -t hubv/simplenotes:latest . \
|
||||
|
||||
+2
-10
@@ -19,10 +19,8 @@ services:
|
||||
volumes:
|
||||
- notes-db-volume:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: "mysql --protocol=tcp -u simplenotes -p$MYSQL_PASSWORD -e 'show databases'"
|
||||
interval: 5s
|
||||
timeout: 1s
|
||||
start_period: 2s
|
||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
|
||||
simplenotes:
|
||||
@@ -41,12 +39,6 @@ services:
|
||||
# - PASSWORD
|
||||
ports:
|
||||
- 127.0.0.1:8080:8080
|
||||
healthcheck:
|
||||
test: "curl --fail -s http://localhost:8080/health"
|
||||
interval: 5s
|
||||
timeout: 1s
|
||||
start_period: 2s
|
||||
retries: 3
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -1,55 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>simplenotes-parent</artifactId>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>simplenotes-domain</artifactId>
|
||||
<artifactId>domain</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>simplenotes-config</artifactId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>simplenotes-test-resources</artifactId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.koin</groupId>
|
||||
<artifactId>koin-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.arrow-kt</groupId>
|
||||
<artifactId>arrow-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.natpryce</groupId>
|
||||
<artifactId>hamkrest</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.mockk</groupId>
|
||||
<artifactId>mockk</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.konform</groupId>
|
||||
<artifactId>konform-jvm</artifactId>
|
||||
@@ -85,29 +57,15 @@
|
||||
<artifactId>owasp-java-html-sanitizer</artifactId>
|
||||
<version>20200713.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-serialization-runtime</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.20</version>
|
||||
</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>
|
||||
|
||||
</project>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package be.simplenotes.types
|
||||
package be.simplenotes.domain.model
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package be.simplenotes.types
|
||||
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)
|
||||
+1
-1
@@ -2,7 +2,7 @@ package be.simplenotes.domain.security
|
||||
|
||||
import org.owasp.html.HtmlPolicyBuilder
|
||||
|
||||
internal object HtmlSanitizer {
|
||||
object HtmlSanitizer {
|
||||
private val htmlPolicy = HtmlPolicyBuilder()
|
||||
.allowElements("a")
|
||||
.allowCommonBlockElements()
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package be.simplenotes.domain.security
|
||||
|
||||
import be.simplenotes.types.PersistedUser
|
||||
import be.simplenotes.domain.model.PersistedUser
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException
|
||||
|
||||
data class JwtPayload(val userId: Int, val username: String) {
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package be.simplenotes.domain.security
|
||||
|
||||
import be.simplenotes.config.JwtConfig
|
||||
import be.simplenotes.shared.config.JwtConfig
|
||||
import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.JWTVerifier
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
+7
-7
@@ -2,16 +2,16 @@ package be.simplenotes.domain.usecases
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.extensions.fx
|
||||
import be.simplenotes.types.Note
|
||||
import be.simplenotes.types.PersistedNote
|
||||
import be.simplenotes.types.PersistedNoteMetadata
|
||||
import be.simplenotes.domain.model.Note
|
||||
import be.simplenotes.domain.model.PersistedNote
|
||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||
import be.simplenotes.domain.security.HtmlSanitizer
|
||||
import be.simplenotes.domain.usecases.markdown.MarkdownConverter
|
||||
import be.simplenotes.domain.usecases.markdown.MarkdownParsingError
|
||||
import be.simplenotes.persistance.repositories.NoteRepository
|
||||
import be.simplenotes.persistance.repositories.UserRepository
|
||||
import be.simplenotes.search.NoteSearcher
|
||||
import be.simplenotes.search.SearchTerms
|
||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.domain.usecases.search.NoteSearcher
|
||||
import be.simplenotes.domain.usecases.search.SearchTerms
|
||||
import java.util.*
|
||||
|
||||
class NoteService(
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package be.simplenotes.domain.usecases.export
|
||||
|
||||
import be.simplenotes.types.ExportedNote
|
||||
import be.simplenotes.persistance.repositories.NoteRepository
|
||||
import be.simplenotes.domain.model.ExportedNote
|
||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
||||
+1
-1
@@ -4,7 +4,7 @@ import arrow.core.Either
|
||||
import arrow.core.extensions.fx
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import be.simplenotes.types.NoteMetadata
|
||||
import be.simplenotes.domain.model.NoteMetadata
|
||||
import be.simplenotes.domain.validation.NoteValidations
|
||||
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
|
||||
import com.vladsch.flexmark.html.HtmlRenderer
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
package be.simplenotes.persistance.repositories
|
||||
package be.simplenotes.domain.usecases.repositories
|
||||
|
||||
import be.simplenotes.types.ExportedNote
|
||||
import be.simplenotes.types.Note
|
||||
import be.simplenotes.types.PersistedNote
|
||||
import be.simplenotes.types.PersistedNoteMetadata
|
||||
import be.simplenotes.domain.model.ExportedNote
|
||||
import be.simplenotes.domain.model.Note
|
||||
import be.simplenotes.domain.model.PersistedNote
|
||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||
import java.util.*
|
||||
|
||||
interface NoteRepository {
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package be.simplenotes.persistance.repositories
|
||||
package be.simplenotes.domain.usecases.repositories
|
||||
|
||||
import be.simplenotes.types.PersistedUser
|
||||
import be.simplenotes.types.User
|
||||
import be.simplenotes.domain.model.PersistedUser
|
||||
import be.simplenotes.domain.model.User
|
||||
|
||||
interface UserRepository {
|
||||
fun create(user: User): PersistedUser?
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package be.simplenotes.search
|
||||
package be.simplenotes.domain.usecases.search
|
||||
|
||||
import be.simplenotes.types.PersistedNote
|
||||
import be.simplenotes.types.PersistedNoteMetadata
|
||||
import be.simplenotes.domain.model.PersistedNote
|
||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||
import java.util.*
|
||||
|
||||
data class SearchTerms(val title: String?, val tag: String?, val content: String?, val all: String?)
|
||||
+2
-2
@@ -4,9 +4,9 @@ import arrow.core.Either
|
||||
import arrow.core.extensions.fx
|
||||
import arrow.core.rightIfNotNull
|
||||
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.search.NoteSearcher
|
||||
|
||||
internal class DeleteUseCaseImpl(
|
||||
private val userRepository: UserRepository,
|
||||
+1
-1
@@ -7,8 +7,8 @@ import arrow.core.rightIfNotNull
|
||||
import be.simplenotes.domain.security.JwtPayload
|
||||
import be.simplenotes.domain.security.PasswordHash
|
||||
import be.simplenotes.domain.security.SimpleJwt
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.domain.validation.UserValidations
|
||||
import be.simplenotes.persistance.repositories.UserRepository
|
||||
|
||||
internal class LoginUseCaseImpl(
|
||||
private val userRepository: UserRepository,
|
||||
+2
-2
@@ -3,10 +3,10 @@ package be.simplenotes.domain.usecases.users.register
|
||||
import arrow.core.Either
|
||||
import arrow.core.filterOrElse
|
||||
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.usecases.repositories.UserRepository
|
||||
import be.simplenotes.domain.validation.UserValidations
|
||||
import be.simplenotes.persistance.repositories.UserRepository
|
||||
|
||||
internal class RegisterUseCaseImpl(
|
||||
private val userRepository: UserRepository,
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package be.simplenotes.domain.usecases.users.register
|
||||
|
||||
import arrow.core.Either
|
||||
import be.simplenotes.types.PersistedUser
|
||||
import be.simplenotes.domain.model.PersistedUser
|
||||
import be.simplenotes.domain.usecases.users.login.LoginForm
|
||||
import io.konform.validation.ValidationErrors
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package be.simplenotes.domain.validation
|
||||
|
||||
import arrow.core.*
|
||||
import be.simplenotes.types.NoteMetadata
|
||||
import be.simplenotes.domain.model.NoteMetadata
|
||||
import be.simplenotes.domain.usecases.markdown.ValidationError
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxItems
|
||||
+1
-1
@@ -3,7 +3,7 @@ package be.simplenotes.domain.validation
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
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.DeleteForm
|
||||
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
-1
@@ -1,7 +1,7 @@
|
||||
package be.simplenotes.domain.security
|
||||
|
||||
import be.simplenotes.domain.usecases.users.login.Token
|
||||
import be.simplenotes.config.JwtConfig
|
||||
import be.simplenotes.shared.config.JwtConfig
|
||||
import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import com.natpryce.hamkrest.absent
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
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.SimpleJwt
|
||||
import be.simplenotes.persistance.repositories.UserRepository
|
||||
import be.simplenotes.config.JwtConfig
|
||||
import be.simplenotes.domain.testutils.isLeftOfType
|
||||
import be.simplenotes.domain.testutils.isRight
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.shared.config.JwtConfig
|
||||
import be.simplenotes.shared.testutils.assertions.isLeftOfType
|
||||
import be.simplenotes.shared.testutils.assertions.isRight
|
||||
import com.natpryce.hamkrest.assertion.assertThat
|
||||
import io.mockk.*
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
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.testutils.isLeftOfType
|
||||
import be.simplenotes.domain.testutils.isRight
|
||||
import be.simplenotes.persistance.repositories.UserRepository
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.shared.testutils.assertions.isLeftOfType
|
||||
import be.simplenotes.shared.testutils.assertions.isRight
|
||||
import com.natpryce.hamkrest.assertion.assertThat
|
||||
import com.natpryce.hamkrest.equalTo
|
||||
import io.mockk.*
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
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.LoginForm
|
||||
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 org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
@@ -1,54 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<project>
|
||||
<parent>
|
||||
<artifactId>simplenotes-parent</artifactId>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>simplenotes-persistance</artifactId>
|
||||
<artifactId>persistance</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>simplenotes-types</artifactId>
|
||||
<artifactId>domain</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>simplenotes-config</artifactId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>simplenotes-test-resources</artifactId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.koin</groupId>
|
||||
<artifactId>koin-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
@@ -64,20 +42,20 @@
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>6.5.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>3.4.3</version>
|
||||
<version>3.4.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>me.liuwj.ktorm</groupId>
|
||||
<artifactId>ktorm-core</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.liuwj.ktorm</groupId>
|
||||
<artifactId>ktorm-support-mysql</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package be.simplenotes.persistance
|
||||
|
||||
import be.simplenotes.shared.config.DataSourceConfig
|
||||
import org.flywaydb.core.Flyway
|
||||
import javax.sql.DataSource
|
||||
|
||||
internal class DbMigrationsImpl(
|
||||
private val dataSource: DataSource,
|
||||
private val dataSourceConfig: DataSourceConfig
|
||||
) : DbMigrations {
|
||||
override fun migrate() {
|
||||
|
||||
val migrationDir = when {
|
||||
dataSourceConfig.jdbcUrl.contains("mariadb") -> "db/migration/mariadb"
|
||||
else -> "db/migration/other"
|
||||
}
|
||||
|
||||
Flyway.configure()
|
||||
.dataSource(dataSource)
|
||||
.locations(migrationDir)
|
||||
.load()
|
||||
.migrate()
|
||||
}
|
||||
}
|
||||
+9
-12
@@ -1,14 +1,10 @@
|
||||
package be.simplenotes.persistance
|
||||
|
||||
import be.simplenotes.config.DataSourceConfig
|
||||
import be.simplenotes.persistance.converters.NoteConverter
|
||||
import be.simplenotes.persistance.converters.NoteConverterImpl
|
||||
import be.simplenotes.persistance.converters.UserConverter
|
||||
import be.simplenotes.persistance.converters.UserConverterImpl
|
||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.persistance.notes.NoteRepositoryImpl
|
||||
import be.simplenotes.persistance.repositories.NoteRepository
|
||||
import be.simplenotes.persistance.repositories.UserRepository
|
||||
import be.simplenotes.persistance.users.UserRepositoryImpl
|
||||
import be.simplenotes.shared.config.DataSourceConfig
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import me.liuwj.ktorm.database.Database
|
||||
@@ -17,6 +13,10 @@ import org.koin.dsl.module
|
||||
import org.koin.dsl.onClose
|
||||
import javax.sql.DataSource
|
||||
|
||||
interface DbMigrations {
|
||||
fun migrate()
|
||||
}
|
||||
|
||||
private fun hikariDataSource(conf: DataSourceConfig): HikariDataSource {
|
||||
val hikariConfig = HikariConfig().also {
|
||||
it.jdbcUrl = conf.jdbcUrl
|
||||
@@ -34,14 +34,11 @@ val migrationModule = module {
|
||||
}
|
||||
|
||||
val persistanceModule = module {
|
||||
single<NoteConverter> { NoteConverterImpl() }
|
||||
single<UserConverter> { UserConverterImpl() }
|
||||
single<UserRepository> { UserRepositoryImpl(get(), get()) }
|
||||
single<NoteRepository> { NoteRepositoryImpl(get(), get()) }
|
||||
single<UserRepository> { UserRepositoryImpl(get()) }
|
||||
single<NoteRepository> { NoteRepositoryImpl(get()) }
|
||||
single { hikariDataSource(get()) } bind DataSource::class onClose { it?.close() }
|
||||
single {
|
||||
get<DbMigrations>().migrate()
|
||||
Database.connect(get<DataSource>())
|
||||
}
|
||||
single<DbHealthCheck> { DbHealthCheckImpl(get(), get()) }
|
||||
}
|
||||
+41
-19
@@ -1,11 +1,7 @@
|
||||
package be.simplenotes.persistance.notes
|
||||
|
||||
import be.simplenotes.types.ExportedNote
|
||||
import be.simplenotes.types.Note
|
||||
import be.simplenotes.types.PersistedNote
|
||||
import be.simplenotes.types.PersistedNoteMetadata
|
||||
import be.simplenotes.persistance.converters.NoteConverter
|
||||
import be.simplenotes.persistance.repositories.NoteRepository
|
||||
import be.simplenotes.domain.model.*
|
||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import me.liuwj.ktorm.dsl.*
|
||||
import me.liuwj.ktorm.entity.*
|
||||
@@ -13,7 +9,7 @@ import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
internal class NoteRepositoryImpl(private val db: Database, private val converter: NoteConverter) : NoteRepository {
|
||||
internal class NoteRepositoryImpl(private val db: Database) : NoteRepository {
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
override fun findAll(
|
||||
@@ -21,7 +17,7 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
|
||||
limit: Int,
|
||||
offset: Int,
|
||||
tag: String?,
|
||||
deleted: Boolean,
|
||||
deleted: Boolean
|
||||
): List<PersistedNoteMetadata> {
|
||||
require(limit > 0) { "limit should be positive" }
|
||||
require(offset >= 0) { "offset should not be negative" }
|
||||
@@ -50,7 +46,7 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
|
||||
|
||||
return notes.map { note ->
|
||||
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
||||
converter.toPersistedNoteMetadata(note, tags)
|
||||
note.toPersistedMetadata(tags)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +56,10 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
|
||||
|
||||
override fun create(userId: Int, note: Note): PersistedNote {
|
||||
val uuid = UUID.randomUUID()
|
||||
val entity = converter.toEntity(note, uuid, userId, LocalDateTime.now())
|
||||
val entity = note.toEntity(uuid, userId).apply {
|
||||
this.updatedAt = LocalDateTime.now()
|
||||
}
|
||||
db.useTransaction {
|
||||
db.notes.add(entity)
|
||||
db.batchInsert(Tags) {
|
||||
note.meta.tags.forEach { tagName ->
|
||||
@@ -70,7 +69,9 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
|
||||
}
|
||||
}
|
||||
}
|
||||
return converter.toPersistedNote(entity, note.meta.tags)
|
||||
}
|
||||
|
||||
return entity.toPersistedNote(note.meta.tags)
|
||||
}
|
||||
|
||||
override fun find(userId: Int, uuid: UUID): PersistedNote? {
|
||||
@@ -85,10 +86,12 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
|
||||
.where { Tags.noteUuid eq uuid }
|
||||
.map { it[Tags.name]!! }
|
||||
|
||||
return converter.toPersistedNote(note, tags)
|
||||
return note.toPersistedNote(tags)
|
||||
}
|
||||
|
||||
override fun update(userId: Int, uuid: UUID, note: Note): PersistedNote? {
|
||||
db.useTransaction {
|
||||
|
||||
val now = LocalDateTime.now()
|
||||
val count = db.update(Notes) {
|
||||
it.title to note.meta.title
|
||||
@@ -113,26 +116,39 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
|
||||
}
|
||||
}
|
||||
|
||||
return converter.toPersistedNote(note, now, uuid)
|
||||
return PersistedNote(
|
||||
meta = note.meta,
|
||||
markdown = note.markdown,
|
||||
html = note.html,
|
||||
updatedAt = now,
|
||||
uuid = uuid,
|
||||
public = false, // TODO
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(userId: Int, uuid: UUID, permanent: Boolean): Boolean {
|
||||
return if (!permanent) {
|
||||
db.useTransaction {
|
||||
db.update(Notes) {
|
||||
it.deleted to true
|
||||
it.updatedAt to LocalDateTime.now()
|
||||
where { it.userId eq userId and (it.uuid eq uuid) }
|
||||
}
|
||||
} == 1
|
||||
} else
|
||||
} else db.useTransaction {
|
||||
db.delete(Notes) { it.uuid eq uuid and (it.userId eq userId) } == 1
|
||||
}
|
||||
}
|
||||
|
||||
override fun restore(userId: Int, uuid: UUID): Boolean {
|
||||
return db.update(Notes) {
|
||||
return db.useTransaction {
|
||||
db.update(Notes) {
|
||||
it.deleted to false
|
||||
where { (it.userId eq userId) and (it.uuid eq uuid) }
|
||||
} == 1
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTags(userId: Int): List<String> =
|
||||
db.from(Tags)
|
||||
@@ -159,8 +175,14 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
|
||||
val tagsByUuid = notes.tagsByUuid()
|
||||
|
||||
return notes.map { note ->
|
||||
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
||||
converter.toExportedNote(note, tags)
|
||||
ExportedNote(
|
||||
title = note.title,
|
||||
tags = tagsByUuid[note.uuid] ?: emptyList(),
|
||||
markdown = note.markdown,
|
||||
html = note.html,
|
||||
updatedAt = note.updatedAt,
|
||||
trash = note.deleted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +196,7 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
|
||||
|
||||
return notes.map { note ->
|
||||
val tags = tagsByUuid[note.uuid] ?: emptyList()
|
||||
converter.toPersistedNote(note, tags)
|
||||
note.toPersistedNote(tags)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +223,7 @@ internal class NoteRepositoryImpl(private val db: Database, private val converte
|
||||
.where { Tags.noteUuid eq uuid }
|
||||
.map { it[Tags.name]!! }
|
||||
|
||||
return converter.toPersistedNote(note, tags)
|
||||
return note.toPersistedNote(tags)
|
||||
}
|
||||
|
||||
private fun List<NoteEntity>.tagsByUuid(): Map<UUID, List<String>> {
|
||||
+24
-3
@@ -2,12 +2,15 @@
|
||||
|
||||
package be.simplenotes.persistance.notes
|
||||
|
||||
import be.simplenotes.domain.model.Note
|
||||
import be.simplenotes.domain.model.NoteMetadata
|
||||
import be.simplenotes.domain.model.PersistedNote
|
||||
import be.simplenotes.domain.model.PersistedNoteMetadata
|
||||
import be.simplenotes.persistance.extensions.uuidBinary
|
||||
import be.simplenotes.persistance.users.UserEntity
|
||||
import be.simplenotes.persistance.users.Users
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import me.liuwj.ktorm.entity.Entity
|
||||
import me.liuwj.ktorm.entity.sequenceOf
|
||||
import me.liuwj.ktorm.database.*
|
||||
import me.liuwj.ktorm.entity.*
|
||||
import me.liuwj.ktorm.schema.*
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
@@ -43,3 +46,21 @@ internal interface NoteEntity : Entity<NoteEntity> {
|
||||
|
||||
var user: UserEntity
|
||||
}
|
||||
|
||||
internal fun NoteEntity.toPersistedMetadata(tags: List<String>) = PersistedNoteMetadata(title, tags, updatedAt, uuid)
|
||||
|
||||
internal fun NoteEntity.toPersistedNote(tags: List<String>) =
|
||||
PersistedNote(NoteMetadata(title, tags), markdown, html, updatedAt, uuid, public)
|
||||
|
||||
internal fun Note.toEntity(uuid: UUID, userId: Int): NoteEntity {
|
||||
val note = this
|
||||
return NoteEntity {
|
||||
this.title = note.meta.title
|
||||
this.markdown = note.markdown
|
||||
this.html = note.html
|
||||
this.uuid = uuid
|
||||
this.deleted = false
|
||||
this.public = false
|
||||
this.user["id"] = userId
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package be.simplenotes.persistance.users
|
||||
|
||||
import be.simplenotes.domain.model.PersistedUser
|
||||
import be.simplenotes.domain.model.User
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import me.liuwj.ktorm.database.*
|
||||
import me.liuwj.ktorm.dsl.*
|
||||
import me.liuwj.ktorm.entity.*
|
||||
import java.sql.SQLIntegrityConstraintViolationException
|
||||
|
||||
internal class UserRepositoryImpl(private val db: Database) : UserRepository {
|
||||
override fun create(user: User): PersistedUser? {
|
||||
return try {
|
||||
db.useTransaction {
|
||||
val id = db.insertAndGenerateKey(Users) {
|
||||
it.username to user.username
|
||||
it.password to user.password
|
||||
} as Int
|
||||
PersistedUser(user.username, user.password, id)
|
||||
}
|
||||
} catch (e: SQLIntegrityConstraintViolationException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun find(username: String) = db.users.find { it.username eq username }?.toPersistedUser()
|
||||
override fun find(id: Int) = db.users.find { it.id eq id }?.toPersistedUser()
|
||||
override fun exists(username: String) = db.users.any { it.username eq username }
|
||||
override fun exists(id: Int) = db.users.any { it.id eq id }
|
||||
override fun delete(id: Int) = db.useTransaction { db.delete(Users) { it.id eq id } == 1 }
|
||||
override fun findAll() = db.from(Users).select(Users.id).map { it[Users.id]!! }
|
||||
}
|
||||
+7
-7
@@ -1,11 +1,9 @@
|
||||
package be.simplenotes.persistance.users
|
||||
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import me.liuwj.ktorm.entity.Entity
|
||||
import me.liuwj.ktorm.entity.sequenceOf
|
||||
import me.liuwj.ktorm.schema.Table
|
||||
import me.liuwj.ktorm.schema.int
|
||||
import me.liuwj.ktorm.schema.varchar
|
||||
import be.simplenotes.domain.model.PersistedUser
|
||||
import me.liuwj.ktorm.database.*
|
||||
import me.liuwj.ktorm.entity.*
|
||||
import me.liuwj.ktorm.schema.*
|
||||
|
||||
internal open class Users(alias: String?) : Table<UserEntity>("Users", alias) {
|
||||
companion object : Users(null)
|
||||
@@ -20,9 +18,11 @@ internal open class Users(alias: String?) : Table<UserEntity>("Users", alias) {
|
||||
internal interface UserEntity : Entity<UserEntity> {
|
||||
companion object : Entity.Factory<UserEntity>()
|
||||
|
||||
var id: Int
|
||||
val id: Int
|
||||
var username: String
|
||||
var password: String
|
||||
}
|
||||
|
||||
internal fun UserEntity.toPersistedUser() = PersistedUser(username, password, id)
|
||||
|
||||
internal val Database.users get() = this.sequenceOf(Users, withReferences = false)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user