Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6a7af840a | |||
| 568a2c6831 | |||
| c39a20cf96 | |||
| bf56314473 | |||
| 11caff1634 | |||
| b1478fd154 | |||
| dd174a6327 | |||
| 941380ad16 | |||
| 4f395d254d | |||
| 6a43acfd46 | |||
| 78b84dc62a | |||
| 1120bc9350 | |||
| a37254452b | |||
| cd9fdd28e8 | |||
| c3fc6a4e88 | |||
| cb58a4fbe0 | |||
| 64059984d3 | |||
| fdc8d34f82 | |||
| 95ec674eb8 | |||
| ea7be84ec3 | |||
| c709f2b44d | |||
| 7995a0b3e0 | |||
| bfd562bc60 | |||
| 4fb85a52e4 | |||
| e64352f54c | |||
| c2c03e415e | |||
| 0260bea951 | |||
| 8b8dbd6fe5 | |||
| 536c6e7b79 | |||
| e5a2b8993f | |||
| 38750a588c | |||
| ee026ec829 | |||
| 29b024d360 | |||
| cd12d1561a | |||
| c2eaf3d0cc | |||
| 4c9ac8944e | |||
| 4ff97044f0 | |||
| ead1932d48 | |||
| 4a7dcec363 | |||
| cb76a3253d |
@@ -1,10 +1,6 @@
|
||||
## can be generated with `openssl rand -base64 32`
|
||||
JWT_SECRET=
|
||||
#
|
||||
## can be generated with `openssl rand -base64 32`
|
||||
# mariadb
|
||||
MYSQL_ROOT_PASSWORD=
|
||||
#
|
||||
## can be generated with `openssl rand -base64 32`
|
||||
MYSQL_PASSWORD=
|
||||
# password should be the same as mysql_password
|
||||
PASSWORD=
|
||||
# simplenotes
|
||||
DB_PASSWORD=
|
||||
JWT_SECRET=
|
||||
|
||||
+5
-97
@@ -1,24 +1,6 @@
|
||||
# Java
|
||||
.mtj.tmp/
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
*.nar
|
||||
hs_err_pid*
|
||||
|
||||
# Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
pom.xml.bak
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
# Gradle
|
||||
build/
|
||||
.gradle
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
@@ -28,11 +10,8 @@ out/
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# Vue
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
@@ -49,85 +28,13 @@ pids
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
*.private.env.json
|
||||
|
||||
# Certificates
|
||||
data/
|
||||
letsencrypt/
|
||||
|
||||
# generated resources
|
||||
app/src/main/resources/css-manifest.json
|
||||
app/src/main/resources/static/styles*
|
||||
|
||||
# h2 db
|
||||
*.db
|
||||
|
||||
@@ -136,3 +43,4 @@ app/src/main/resources/static/styles*
|
||||
|
||||
# python
|
||||
__pycache__
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=14.0.2-open
|
||||
gradle=6.7
|
||||
kotlin=1.4.10
|
||||
+15
-24
@@ -1,25 +1,3 @@
|
||||
FROM maven:3.6.3-jdk-14 as builder
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
# Cache dependencies
|
||||
COPY 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 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
|
||||
|
||||
FROM openjdk:14-alpine as jdkbuilder
|
||||
|
||||
RUN apk add --no-cache binutils
|
||||
@@ -42,8 +20,21 @@ RUN chown -R $APPLICATION_USER /app
|
||||
|
||||
USER $APPLICATION_USER
|
||||
|
||||
COPY --from=builder /tmp/app/target/app-*.jar /app/app.jar
|
||||
COPY --from=jdkbuilder /myjdk /myjdk
|
||||
COPY simplenotes-app/build/libs/simplenotes-app-with-dependencies*.jar /app/simplenotes.jar
|
||||
WORKDIR /app
|
||||
|
||||
CMD ["/myjdk/bin/java", "-server", "-XX:+UnlockExperimentalVMOptions", "-Xms64m", "-Xmx256m", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "app.jar"]
|
||||
CMD [ \
|
||||
"/myjdk/bin/java", \
|
||||
"--add-opens", \
|
||||
"java.base/java.nio=ALL-UNNAMED", \
|
||||
"-server", \
|
||||
"-XX:+UnlockExperimentalVMOptions", \
|
||||
"-Xms64m", \
|
||||
"-Xmx256m", \
|
||||
"-XX:+UseG1GC", \
|
||||
"-XX:MaxGCPauseMillis=100", \
|
||||
"-XX:+UseStringDeduplication", \
|
||||
"-jar", \
|
||||
"simplenotes.jar" \
|
||||
]
|
||||
|
||||
@@ -15,5 +15,4 @@
|
||||
## Configuration
|
||||
|
||||
The app is configured with environments variables.
|
||||
If no match is found within the env, a default value is read from a properties file in /app/src/main/resources/application.properties.
|
||||
Don't use the default values for secrets ! Every value inside *.env.dist* should be changed.
|
||||
If no match is found within the env, a default value is read from a yaml file in simplenotes-app/src/main/resources/application.yaml.
|
||||
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>app</artifactId>
|
||||
|
||||
<properties>
|
||||
<http4k.version>3.268.0</http4k.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>persistance</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>search</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>domain</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.http4k</groupId>
|
||||
<artifactId>http4k-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.http4k</groupId>
|
||||
<artifactId>http4k-server-jetty</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>javax-websocket-server-impl</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-html-jvm</artifactId>
|
||||
<version>0.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-serialization-json-jvm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ocpsoft.prettytime</groupId>
|
||||
<artifactId>prettytime</artifactId>
|
||||
<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>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.http4k</groupId>
|
||||
<artifactId>http4k-testing-hamkrest</artifactId>
|
||||
<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>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>be.simplenotes.app.SimpleNotesKt</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>com.h2database:h2</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.mariadb.jdbc:mariadb-java-client</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.jetbrains.kotlin:kotlin-reflect</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.eclipse.jetty:*</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.apache.lucene:*</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>org.ocpsoft.prettytime:prettytime</artifact>
|
||||
<includes>
|
||||
<include>**</include>
|
||||
</includes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/maven/**</exclude>
|
||||
<exclude>META-INF/proguard/**</exclude>
|
||||
<exclude>META-INF/*.kotlin_module</exclude>
|
||||
<exclude>META-INF/DEPENDENCIES*</exclude>
|
||||
<exclude>META-INF/NOTICE*</exclude>
|
||||
<exclude>META-INF/LICENSE*</exclude>
|
||||
<exclude>LICENSE*</exclude>
|
||||
<exclude>META-INF/README*</exclude>
|
||||
<exclude>META-INF/native-image/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,48 +0,0 @@
|
||||
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.concurrent.TimeUnit
|
||||
|
||||
class Config {
|
||||
//region Config loading
|
||||
private val properties: Properties = javaClass
|
||||
.getResource("/application.properties")
|
||||
.openStream()
|
||||
.use {
|
||||
Properties().apply { load(it) }
|
||||
}
|
||||
|
||||
private val env = System.getenv()
|
||||
|
||||
private fun value(key: String): String =
|
||||
env[key.toUpperCase().replace(".", "_")]
|
||||
?: properties.getProperty(key)
|
||||
?: error("Missing config key $key")
|
||||
//endregion
|
||||
|
||||
val jwtConfig
|
||||
get() = JwtConfig(
|
||||
secret = value("jwt.secret"),
|
||||
validity = value("jwt.validity").toLong(),
|
||||
timeUnit = TimeUnit.HOURS,
|
||||
)
|
||||
|
||||
val dataSourceConfig
|
||||
get() = DataSourceConfig(
|
||||
jdbcUrl = value("jdbcUrl"),
|
||||
driverClassName = value("driverClassName"),
|
||||
username = value("username"),
|
||||
password = value("password"),
|
||||
maximumPoolSize = value("maximumPoolSize").toInt(),
|
||||
connectionTimeout = value("connectionTimeout").toLong()
|
||||
)
|
||||
|
||||
val serverConfig
|
||||
get() = ServerConfig(
|
||||
host = value("host"),
|
||||
port = value("port").toInt(),
|
||||
)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package be.simplenotes.app
|
||||
|
||||
import be.simplenotes.app.extensions.addShutdownHook
|
||||
import be.simplenotes.app.modules.*
|
||||
import be.simplenotes.domain.domainModule
|
||||
import be.simplenotes.persistance.migrationModule
|
||||
import be.simplenotes.persistance.persistanceModule
|
||||
import be.simplenotes.search.searchModule
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.unloadKoinModules
|
||||
|
||||
fun main() {
|
||||
startKoin {
|
||||
modules(
|
||||
serverModule,
|
||||
persistanceModule,
|
||||
migrationModule,
|
||||
configModule,
|
||||
baseModule,
|
||||
userModule,
|
||||
noteModule,
|
||||
settingsModule,
|
||||
domainModule,
|
||||
searchModule,
|
||||
apiModule,
|
||||
jsonModule
|
||||
)
|
||||
}.addShutdownHook()
|
||||
|
||||
unloadKoinModules(listOf(migrationModule, configModule))
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
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)
|
||||
@@ -1,26 +0,0 @@
|
||||
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 +0,0 @@
|
||||
package be.simplenotes.app.controllers
|
||||
|
||||
import be.simplenotes.app.extensions.html
|
||||
import be.simplenotes.app.views.BaseView
|
||||
import be.simplenotes.domain.security.JwtPayload
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.Response
|
||||
import org.http4k.core.Status.Companion.OK
|
||||
|
||||
class BaseController(private val view: BaseView) {
|
||||
fun index(@Suppress("UNUSED_PARAMETER") request: Request, jwtPayload: JwtPayload?) =
|
||||
Response(OK).html(view.renderHome(jwtPayload))
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package be.simplenotes.app.controllers
|
||||
|
||||
import be.simplenotes.persistance.DbHealthCheck
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.Response
|
||||
import org.http4k.core.Status.Companion.OK
|
||||
import org.http4k.core.Status.Companion.SERVICE_UNAVAILABLE
|
||||
|
||||
class HealthCheckController(private val dbHealthCheck: DbHealthCheck) {
|
||||
fun healthCheck(request: Request) =
|
||||
if (dbHealthCheck.isOk()) Response(OK) else Response(SERVICE_UNAVAILABLE)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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,12 +0,0 @@
|
||||
package be.simplenotes.app.extensions
|
||||
|
||||
import org.koin.core.KoinApplication
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
fun KoinApplication.addShutdownHook() {
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
thread(start = false) {
|
||||
close()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package be.simplenotes.app.extensions
|
||||
|
||||
import kotlinx.html.*
|
||||
|
||||
class SUMMARY(consumer: TagConsumer<*>) :
|
||||
HTMLTag(
|
||||
"summary", consumer, emptyMap(),
|
||||
inlineTag = true,
|
||||
emptyTag = false
|
||||
),
|
||||
HtmlInlineTag
|
||||
|
||||
fun DETAILS.summary(block: SUMMARY.() -> Unit = {}) {
|
||||
SUMMARY(consumer).visit(block)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package be.simplenotes.app.filters
|
||||
|
||||
import be.simplenotes.app.extensions.redirect
|
||||
import be.simplenotes.domain.security.JwtPayload
|
||||
import be.simplenotes.domain.security.JwtPayloadExtractor
|
||||
import org.http4k.core.*
|
||||
import org.http4k.core.Status.Companion.UNAUTHORIZED
|
||||
import org.http4k.core.cookie.cookie
|
||||
|
||||
enum class AuthType {
|
||||
Optional, Required
|
||||
}
|
||||
|
||||
private const val authKey = "auth"
|
||||
|
||||
class AuthFilter(
|
||||
private val extractor: JwtPayloadExtractor,
|
||||
private val authType: AuthType,
|
||||
private val ctx: RequestContexts,
|
||||
private val source: JwtSource = JwtSource.Cookie,
|
||||
private val redirect: Boolean = true,
|
||||
) : Filter {
|
||||
override fun invoke(next: HttpHandler): HttpHandler = {
|
||||
val token = when (source) {
|
||||
JwtSource.Header -> it.bearerTokenHeader()
|
||||
JwtSource.Cookie -> it.bearerTokenCookie()
|
||||
}
|
||||
val jwtPayload = token?.let { token -> extractor(token) }
|
||||
when {
|
||||
jwtPayload != null -> {
|
||||
ctx[it][authKey] = jwtPayload
|
||||
next(it)
|
||||
}
|
||||
authType == AuthType.Required -> {
|
||||
if (redirect) Response.redirect("/login")
|
||||
else Response(UNAUTHORIZED)
|
||||
}
|
||||
else -> next(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Request.jwtPayload(ctx: RequestContexts): JwtPayload? = ctx[this][authKey]
|
||||
|
||||
enum class JwtSource {
|
||||
Header, Cookie
|
||||
}
|
||||
|
||||
private fun Request.bearerTokenCookie(): String? = cookie("Bearer")
|
||||
?.value
|
||||
?.trim()
|
||||
|
||||
private fun Request.bearerTokenHeader(): String? =
|
||||
header("Authorization")
|
||||
?.trim()
|
||||
?.takeIf { it.startsWith("Bearer") }
|
||||
?.substringAfter("Bearer")
|
||||
?.trim()
|
||||
@@ -1,13 +0,0 @@
|
||||
package be.simplenotes.app.filters
|
||||
|
||||
import me.liuwj.ktorm.database.Database
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.HttpHandler
|
||||
|
||||
class TransactionFilter(private val db: Database) : Filter {
|
||||
override fun invoke(next: HttpHandler): HttpHandler = { request ->
|
||||
db.useTransaction {
|
||||
next(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package be.simplenotes.app.modules
|
||||
|
||||
import be.simplenotes.app.api.ApiNoteController
|
||||
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")) {
|
||||
AuthFilter(
|
||||
extractor = get(),
|
||||
authType = AuthType.Required,
|
||||
ctx = get(),
|
||||
source = JwtSource.Header,
|
||||
redirect = false
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
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 }
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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()) }
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package be.simplenotes.app.modules
|
||||
|
||||
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.routes.Router
|
||||
import be.simplenotes.app.utils.StaticFileResolver
|
||||
import be.simplenotes.app.utils.StaticFileResolverImpl
|
||||
import be.simplenotes.app.views.ErrorView
|
||||
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
|
||||
import org.koin.dsl.module
|
||||
import org.koin.dsl.onClose
|
||||
import org.http4k.server.ServerConfig as Http4kServerConfig
|
||||
|
||||
val serverModule = module {
|
||||
single(createdAtStart = true) { Server(get(), get()).start() } onClose { it?.stop() }
|
||||
single { get<RoutingHttpHandler>().asServer(get()) }
|
||||
single<Http4kServerConfig> {
|
||||
val config = get<ServerConfig>()
|
||||
val builder: ConnectorBuilder = { server: org.eclipse.jetty.server.Server ->
|
||||
ServerConnector(server).apply {
|
||||
port = config.port
|
||||
host = config.host
|
||||
}
|
||||
}
|
||||
Jetty(config.port, builder)
|
||||
}
|
||||
single<StaticFileResolver> { StaticFileResolverImpl(get()) }
|
||||
single {
|
||||
Router(
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
requiredAuth = get(AuthType.Required.qualifier),
|
||||
optionalAuth = get(AuthType.Optional.qualifier),
|
||||
apiAuth = get(named("apiAuthFilter")),
|
||||
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 { ErrorView(get()) }
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
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.domain.security.JwtPayload
|
||||
import org.http4k.core.*
|
||||
import org.http4k.core.Method.*
|
||||
import org.http4k.filter.ResponseFilters.GZip
|
||||
import org.http4k.filter.ServerFilters.InitialiseRequestContext
|
||||
import org.http4k.routing.*
|
||||
import org.http4k.routing.ResourceLoader.Companion.Classpath
|
||||
|
||||
class Router(
|
||||
private val baseController: BaseController,
|
||||
private val userController: UserController,
|
||||
private val noteController: NoteController,
|
||||
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 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 publicRoutes = routes(
|
||||
"/" bind GET public baseController::index,
|
||||
"/register" bind GET public userController::register,
|
||||
"/register" bind POST `public transactional` userController::register,
|
||||
"/login" bind GET public userController::login,
|
||||
"/login" bind POST public userController::login,
|
||||
"/logout" bind POST to userController::logout,
|
||||
"/notes/public/{uuid}" bind GET public noteController::public,
|
||||
)
|
||||
|
||||
val protectedRoutes = routes(
|
||||
"/settings" bind GET protected settingsController::settings,
|
||||
"/settings" bind POST transactional 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/trash" bind GET protected noteController::trash,
|
||||
"/notes/{uuid}" bind GET protected noteController::note,
|
||||
"/notes/{uuid}" bind POST transactional 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,
|
||||
)
|
||||
|
||||
val apiRoutes = routes(
|
||||
"/api/login" bind POST to apiUserController::login,
|
||||
)
|
||||
|
||||
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/{uuid}" bind GET protected apiNoteController::note,
|
||||
"/api/notes/{uuid}" bind PUT transactional apiNoteController::update,
|
||||
)
|
||||
|
||||
val routes = routes(
|
||||
basicRoutes,
|
||||
optionalAuth.then(publicRoutes),
|
||||
requiredAuth.then(protectedRoutes),
|
||||
apiAuth.then(protectedApiRoutes),
|
||||
apiRoutes,
|
||||
)
|
||||
|
||||
val globalFilters = errorFilter
|
||||
.then(InitialiseRequestContext(contexts))
|
||||
.then(SecurityFilter)
|
||||
.then(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
|
||||
private typealias ProtectedHandler = (Request, JwtPayload) -> Response
|
||||
@@ -1,10 +0,0 @@
|
||||
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,12 +0,0 @@
|
||||
host=localhost
|
||||
port=8080
|
||||
#
|
||||
jdbcUrl=jdbc:h2:./notes-db;
|
||||
driverClassName=org.h2.Driver
|
||||
username=h2
|
||||
password=
|
||||
maximumPoolSize=10
|
||||
connectionTimeout=3000
|
||||
#
|
||||
jwt.secret=PliLvfk7l4WF+cZJk66LR5Mpnh+ocbvJ2wfUCK2UCms=
|
||||
jwt.validity=24
|
||||
@@ -1 +0,0 @@
|
||||
package be.simplenotes.app
|
||||
@@ -0,0 +1,2 @@
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
@@ -0,0 +1,20 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
kotlinDslPluginOptions {
|
||||
experimentalWarning.set(false)
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven { setUrl("https://kotlin.bintray.com/kotlinx") }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10")
|
||||
implementation("org.jetbrains.kotlin:kotlin-serialization:1.4.10")
|
||||
implementation("com.github.jengelman.gradle.plugins:shadow:6.1.0")
|
||||
implementation("org.jlleitschuh.gradle:ktlint-gradle:9.4.1")
|
||||
implementation("com.github.ben-manes:gradle-versions-plugin:0.28.0")
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package be.simplenotes
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.kotlin.dsl.getByType
|
||||
import java.io.File
|
||||
import java.lang.ProcessBuilder.Redirect.PIPE
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
open class CssTask : DefaultTask() {
|
||||
|
||||
private val root = project.parent!!.rootDir
|
||||
|
||||
private val viewsProject = project
|
||||
.parent
|
||||
?.project(":simplenotes-views")
|
||||
?: error("Missing :simplenotes-views")
|
||||
|
||||
@get:InputDirectory
|
||||
val templatesDir = viewsProject.extensions
|
||||
.getByType<SourceSetContainer>()
|
||||
.asMap.getOrElse("main") { error("main sources not found") }
|
||||
.allSource.srcDirs
|
||||
.find { it.endsWith("kotlin") }
|
||||
?: error("kotlin sources not found")
|
||||
|
||||
private val yarnRoot = File(project.rootDir, "css")
|
||||
|
||||
@get:InputDirectory
|
||||
val postCssDir = File(project.rootDir, "css/src")
|
||||
|
||||
@get:InputFiles
|
||||
val postCssConfig = listOf(
|
||||
"tailwind.config.js",
|
||||
"postcss.config.js",
|
||||
"package.json"
|
||||
).map { File(yarnRoot, it) }
|
||||
|
||||
@get:OutputDirectory
|
||||
val outputRootDir = File(project.buildDir, "generated-resources/css")
|
||||
|
||||
private val cssIndex = File(postCssDir, "styles.pcss")
|
||||
|
||||
private val cssOutput = File(outputRootDir, "static/styles.css")
|
||||
private val manifestOutput = File(outputRootDir, "css-manifest.json")
|
||||
|
||||
private val purgeGlob = "$templatesDir/**/*.kt"
|
||||
|
||||
@TaskAction
|
||||
fun generateCss() {
|
||||
// TODO: auto yarn install ?
|
||||
|
||||
outputRootDir.deleteRecursively()
|
||||
|
||||
ProcessBuilder("yarn", "run", "postcss", "build", "$cssIndex", "--output", "$cssOutput")
|
||||
.apply {
|
||||
environment().let {
|
||||
it["MANIFEST"] = "$manifestOutput"
|
||||
it["NODE_ENV"] = "production"
|
||||
it["PURGE"] = purgeGlob
|
||||
}
|
||||
}
|
||||
.redirectOutput(PIPE)
|
||||
.redirectError(PIPE)
|
||||
.directory(yarnRoot)
|
||||
.start()
|
||||
.apply {
|
||||
thread { inputStream.use { it.copyTo(System.out) } }
|
||||
thread { errorStream.use { it.copyTo(System.out) } }
|
||||
waitFor(30, TimeUnit.SECONDS)
|
||||
if (exitValue() != 0) throw GradleException(":/")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
@file:Suppress("SpellCheckingInspection")
|
||||
|
||||
package be.simplenotes
|
||||
|
||||
object Libs {
|
||||
const val arrowCoreData = "io.arrow-kt:arrow-core-data:0.11.0"
|
||||
const val commonsCompress = "org.apache.commons:commons-compress:1.20"
|
||||
const val flexmark = "com.vladsch.flexmark:flexmark:0.62.2"
|
||||
const val flexmarkGfmTasklist = "com.vladsch.flexmark:flexmark-ext-gfm-tasklist:0.62.2"
|
||||
const val flywayCore = "org.flywaydb:flyway-core:6.5.4"
|
||||
const val h2 = "com.h2database:h2:1.4.200"
|
||||
const val hikariCP = "com.zaxxer:HikariCP:3.4.3"
|
||||
const val http4kCore = "org.http4k:http4k-core:3.268.0"
|
||||
const val javaJwt = "com.auth0:java-jwt:3.10.3"
|
||||
const val javaxServlet = "javax.servlet:javax.servlet-api:4.0.1"
|
||||
const val jbcrypt = "org.mindrot:jbcrypt:0.4"
|
||||
const val jettyServer = "org.eclipse.jetty:jetty-server:9.4.32.v20200930"
|
||||
const val jettyServlet = "org.eclipse.jetty:jetty-servlet:9.4.32.v20200930"
|
||||
const val konform = "io.konform:konform-jvm:0.2.0"
|
||||
const val kotlinxHtml = "org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.1"
|
||||
const val kotlinxSerializationJson = "org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.0.0"
|
||||
const val ktormCore = "me.liuwj.ktorm:ktorm-core:3.0.0"
|
||||
const val ktormMysql = "me.liuwj.ktorm:ktorm-support-mysql:3.0.0"
|
||||
const val logbackClassic = "ch.qos.logback:logback-classic:1.2.3"
|
||||
const val luceneAnalyzersCommon = "org.apache.lucene:lucene-analyzers-common:8.6.1"
|
||||
const val luceneCore = "org.apache.lucene:lucene-core:8.6.1"
|
||||
const val luceneQueryParser = "org.apache.lucene:lucene-queryparser:8.6.1"
|
||||
const val mapstruct = "org.mapstruct:mapstruct:1.4.1.Final"
|
||||
const val mapstructProcessor = "org.mapstruct:mapstruct-processor:1.4.1.Final"
|
||||
const val micronaut = "io.micronaut:micronaut-inject:2.1.2"
|
||||
const val micronautProcessor = "io.micronaut:micronaut-inject-java:2.1.2"
|
||||
const val mariadbClient = "org.mariadb.jdbc:mariadb-java-client:2.6.2"
|
||||
const val owaspHtmlSanitizer = "com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20200713.1"
|
||||
const val prettytime ="org.ocpsoft.prettytime:prettytime:4.0.5.Final"
|
||||
const val slf4jApi = "org.slf4j:slf4j-api:1.7.25"
|
||||
const val snakeyaml = "org.yaml:snakeyaml:1.26"
|
||||
|
||||
const val assertJ = "org.assertj:assertj-core:3.16.1"
|
||||
const val hamkrest = "com.natpryce:hamkrest:1.7.0.3"
|
||||
const val http4kTestingHamkrest = "org.http4k:http4k-testing-hamkrest:3.268.0"
|
||||
const val junit = "org.junit.jupiter:junit-jupiter:5.6.2"
|
||||
const val mockk = "io.mockk:mockk:1.10.0"
|
||||
const val faker = "com.github.javafaker:javafaker:1.0.2"
|
||||
const val mariaTestContainer = "org.testcontainers:mariadb:1.15.0-rc2"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package be.simplenotes
|
||||
|
||||
import org.gradle.kotlin.dsl.register
|
||||
|
||||
plugins {
|
||||
java apply false
|
||||
}
|
||||
|
||||
tasks.register<CssTask>("css")
|
||||
|
||||
sourceSets {
|
||||
val main by getting
|
||||
val root = file("$buildDir/generated-resources/css")
|
||||
main.resources.srcDir(root)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package be.simplenotes
|
||||
|
||||
tasks.create("dockerBuild") {
|
||||
dependsOn("package")
|
||||
|
||||
doLast {
|
||||
exec {
|
||||
commandLine("docker", "build", "-t", "hubv/simplenotes:latest", ".")
|
||||
workingDir(rootProject.projectDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.create("dockerPush") {
|
||||
dependsOn("dockerBuild")
|
||||
|
||||
doLast {
|
||||
exec {
|
||||
commandLine("docker", "push", "hubv/simplenotes:latest")
|
||||
workingDir(rootProject.projectDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package be.simplenotes
|
||||
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
id("com.github.johnrengelman.shadow")
|
||||
}
|
||||
|
||||
tasks.withType<ShadowJar> {
|
||||
|
||||
archiveAppendix.set("with-dependencies")
|
||||
manifest.attributes["Main-Class"] = "be.simplenotes.app.SimpleNotesKt"
|
||||
|
||||
mergeServiceFiles()
|
||||
|
||||
File(rootProject.projectDir, "buildSrc/src/main/resources/exclusions")
|
||||
.listFiles()!!
|
||||
.flatMap {
|
||||
it.readLines()
|
||||
.asSequence()
|
||||
.map { it.trim() }
|
||||
.filterNot { it.isBlank() }
|
||||
.filterNot { it.startsWith("#") }
|
||||
.asIterable()
|
||||
}.forEach { exclude(it) }
|
||||
|
||||
}
|
||||
|
||||
tasks.create("package") {
|
||||
tasks.getByName("build").dependsOn("package")
|
||||
|
||||
dependsOn("shadowJar")
|
||||
dependsOn("css")
|
||||
|
||||
doLast {
|
||||
println("SimpleNotes Packaged !")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package be.simplenotes
|
||||
|
||||
plugins {
|
||||
id("be.simplenotes.java-convention")
|
||||
id("be.simplenotes.kotlin-convention")
|
||||
id("be.simplenotes.junit-convention")
|
||||
id("org.jlleitschuh.gradle.ktlint")
|
||||
id("com.github.ben-manes.versions")
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package be.simplenotes
|
||||
|
||||
plugins {
|
||||
java
|
||||
`java-library`
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven { url = uri("https://dl.bintray.com/arrow-kt/arrow-kt/") }
|
||||
maven { url = uri("https://kotlin.bintray.com/kotlinx") }
|
||||
}
|
||||
|
||||
group = "be.simplenotes"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_14
|
||||
targetCompatibility = JavaVersion.VERSION_14
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package be.simplenotes
|
||||
|
||||
plugins {
|
||||
java apply false
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val test by getting
|
||||
test.resources.srcDir("${rootProject.projectDir}/simplenotes-test-resources/src/test/resources")
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package be.simplenotes
|
||||
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.4.10"))
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions {
|
||||
jvmTarget = "14"
|
||||
javaParameters = true
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xinline-classes",
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-receiver-assertions"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package be.simplenotes
|
||||
|
||||
plugins {
|
||||
kotlin("plugin.serialization")
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
META-INF/maven/**
|
||||
META-INF/proguard/**
|
||||
META-INF/*.kotlin_module
|
||||
META-INF/DEPENDENCIES*
|
||||
META-INF/NOTICE*
|
||||
META-INF/LICENSE*
|
||||
LICENSE*
|
||||
META-INF/README*
|
||||
META-INF/native-image/**
|
||||
|
||||
# Jetty
|
||||
about.html
|
||||
jetty-dir.css
|
||||
server-ssl-cert.pem
|
||||
@@ -0,0 +1,261 @@
|
||||
com/google/common/util/**
|
||||
com/google/common/eventbus/**
|
||||
com/google/common/reflect/**
|
||||
com/google/common/escape/**
|
||||
com/google/common/graph/**
|
||||
com/google/common/html/**
|
||||
com/google/common/hash/**
|
||||
com/google/common/xml/**
|
||||
com/google/common/io/**
|
||||
com/google/common/cache/**
|
||||
com/google/common/net/**
|
||||
|
||||
# Collections
|
||||
com/google/common/collect/AbstractBiMap$1.class
|
||||
com/google/common/collect/AbstractBiMap$BiMapEntry.class
|
||||
com/google/common/collect/AbstractBiMap$EntrySet.class
|
||||
com/google/common/collect/AbstractBiMap$Inverse.class
|
||||
com/google/common/collect/AbstractBiMap$KeySet.class
|
||||
com/google/common/collect/AbstractBiMap$ValueSet.class
|
||||
com/google/common/collect/AbstractBiMap.class
|
||||
com/google/common/collect/AbstractSortedKeySortedSetMultimap.class
|
||||
com/google/common/collect/AbstractSortedMultiset$1DescendingMultisetImpl.class
|
||||
com/google/common/collect/AbstractSortedMultiset.class
|
||||
com/google/common/collect/AbstractTable$1.class
|
||||
com/google/common/collect/AbstractTable$CellSet.class
|
||||
com/google/common/collect/AbstractTable$Values.class
|
||||
com/google/common/collect/AbstractTable.class
|
||||
com/google/common/collect/ArrayListMultimap.class
|
||||
com/google/common/collect/ArrayListMultimapGwtSerializationDependencies.class
|
||||
com/google/common/collect/ArrayTable$1.class
|
||||
com/google/common/collect/ArrayTable$2.class
|
||||
com/google/common/collect/ArrayTable$3.class
|
||||
com/google/common/collect/ArrayTable$ArrayMap$1.class
|
||||
com/google/common/collect/ArrayTable$ArrayMap$2.class
|
||||
com/google/common/collect/ArrayTable$ArrayMap.class
|
||||
com/google/common/collect/ArrayTable$Column.class
|
||||
com/google/common/collect/ArrayTable$ColumnMap.class
|
||||
com/google/common/collect/ArrayTable$Row.class
|
||||
com/google/common/collect/ArrayTable$RowMap.class
|
||||
com/google/common/collect/ArrayTable.class
|
||||
com/google/common/collect/ClassToInstanceMap.class
|
||||
com/google/common/collect/CompactHashMap$1.class
|
||||
com/google/common/collect/CompactHashMap$2.class
|
||||
com/google/common/collect/CompactHashMap$3.class
|
||||
com/google/common/collect/CompactHashMap$EntrySetView.class
|
||||
com/google/common/collect/CompactHashMap$Itr.class
|
||||
com/google/common/collect/CompactHashMap$KeySetView.class
|
||||
com/google/common/collect/CompactHashMap$MapEntry.class
|
||||
com/google/common/collect/CompactHashMap$ValuesView.class
|
||||
com/google/common/collect/CompactHashMap.class
|
||||
com/google/common/collect/CompactHashSet$1.class
|
||||
com/google/common/collect/CompactHashSet.class
|
||||
com/google/common/collect/CompactLinkedHashMap$1EntrySetImpl.class
|
||||
com/google/common/collect/CompactLinkedHashMap$1KeySetImpl.class
|
||||
com/google/common/collect/CompactLinkedHashMap$1ValuesImpl.class
|
||||
com/google/common/collect/CompactLinkedHashMap.class
|
||||
com/google/common/collect/CompactLinkedHashSet.class
|
||||
com/google/common/collect/Comparators.class
|
||||
com/google/common/collect/ComputationException.class
|
||||
com/google/common/collect/ConcurrentHashMultiset$1.class
|
||||
com/google/common/collect/ConcurrentHashMultiset$2.class
|
||||
com/google/common/collect/ConcurrentHashMultiset$3.class
|
||||
com/google/common/collect/ConcurrentHashMultiset$EntrySet.class
|
||||
com/google/common/collect/ConcurrentHashMultiset$FieldSettersHolder.class
|
||||
com/google/common/collect/ConcurrentHashMultiset.class
|
||||
com/google/common/collect/DenseImmutableTable$1.class
|
||||
com/google/common/collect/DenseImmutableTable$Column.class
|
||||
com/google/common/collect/DenseImmutableTable$ColumnMap.class
|
||||
com/google/common/collect/DenseImmutableTable$ImmutableArrayMap$1.class
|
||||
com/google/common/collect/DenseImmutableTable$ImmutableArrayMap.class
|
||||
com/google/common/collect/DenseImmutableTable$Row.class
|
||||
com/google/common/collect/DenseImmutableTable$RowMap.class
|
||||
com/google/common/collect/DenseImmutableTable.class
|
||||
com/google/common/collect/DescendingImmutableSortedMultiset.class
|
||||
com/google/common/collect/DescendingMultiset$1EntrySetImpl.class
|
||||
com/google/common/collect/DescendingMultiset.class
|
||||
com/google/common/collect/EnumBiMap.class
|
||||
com/google/common/collect/EnumHashBiMap.class
|
||||
com/google/common/collect/EnumMultiset$1.class
|
||||
com/google/common/collect/EnumMultiset$2$1.class
|
||||
com/google/common/collect/EnumMultiset$2.class
|
||||
com/google/common/collect/EnumMultiset$Itr.class
|
||||
com/google/common/collect/EnumMultiset.class
|
||||
com/google/common/collect/EvictingQueue.class
|
||||
com/google/common/collect/ForwardingBlockingDeque.class
|
||||
com/google/common/collect/ForwardingDeque.class
|
||||
com/google/common/collect/ForwardingImmutableCollection.class
|
||||
com/google/common/collect/ForwardingImmutableList.class
|
||||
com/google/common/collect/ForwardingImmutableMap.class
|
||||
com/google/common/collect/ForwardingImmutableSet.class
|
||||
com/google/common/collect/ForwardingIterator.class
|
||||
com/google/common/collect/ForwardingListIterator.class
|
||||
com/google/common/collect/ForwardingListMultimap.class
|
||||
com/google/common/collect/ForwardingNavigableMap$StandardDescendingMap$1.class
|
||||
com/google/common/collect/ForwardingNavigableMap$StandardDescendingMap.class
|
||||
com/google/common/collect/ForwardingNavigableMap$StandardNavigableKeySet.class
|
||||
com/google/common/collect/ForwardingNavigableMap.class
|
||||
com/google/common/collect/ForwardingQueue.class
|
||||
com/google/common/collect/ForwardingSetMultimap.class
|
||||
com/google/common/collect/ForwardingSortedMultiset$StandardDescendingMultiset.class
|
||||
com/google/common/collect/ForwardingSortedMultiset$StandardElementSet.class
|
||||
com/google/common/collect/ForwardingSortedMultiset.class
|
||||
com/google/common/collect/ForwardingSortedSetMultimap.class
|
||||
com/google/common/collect/ForwardingTable.class
|
||||
com/google/common/collect/GeneralRange.class
|
||||
com/google/common/collect/GwtTransient.class
|
||||
com/google/common/collect/HashBasedTable$Factory.class
|
||||
com/google/common/collect/HashBasedTable.class
|
||||
com/google/common/collect/HashBiMap$1$MapEntry.class
|
||||
com/google/common/collect/HashBiMap$1.class
|
||||
com/google/common/collect/HashBiMap$BiEntry.class
|
||||
com/google/common/collect/HashBiMap$Inverse$1$InverseEntry.class
|
||||
com/google/common/collect/HashBiMap$Inverse$1.class
|
||||
com/google/common/collect/HashBiMap$Inverse$InverseKeySet$1.class
|
||||
com/google/common/collect/HashBiMap$Inverse$InverseKeySet.class
|
||||
com/google/common/collect/HashBiMap$Inverse.class
|
||||
com/google/common/collect/HashBiMap$InverseSerializedForm.class
|
||||
com/google/common/collect/HashBiMap$Itr.class
|
||||
com/google/common/collect/HashBiMap$KeySet$1.class
|
||||
com/google/common/collect/HashBiMap$KeySet.class
|
||||
com/google/common/collect/HashBiMap.class
|
||||
com/google/common/collect/HashMultimap.class
|
||||
com/google/common/collect/HashMultimapGwtSerializationDependencies.class
|
||||
com/google/common/collect/ImmutableClassToInstanceMap$1.class
|
||||
com/google/common/collect/ImmutableClassToInstanceMap$Builder.class
|
||||
com/google/common/collect/ImmutableClassToInstanceMap.class
|
||||
com/google/common/collect/ImmutableSortedMultiset$Builder.class
|
||||
com/google/common/collect/ImmutableSortedMultiset$SerializedForm.class
|
||||
com/google/common/collect/ImmutableSortedMultiset.class
|
||||
com/google/common/collect/ImmutableSortedMultisetFauxverideShim.class
|
||||
com/google/common/collect/ImmutableTable$1.class
|
||||
com/google/common/collect/ImmutableTable$Builder.class
|
||||
com/google/common/collect/ImmutableTable$CollectorState.class
|
||||
com/google/common/collect/ImmutableTable$MutableCell.class
|
||||
com/google/common/collect/ImmutableTable$SerializedForm.class
|
||||
com/google/common/collect/ImmutableTable.class
|
||||
com/google/common/collect/Interner.class
|
||||
com/google/common/collect/Interners$1.class
|
||||
com/google/common/collect/Interners$InternerBuilder.class
|
||||
com/google/common/collect/Interners$InternerFunction.class
|
||||
com/google/common/collect/Interners$InternerImpl.class
|
||||
com/google/common/collect/Interners.class
|
||||
com/google/common/collect/LinkedHashMultimap$1.class
|
||||
com/google/common/collect/LinkedHashMultimap$ValueEntry.class
|
||||
com/google/common/collect/LinkedHashMultimap$ValueSet$1.class
|
||||
com/google/common/collect/LinkedHashMultimap$ValueSet.class
|
||||
com/google/common/collect/LinkedHashMultimap$ValueSetLink.class
|
||||
com/google/common/collect/LinkedHashMultimap.class
|
||||
com/google/common/collect/LinkedHashMultimapGwtSerializationDependencies.class
|
||||
com/google/common/collect/LinkedListMultimap$1.class
|
||||
com/google/common/collect/LinkedListMultimap$1EntriesImpl.class
|
||||
com/google/common/collect/LinkedListMultimap$1KeySetImpl.class
|
||||
com/google/common/collect/LinkedListMultimap$1ValuesImpl$1.class
|
||||
com/google/common/collect/LinkedListMultimap$1ValuesImpl.class
|
||||
com/google/common/collect/LinkedListMultimap$DistinctKeyIterator.class
|
||||
com/google/common/collect/LinkedListMultimap$KeyList.class
|
||||
com/google/common/collect/LinkedListMultimap$Node.class
|
||||
com/google/common/collect/LinkedListMultimap$NodeIterator.class
|
||||
com/google/common/collect/LinkedListMultimap$ValueForKeyIterator.class
|
||||
com/google/common/collect/LinkedListMultimap.class
|
||||
com/google/common/collect/MinMaxPriorityQueue$1.class
|
||||
com/google/common/collect/MinMaxPriorityQueue$Builder.class
|
||||
com/google/common/collect/MinMaxPriorityQueue$Heap.class
|
||||
com/google/common/collect/MinMaxPriorityQueue$MoveDesc.class
|
||||
com/google/common/collect/MinMaxPriorityQueue$QueueIterator.class
|
||||
com/google/common/collect/MinMaxPriorityQueue.class
|
||||
com/google/common/collect/MoreCollectors$ToOptionalState.class
|
||||
com/google/common/collect/MoreCollectors.class
|
||||
com/google/common/collect/MutableClassToInstanceMap$1.class
|
||||
com/google/common/collect/MutableClassToInstanceMap$2$1.class
|
||||
com/google/common/collect/MutableClassToInstanceMap$2.class
|
||||
com/google/common/collect/MutableClassToInstanceMap$SerializedForm.class
|
||||
com/google/common/collect/MutableClassToInstanceMap.class
|
||||
com/google/common/collect/Queues.class
|
||||
com/google/common/collect/RegularImmutableSortedMultiset.class
|
||||
com/google/common/collect/RegularImmutableTable$1.class
|
||||
com/google/common/collect/RegularImmutableTable$CellSet.class
|
||||
com/google/common/collect/RegularImmutableTable$Values.class
|
||||
com/google/common/collect/RegularImmutableTable.class
|
||||
com/google/common/collect/RowSortedTable.class
|
||||
com/google/common/collect/SingletonImmutableTable.class
|
||||
com/google/common/collect/SortedMultisets$ElementSet.class
|
||||
com/google/common/collect/SortedMultisets$NavigableElementSet.class
|
||||
com/google/common/collect/SortedMultisets.class
|
||||
com/google/common/collect/SparseImmutableTable.class
|
||||
com/google/common/collect/StandardRowSortedTable$1.class
|
||||
com/google/common/collect/StandardRowSortedTable$RowSortedMap.class
|
||||
com/google/common/collect/StandardRowSortedTable.class
|
||||
com/google/common/collect/StandardTable$1.class
|
||||
com/google/common/collect/StandardTable$CellIterator.class
|
||||
com/google/common/collect/StandardTable$Column$EntrySet.class
|
||||
com/google/common/collect/StandardTable$Column$EntrySetIterator$1EntryImpl.class
|
||||
com/google/common/collect/StandardTable$Column$EntrySetIterator.class
|
||||
com/google/common/collect/StandardTable$Column$KeySet.class
|
||||
com/google/common/collect/StandardTable$Column$Values.class
|
||||
com/google/common/collect/StandardTable$Column.class
|
||||
com/google/common/collect/StandardTable$ColumnKeyIterator.class
|
||||
com/google/common/collect/StandardTable$ColumnKeySet.class
|
||||
com/google/common/collect/StandardTable$ColumnMap$ColumnMapEntrySet$1.class
|
||||
com/google/common/collect/StandardTable$ColumnMap$ColumnMapEntrySet.class
|
||||
com/google/common/collect/StandardTable$ColumnMap$ColumnMapValues.class
|
||||
com/google/common/collect/StandardTable$ColumnMap.class
|
||||
com/google/common/collect/StandardTable$Row$1.class
|
||||
com/google/common/collect/StandardTable$Row$2.class
|
||||
com/google/common/collect/StandardTable$Row.class
|
||||
com/google/common/collect/StandardTable$RowMap$EntrySet$1.class
|
||||
com/google/common/collect/StandardTable$RowMap$EntrySet.class
|
||||
com/google/common/collect/StandardTable$RowMap.class
|
||||
com/google/common/collect/StandardTable$TableSet.class
|
||||
com/google/common/collect/StandardTable.class
|
||||
com/google/common/collect/Tables$1.class
|
||||
com/google/common/collect/Tables$AbstractCell.class
|
||||
com/google/common/collect/Tables$ImmutableCell.class
|
||||
com/google/common/collect/Tables$TransformedTable$1.class
|
||||
com/google/common/collect/Tables$TransformedTable$2.class
|
||||
com/google/common/collect/Tables$TransformedTable$3.class
|
||||
com/google/common/collect/Tables$TransformedTable.class
|
||||
com/google/common/collect/Tables$TransposeTable$1.class
|
||||
com/google/common/collect/Tables$TransposeTable.class
|
||||
com/google/common/collect/Tables$UnmodifiableRowSortedMap.class
|
||||
com/google/common/collect/Tables$UnmodifiableTable.class
|
||||
com/google/common/collect/Tables.class
|
||||
com/google/common/collect/TreeBasedTable$1.class
|
||||
com/google/common/collect/TreeBasedTable$2.class
|
||||
com/google/common/collect/TreeBasedTable$Factory.class
|
||||
com/google/common/collect/TreeBasedTable$TreeRow.class
|
||||
com/google/common/collect/TreeBasedTable.class
|
||||
com/google/common/collect/TreeMultimap.class
|
||||
com/google/common/collect/TreeMultiset$1.class
|
||||
com/google/common/collect/TreeMultiset$2.class
|
||||
com/google/common/collect/TreeMultiset$3.class
|
||||
com/google/common/collect/TreeMultiset$4.class
|
||||
com/google/common/collect/TreeMultiset$Aggregate$1.class
|
||||
com/google/common/collect/TreeMultiset$Aggregate$2.class
|
||||
com/google/common/collect/TreeMultiset$Aggregate.class
|
||||
com/google/common/collect/TreeMultiset$AvlNode.class
|
||||
com/google/common/collect/TreeMultiset$Reference.class
|
||||
com/google/common/collect/TreeMultiset.class
|
||||
com/google/common/collect/TreeRangeMap$1.class
|
||||
com/google/common/collect/TreeRangeMap$AsMapOfRanges.class
|
||||
com/google/common/collect/TreeRangeMap$RangeMapEntry.class
|
||||
com/google/common/collect/TreeRangeMap$SubRangeMap$1$1.class
|
||||
com/google/common/collect/TreeRangeMap$SubRangeMap$1.class
|
||||
com/google/common/collect/TreeRangeMap$SubRangeMap$SubRangeMapAsMap$1.class
|
||||
com/google/common/collect/TreeRangeMap$SubRangeMap$SubRangeMapAsMap$2.class
|
||||
com/google/common/collect/TreeRangeMap$SubRangeMap$SubRangeMapAsMap$3.class
|
||||
com/google/common/collect/TreeRangeMap$SubRangeMap$SubRangeMapAsMap$4.class
|
||||
com/google/common/collect/TreeRangeMap$SubRangeMap$SubRangeMapAsMap.class
|
||||
com/google/common/collect/TreeRangeMap$SubRangeMap.class
|
||||
com/google/common/collect/TreeRangeMap.class
|
||||
com/google/common/collect/TreeTraverser$1.class
|
||||
com/google/common/collect/TreeTraverser$2$1.class
|
||||
com/google/common/collect/TreeTraverser$2.class
|
||||
com/google/common/collect/TreeTraverser$3$1.class
|
||||
com/google/common/collect/TreeTraverser$3.class
|
||||
com/google/common/collect/TreeTraverser$4.class
|
||||
com/google/common/collect/TreeTraverser$BreadthFirstIterator.class
|
||||
com/google/common/collect/TreeTraverser$PostOrderIterator.class
|
||||
com/google/common/collect/TreeTraverser$PostOrderNode.class
|
||||
com/google/common/collect/TreeTraverser$PreOrderIterator.class
|
||||
com/google/common/collect/TreeTraverser.class
|
||||
@@ -0,0 +1,2 @@
|
||||
ch/qos/logback/core/db/**
|
||||
ch/qos/logback/classic/db/**
|
||||
@@ -0,0 +1,5 @@
|
||||
org/checkerframework/**
|
||||
org/intellij/**
|
||||
com/google/errorprone/**
|
||||
com/google/thirdparty/**
|
||||
com/google/j2objc/**
|
||||
+1
-2
@@ -2,8 +2,7 @@
|
||||
"name": "css",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"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"
|
||||
"//": "`gradle css`"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^9.8.6",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
purge: {
|
||||
content: [
|
||||
'../app/src/main/kotlin/views/**/*.kt'
|
||||
process.env.PURGE
|
||||
]
|
||||
},
|
||||
theme: {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
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 . \
|
||||
&& docker push hubv/simplenotes:latest
|
||||
+5
-5
@@ -32,13 +32,13 @@ services:
|
||||
- .env
|
||||
environment:
|
||||
- TZ=Europe/Brussels
|
||||
- HOST=0.0.0.0
|
||||
- JDBCURL=jdbc:mariadb://db:3306/simplenotes
|
||||
- DRIVERCLASSNAME=org.mariadb.jdbc.Driver
|
||||
- USERNAME=simplenotes
|
||||
- SERVER_HOST=0.0.0.0
|
||||
- DB_JDBC_URL=jdbc:mariadb://db:3306/simplenotes
|
||||
- DB_DRIVER_CLASS_NAME=org.mariadb.jdbc.Driver
|
||||
- DB_USERNAME=simplenotes
|
||||
# .env:
|
||||
# - JWT_SECRET
|
||||
# - PASSWORD
|
||||
# - DB_PASSWORD
|
||||
ports:
|
||||
- 127.0.0.1:8080:8080
|
||||
healthcheck:
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>domain</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.arrow-kt</groupId>
|
||||
<artifactId>arrow-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.koin</groupId>
|
||||
<artifactId>koin-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.natpryce</groupId>
|
||||
<artifactId>hamkrest</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.mockk</groupId>
|
||||
<artifactId>mockk</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.konform</groupId>
|
||||
<artifactId>konform-jvm</artifactId>
|
||||
<version>0.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mindrot</groupId>
|
||||
<artifactId>jbcrypt</artifactId>
|
||||
<version>0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>3.10.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vladsch.flexmark</groupId>
|
||||
<artifactId>flexmark</artifactId>
|
||||
<version>0.62.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vladsch.flexmark</groupId>
|
||||
<artifactId>flexmark-ext-gfm-tasklist</artifactId>
|
||||
<version>0.62.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.26</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
|
||||
<artifactId>owasp-java-html-sanitizer</artifactId>
|
||||
<version>20200713.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-serialization-json-jvm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.20</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,37 +0,0 @@
|
||||
package be.simplenotes.domain
|
||||
|
||||
import be.simplenotes.domain.security.BcryptPasswordHash
|
||||
import be.simplenotes.domain.security.JwtPayloadExtractor
|
||||
import be.simplenotes.domain.security.PasswordHash
|
||||
import be.simplenotes.domain.security.SimpleJwt
|
||||
import be.simplenotes.domain.usecases.NoteService
|
||||
import be.simplenotes.domain.usecases.UserService
|
||||
import be.simplenotes.domain.usecases.export.ExportUseCase
|
||||
import be.simplenotes.domain.usecases.export.ExportUseCaseImpl
|
||||
import be.simplenotes.domain.usecases.markdown.MarkdownConverter
|
||||
import be.simplenotes.domain.usecases.markdown.MarkdownConverterImpl
|
||||
import be.simplenotes.domain.usecases.users.delete.DeleteUseCase
|
||||
import be.simplenotes.domain.usecases.users.delete.DeleteUseCaseImpl
|
||||
import be.simplenotes.domain.usecases.users.login.LoginUseCase
|
||||
import be.simplenotes.domain.usecases.users.login.LoginUseCaseImpl
|
||||
import be.simplenotes.domain.usecases.users.register.RegisterUseCase
|
||||
import be.simplenotes.domain.usecases.users.register.RegisterUseCaseImpl
|
||||
import org.koin.dsl.module
|
||||
|
||||
val domainModule = module {
|
||||
single<LoginUseCase> { LoginUseCaseImpl(get(), get(), get()) }
|
||||
single<RegisterUseCase> { RegisterUseCaseImpl(get(), get()) }
|
||||
single<DeleteUseCase> { DeleteUseCaseImpl(get(), get(), get()) }
|
||||
single { UserService(get(), get(), get(), get()) }
|
||||
single<PasswordHash> { BcryptPasswordHash() }
|
||||
single { SimpleJwt(get()) }
|
||||
single { JwtPayloadExtractor(get()) }
|
||||
single {
|
||||
NoteService(get(), get(), get(), get()).apply {
|
||||
dropAllIndexes()
|
||||
indexAll()
|
||||
}
|
||||
}
|
||||
single<MarkdownConverter> { MarkdownConverterImpl() }
|
||||
single<ExportUseCase> { ExportUseCaseImpl(get(), get()) }
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
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,20 +0,0 @@
|
||||
package be.simplenotes.domain.security
|
||||
|
||||
import org.owasp.html.HtmlPolicyBuilder
|
||||
|
||||
object HtmlSanitizer {
|
||||
private val htmlPolicy = HtmlPolicyBuilder()
|
||||
.allowElements("a")
|
||||
.allowCommonBlockElements()
|
||||
.allowCommonInlineFormattingElements()
|
||||
.allowElements("pre")
|
||||
.allowAttributes("class").onElements("code")
|
||||
.allowUrlProtocols("http", "https")
|
||||
.allowAttributes("href").onElements("a")
|
||||
.allowElements("input")
|
||||
.allowAttributes("type", "checked", "disabled", "readonly").onElements("input")
|
||||
.requireRelNofollowOnLinks()
|
||||
.toFactory()!!
|
||||
|
||||
fun sanitize(unsafeHtml: String) = htmlPolicy.sanitize(unsafeHtml)!!
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package be.simplenotes.domain.usecases
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.extensions.fx
|
||||
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.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(
|
||||
private val markdownConverter: MarkdownConverter,
|
||||
private val noteRepository: NoteRepository,
|
||||
private val userRepository: UserRepository,
|
||||
private val searcher: NoteSearcher,
|
||||
) {
|
||||
|
||||
fun create(userId: Int, markdownText: String) = Either.fx<MarkdownParsingError, PersistedNote> {
|
||||
val persistedNote = !markdownConverter.renderDocument(markdownText)
|
||||
.map { it.copy(html = HtmlSanitizer.sanitize(it.html)) }
|
||||
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
|
||||
.map { noteRepository.create(userId, it) }
|
||||
|
||||
searcher.indexNote(userId, persistedNote)
|
||||
persistedNote
|
||||
}
|
||||
|
||||
fun update(userId: Int, uuid: UUID, markdownText: String) = Either.fx<MarkdownParsingError, PersistedNote?> {
|
||||
val persistedNote = !markdownConverter.renderDocument(markdownText)
|
||||
.map { it.copy(html = HtmlSanitizer.sanitize(it.html)) }
|
||||
.map { Note(it.metadata, markdown = markdownText, html = it.html) }
|
||||
.map { noteRepository.update(userId, uuid, it) }
|
||||
|
||||
persistedNote?.let { searcher.updateIndex(userId, it) }
|
||||
persistedNote
|
||||
}
|
||||
|
||||
fun paginatedNotes(
|
||||
userId: Int,
|
||||
page: Int,
|
||||
itemsPerPage: Int = 20,
|
||||
tag: String? = null,
|
||||
deleted: Boolean = false,
|
||||
): PaginatedNotes {
|
||||
val count = noteRepository.count(userId, tag, deleted)
|
||||
val offset = (page - 1) * itemsPerPage
|
||||
val numberOfPages = (count / itemsPerPage) + 1
|
||||
val notes = if (count == 0) emptyList() else noteRepository.findAll(userId, itemsPerPage, offset, tag, deleted)
|
||||
return PaginatedNotes(numberOfPages, notes)
|
||||
}
|
||||
|
||||
fun find(userId: Int, uuid: UUID) = noteRepository.find(userId, uuid)
|
||||
|
||||
fun trash(userId: Int, uuid: UUID): Boolean {
|
||||
val res = noteRepository.delete(userId, uuid, permanent = false)
|
||||
if (res) searcher.deleteIndex(userId, uuid)
|
||||
return res
|
||||
}
|
||||
|
||||
fun restore(userId: Int, uuid: UUID): Boolean {
|
||||
val res = noteRepository.restore(userId, uuid)
|
||||
if (res) find(userId, uuid)?.let { note -> searcher.indexNote(userId, note) }
|
||||
return res
|
||||
}
|
||||
|
||||
fun delete(userId: Int, uuid: UUID): Boolean {
|
||||
val res = noteRepository.delete(userId, uuid, permanent = true)
|
||||
if (res) searcher.deleteIndex(userId, uuid)
|
||||
return res
|
||||
}
|
||||
|
||||
fun countDeleted(userId: Int) = noteRepository.count(userId, deleted = true)
|
||||
|
||||
fun indexAll() {
|
||||
val userIds = userRepository.findAll()
|
||||
userIds.forEach { id ->
|
||||
val notes = noteRepository.findAllDetails(id)
|
||||
searcher.indexNotes(id, notes)
|
||||
}
|
||||
}
|
||||
|
||||
fun search(userId: Int, searchTerms: SearchTerms) = searcher.search(userId, searchTerms)
|
||||
|
||||
fun dropAllIndexes() = searcher.dropAll()
|
||||
|
||||
fun makePublic(userId: Int, uuid: UUID) = noteRepository.makePublic(userId, uuid)
|
||||
fun makePrivate(userId: Int, uuid: UUID) = noteRepository.makePrivate(userId, uuid)
|
||||
fun findPublic(uuid: UUID) = noteRepository.findPublic(uuid)
|
||||
}
|
||||
|
||||
data class PaginatedNotes(val pages: Int, val notes: List<PersistedNoteMetadata>)
|
||||
@@ -1,27 +0,0 @@
|
||||
package be.simplenotes.domain.usecases.users.delete
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.extensions.fx
|
||||
import arrow.core.rightIfNotNull
|
||||
import be.simplenotes.domain.security.PasswordHash
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.domain.usecases.search.NoteSearcher
|
||||
import be.simplenotes.domain.validation.UserValidations
|
||||
|
||||
internal class DeleteUseCaseImpl(
|
||||
private val userRepository: UserRepository,
|
||||
private val passwordHash: PasswordHash,
|
||||
private val searcher: NoteSearcher,
|
||||
) : DeleteUseCase {
|
||||
override fun delete(form: DeleteForm) = Either.fx<DeleteError, Unit> {
|
||||
val user = !UserValidations.validateDelete(form)
|
||||
val persistedUser = !userRepository.find(user.username).rightIfNotNull { DeleteError.Unregistered }
|
||||
!Either.cond(
|
||||
passwordHash.verify(user.password, persistedUser.password),
|
||||
{ Unit },
|
||||
{ DeleteError.WrongPassword }
|
||||
)
|
||||
!Either.cond(userRepository.delete(persistedUser.id), { Unit }, { DeleteError.Unregistered })
|
||||
searcher.dropIndex(persistedUser.id)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package be.simplenotes.domain
|
||||
|
||||
/**
|
||||
* Empty file @see [root-package-declaration](https://discuss.kotlinlang.org/t/root-package-declaration-to-reduce-folder-clutter/2247/4)
|
||||
*/
|
||||
Vendored
BIN
Binary file not shown.
+5
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,77 +0,0 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>persistance</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>domain</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</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>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.200</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>6.5.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>3.4.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.liuwj.ktorm</groupId>
|
||||
<artifactId>ktorm-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.liuwj.ktorm</groupId>
|
||||
<artifactId>ktorm-support-mysql</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,48 +0,0 @@
|
||||
package be.simplenotes.persistance
|
||||
|
||||
import be.simplenotes.domain.usecases.repositories.NoteRepository
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
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.persistance.notes.NoteRepositoryImpl
|
||||
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
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
import org.koin.dsl.onClose
|
||||
import org.mapstruct.factory.Mappers
|
||||
import javax.sql.DataSource
|
||||
|
||||
private fun hikariDataSource(conf: DataSourceConfig): HikariDataSource {
|
||||
val hikariConfig = HikariConfig().also {
|
||||
it.jdbcUrl = conf.jdbcUrl
|
||||
it.driverClassName = conf.driverClassName
|
||||
it.username = conf.username
|
||||
it.password = conf.password
|
||||
it.maximumPoolSize = conf.maximumPoolSize
|
||||
it.connectionTimeout = conf.connectionTimeout
|
||||
}
|
||||
return HikariDataSource(hikariConfig)
|
||||
}
|
||||
|
||||
val migrationModule = module {
|
||||
single<DbMigrations> { DbMigrationsImpl(get(), get()) }
|
||||
}
|
||||
|
||||
val persistanceModule = module {
|
||||
single<NoteConverter> { NoteConverterImpl() }
|
||||
single<UserConverter> { UserConverterImpl() }
|
||||
single<UserRepository> { UserRepositoryImpl(get(), get()) }
|
||||
single<NoteRepository> { NoteRepositoryImpl(get(), get()) }
|
||||
single { hikariDataSource(get()) } bind DataSource::class onClose { it?.close() }
|
||||
single {
|
||||
get<DbMigrations>().migrate()
|
||||
Database.connect(get<DataSource>())
|
||||
}
|
||||
single<DbHealthCheck> { DbHealthCheckImpl(get(), get()) }
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package be.simplenotes.persistance.converters
|
||||
|
||||
import be.simplenotes.domain.model.PersistedUser
|
||||
import be.simplenotes.domain.model.User
|
||||
import be.simplenotes.persistance.users.UserEntity
|
||||
import me.liuwj.ktorm.entity.Entity
|
||||
import org.mapstruct.Mapper
|
||||
|
||||
@Mapper(uses = [UserEntityFactory::class])
|
||||
internal interface UserConverter {
|
||||
fun convertToUser(userEntity: UserEntity): User
|
||||
fun convertToPersistedUser(userEntity: UserEntity): PersistedUser
|
||||
fun convertToEntity(user: User): UserEntity
|
||||
}
|
||||
|
||||
internal class UserEntityFactory : Entity.Factory<UserEntity>()
|
||||
@@ -1 +0,0 @@
|
||||
package be.simplenotes.persistance
|
||||
@@ -1,121 +0,0 @@
|
||||
package be.simplenotes.persistance.users
|
||||
|
||||
import be.simplenotes.domain.model.User
|
||||
import be.simplenotes.domain.usecases.repositories.UserRepository
|
||||
import be.simplenotes.persistance.DbMigrations
|
||||
import be.simplenotes.persistance.migrationModule
|
||||
import be.simplenotes.persistance.persistanceModule
|
||||
import be.simplenotes.shared.config.DataSourceConfig
|
||||
import me.liuwj.ktorm.database.*
|
||||
import me.liuwj.ktorm.dsl.*
|
||||
import me.liuwj.ktorm.entity.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.flywaydb.core.Flyway
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.parallel.ResourceLock
|
||||
import org.koin.dsl.koinApplication
|
||||
import org.koin.dsl.module
|
||||
import javax.sql.DataSource
|
||||
|
||||
@ResourceLock("h2")
|
||||
internal class UserRepositoryImplTest {
|
||||
|
||||
// region setup
|
||||
private val testModule = module {
|
||||
single { dataSourceConfig() }
|
||||
}
|
||||
|
||||
private val koinApp = koinApplication {
|
||||
modules(persistanceModule, migrationModule, testModule)
|
||||
}
|
||||
|
||||
private fun dataSourceConfig() = DataSourceConfig(
|
||||
jdbcUrl = "jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;",
|
||||
driverClassName = "org.h2.Driver",
|
||||
username = "h2",
|
||||
password = "",
|
||||
maximumPoolSize = 2,
|
||||
connectionTimeout = 3000
|
||||
)
|
||||
|
||||
private val koin = koinApp.koin
|
||||
|
||||
@AfterAll
|
||||
fun afterAll() = koinApp.close()
|
||||
|
||||
private val migration = koin.get<DbMigrations>()
|
||||
private val dataSource = koin.get<DataSource>()
|
||||
private val userRepo = koin.get<UserRepository>()
|
||||
private val db = koin.get<Database>()
|
||||
|
||||
@BeforeEach
|
||||
fun beforeEach() {
|
||||
Flyway.configure()
|
||||
.dataSource(dataSource)
|
||||
.load()
|
||||
.clean()
|
||||
migration.migrate()
|
||||
}
|
||||
// endregion setup
|
||||
|
||||
@Test
|
||||
fun `insert user`() {
|
||||
val user = User("username", "test")
|
||||
assertThat(userRepo.create(user))
|
||||
.isNotNull
|
||||
.hasFieldOrPropertyWithValue("username", user.username)
|
||||
.hasFieldOrPropertyWithValue("password", user.password)
|
||||
|
||||
assertThat(db.users.find { it.username eq user.username }).isNotNull
|
||||
assertThat(db.users.toList()).hasSize(1)
|
||||
assertThat(userRepo.create(user)).isNull()
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Query {
|
||||
|
||||
@Test
|
||||
fun `query existing user`() {
|
||||
val user = User("username", "test")
|
||||
userRepo.create(user)
|
||||
|
||||
val foundUserMaybe = userRepo.find(user.username)
|
||||
assertThat(foundUserMaybe).isNotNull
|
||||
val foundUser = foundUserMaybe!!
|
||||
assertThat(foundUser).isEqualToIgnoringGivenFields(user, "id")
|
||||
assertThat(userRepo.exists(user.username)).isTrue
|
||||
assertThat(userRepo.find(foundUser.id)).isEqualTo(foundUser)
|
||||
assertThat(userRepo.exists(foundUser.id)).isTrue
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `query non existing user`() {
|
||||
assertThat(userRepo.find("I don't exist")).isNull()
|
||||
assertThat(userRepo.find(1)).isNull()
|
||||
assertThat(userRepo.exists(1)).isFalse
|
||||
assertThat(userRepo.exists("I don't exist")).isFalse
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Delete {
|
||||
|
||||
@Test
|
||||
fun `delete existing user`() {
|
||||
val user = User("username", "test")
|
||||
userRepo.create(user)
|
||||
|
||||
val foundUser = userRepo.find(user.username)!!
|
||||
assertThat(userRepo.delete(foundUser.id)).isTrue
|
||||
assertThat(db.users.toList()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete non existing user`() {
|
||||
assertThat(userRepo.delete(1)).isFalse
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<modules>
|
||||
<module>persistance</module>
|
||||
<module>app</module>
|
||||
<module>domain</module>
|
||||
<module>shared</module>
|
||||
<module>search</module>
|
||||
</modules>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<java.version>14</java.version>
|
||||
<kotlin.version>1.4.10</kotlin.version>
|
||||
<junit.version>5.6.2</junit.version>
|
||||
|
||||
<kotlin.code.style>official</kotlin.code.style>
|
||||
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
|
||||
|
||||
<org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
|
||||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.surefire</groupId>
|
||||
<artifactId>surefire-junit-platform</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<version>3.0.0-M3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>enforce</id>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<banDuplicatePomDependencyVersions/>
|
||||
<requireMavenVersion>
|
||||
<version>3.6</version>
|
||||
</requireMavenVersion>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>kapt</id>
|
||||
<goals>
|
||||
<goal>kapt</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<annotationProcessorPath>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</annotationProcessorPath>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<args>
|
||||
<arg>-Xno-param-assertions</arg>
|
||||
<arg>-Xno-call-assertions</arg>
|
||||
<arg>-Xno-receiver-assertions</arg>
|
||||
</args>
|
||||
<compilerPlugins>
|
||||
<plugin>kotlinx-serialization</plugin>
|
||||
</compilerPlugins>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-serialization</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-bom</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-serialization-json-jvm</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.koin</groupId>
|
||||
<artifactId>koin-core</artifactId>
|
||||
<version>2.1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.arrow-kt</groupId>
|
||||
<artifactId>arrow-core</artifactId>
|
||||
<version>0.10.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.25</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>me.liuwj.ktorm</groupId>
|
||||
<artifactId>ktorm-core</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.liuwj.ktorm</groupId>
|
||||
<artifactId>ktorm-support-mysql</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- region tests -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.mockk</groupId>
|
||||
<artifactId>mockk</artifactId>
|
||||
<version>1.10.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.natpryce</groupId>
|
||||
<artifactId>hamkrest</artifactId>
|
||||
<version>1.7.0.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.16.1</version>
|
||||
</dependency>
|
||||
<!-- endregion -->
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jcenter</id>
|
||||
<url>https://jcenter.bintray.com</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>arrow</id>
|
||||
<url>https://dl.bintray.com/arrow-kt/arrow-kt/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>kotlinx</id>
|
||||
<url>https://kotlin.bintray.com/kotlinx</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
</project>
|
||||
@@ -1,66 +0,0 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>search</artifactId>
|
||||
|
||||
<properties>
|
||||
<lucene.version>8.6.1</lucene.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>domain</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-core</artifactId>
|
||||
<version>${lucene.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-analyzers-common</artifactId>
|
||||
<version>${lucene.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-queryparser</artifactId>
|
||||
<version>${lucene.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<artifactId>shared</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -1,9 +0,0 @@
|
||||
package be.simplenotes.search
|
||||
|
||||
import be.simplenotes.domain.usecases.search.NoteSearcher
|
||||
import org.koin.dsl.module
|
||||
import java.nio.file.Path
|
||||
|
||||
val searchModule = module {
|
||||
single<NoteSearcher> { NoteSearcherImpl(Path.of(".lucene")) }
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package be.simplenotes.search.utils
|
||||
|
||||
import java.io.IOException
|
||||
import java.nio.file.FileVisitResult
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.SimpleFileVisitor
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
|
||||
internal fun rmdir(path: Path) {
|
||||
try {
|
||||
Files.walkFileTree(
|
||||
path,
|
||||
object : SimpleFileVisitor<Path>() {
|
||||
override fun visitFile(file: Path, attrs: BasicFileAttributes?): FileVisitResult {
|
||||
Files.delete(file)
|
||||
return FileVisitResult.CONTINUE
|
||||
}
|
||||
|
||||
override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult {
|
||||
Files.delete(dir)
|
||||
return FileVisitResult.CONTINUE
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
// This is fine
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
rootProject.name = "simplenotes"
|
||||
include(":simplenotes-config")
|
||||
include(":simplenotes-views")
|
||||
include(":simplenotes-app")
|
||||
include(":simplenotes-domain")
|
||||
include(":simplenotes-search")
|
||||
include(":simplenotes-types")
|
||||
include(":simplenotes-persistance")
|
||||
@@ -1,41 +0,0 @@
|
||||
<?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">
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>be.simplenotes</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>shared</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.arrow-kt</groupId>
|
||||
<artifactId>arrow-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.natpryce</groupId>
|
||||
<artifactId>hamkrest</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1 +0,0 @@
|
||||
package be.simplenotes.shared
|
||||
@@ -0,0 +1,33 @@
|
||||
import be.simplenotes.Libs
|
||||
|
||||
plugins {
|
||||
id("be.simplenotes.base")
|
||||
id("be.simplenotes.kotlinx-serialization")
|
||||
id("be.simplenotes.app-shadow")
|
||||
id("be.simplenotes.app-css")
|
||||
id("be.simplenotes.app-docker")
|
||||
kotlin("kapt")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":simplenotes-domain"))
|
||||
implementation(project(":simplenotes-types"))
|
||||
implementation(project(":simplenotes-config"))
|
||||
implementation(project(":simplenotes-views"))
|
||||
|
||||
implementation(Libs.arrowCoreData)
|
||||
implementation(Libs.konform)
|
||||
implementation(Libs.http4kCore)
|
||||
implementation(Libs.jettyServer)
|
||||
implementation(Libs.jettyServlet)
|
||||
implementation(Libs.javaxServlet)
|
||||
implementation(Libs.kotlinxSerializationJson)
|
||||
implementation(Libs.logbackClassic)
|
||||
|
||||
implementation(Libs.micronaut)
|
||||
kapt(Libs.micronautProcessor)
|
||||
|
||||
testImplementation(Libs.junit)
|
||||
testImplementation(Libs.assertJ)
|
||||
testImplementation(Libs.http4kTestingHamkrest)
|
||||
}
|
||||
+8
-2
@@ -2,20 +2,26 @@ package be.simplenotes.app
|
||||
|
||||
import org.http4k.server.Http4kServer
|
||||
import org.slf4j.LoggerFactory
|
||||
import be.simplenotes.shared.config.ServerConfig as SimpleNotesServerConfig
|
||||
import javax.annotation.PostConstruct
|
||||
import javax.annotation.PreDestroy
|
||||
import javax.inject.Singleton
|
||||
import be.simplenotes.config.ServerConfig as SimpleNotesServerConfig
|
||||
|
||||
@Singleton
|
||||
class Server(
|
||||
private val config: SimpleNotesServerConfig,
|
||||
private val http4kServer: Http4kServer,
|
||||
) {
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
@PostConstruct
|
||||
fun start(): Server {
|
||||
http4kServer.start()
|
||||
logger.info("Listening on http://${config.host}:${config.port}")
|
||||
logger.info("Listening on http://${config.host}:${http4kServer.port()}")
|
||||
return this
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
fun stop() {
|
||||
logger.info("Stopping server")
|
||||
http4kServer.close()
|
||||
@@ -0,0 +1,10 @@
|
||||
package be.simplenotes.app
|
||||
|
||||
import io.micronaut.context.ApplicationContext
|
||||
import java.lang.Runtime.getRuntime
|
||||
|
||||
fun main() {
|
||||
val ctx = ApplicationContext.run()
|
||||
ctx.createBean(Server::class.java)
|
||||
getRuntime().addShutdownHook(Thread { ctx.stop() })
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package be.simplenotes.app.api
|
||||
|
||||
import be.simplenotes.app.extensions.auto
|
||||
import be.simplenotes.domain.usecases.NoteService
|
||||
import be.simplenotes.types.LoggedInUser
|
||||
import be.simplenotes.types.PersistedNote
|
||||
import be.simplenotes.types.PersistedNoteMetadata
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
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 java.util.*
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ApiNoteController(
|
||||
json: Json,
|
||||
private val noteService: NoteService,
|
||||
) {
|
||||
|
||||
fun createNote(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val content = noteContentLens(request)
|
||||
return noteService.create(loggedInUser, content).fold(
|
||||
{ Response(BAD_REQUEST) },
|
||||
{ uuidContentLens(UuidContent(it.uuid), Response(OK)) }
|
||||
)
|
||||
}
|
||||
|
||||
fun notes(@Suppress("UNUSED_PARAMETER") request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val notes = noteService.paginatedNotes(loggedInUser.userId, page = 1).notes
|
||||
return persistedNotesMetadataLens(notes, Response(OK))
|
||||
}
|
||||
|
||||
fun note(request: Request, loggedInUser: LoggedInUser): Response =
|
||||
noteService.find(loggedInUser.userId, uuidLens(request))
|
||||
?.let { persistedNoteLens(it, Response(OK)) }
|
||||
?: Response(NOT_FOUND)
|
||||
|
||||
fun update(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val content = noteContentLens(request)
|
||||
return noteService.update(loggedInUser, uuidLens(request), content).fold(
|
||||
{
|
||||
Response(BAD_REQUEST)
|
||||
},
|
||||
{
|
||||
if (it == null) Response(NOT_FOUND)
|
||||
else Response(OK)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun search(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val query = searchContentLens(request)
|
||||
val notes = noteService.search(loggedInUser.userId, query)
|
||||
return persistedNotesMetadataLens(notes, Response(OK))
|
||||
}
|
||||
|
||||
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
|
||||
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,31 @@
|
||||
package be.simplenotes.app.api
|
||||
|
||||
import be.simplenotes.app.extensions.auto
|
||||
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.Companion.BAD_REQUEST
|
||||
import org.http4k.core.Status.Companion.OK
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ApiUserController(
|
||||
json: Json,
|
||||
private val userService: UserService,
|
||||
) {
|
||||
private val tokenLens = json.auto<Token>().toLens()
|
||||
private val loginFormLens = json.auto<LoginForm>().toLens()
|
||||
|
||||
fun login(request: Request) = userService
|
||||
.login(loginFormLens(request))
|
||||
.fold(
|
||||
{ Response(BAD_REQUEST) },
|
||||
{ tokenLens(Token(it), Response(OK)) }
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Token(val token: String)
|
||||
@@ -0,0 +1,15 @@
|
||||
package be.simplenotes.app.controllers
|
||||
|
||||
import be.simplenotes.app.extensions.html
|
||||
import be.simplenotes.types.LoggedInUser
|
||||
import be.simplenotes.views.BaseView
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.Response
|
||||
import org.http4k.core.Status.Companion.OK
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class BaseController(private val view: BaseView) {
|
||||
fun index(@Suppress("UNUSED_PARAMETER") request: Request, loggedInUser: LoggedInUser?) =
|
||||
Response(OK).html(view.renderHome(loggedInUser))
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package be.simplenotes.app.controllers
|
||||
|
||||
import be.simplenotes.domain.usecases.HealthCheckService
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.Response
|
||||
import org.http4k.core.Status.Companion.OK
|
||||
import org.http4k.core.Status.Companion.SERVICE_UNAVAILABLE
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class HealthCheckController(private val healthCheckService: HealthCheckService) {
|
||||
fun healthCheck(@Suppress("UNUSED_PARAMETER") request: Request) =
|
||||
if (healthCheckService.isOk()) Response(OK) else Response(SERVICE_UNAVAILABLE)
|
||||
}
|
||||
+55
-39
@@ -2,13 +2,12 @@ package be.simplenotes.app.controllers
|
||||
|
||||
import be.simplenotes.app.extensions.html
|
||||
import be.simplenotes.app.extensions.redirect
|
||||
import be.simplenotes.app.utils.parseSearchTerms
|
||||
import be.simplenotes.app.views.NoteView
|
||||
import be.simplenotes.domain.security.JwtPayload
|
||||
import be.simplenotes.domain.usecases.NoteService
|
||||
import be.simplenotes.domain.usecases.markdown.InvalidMeta
|
||||
import be.simplenotes.domain.usecases.markdown.MissingMeta
|
||||
import be.simplenotes.domain.usecases.markdown.ValidationError
|
||||
import be.simplenotes.types.LoggedInUser
|
||||
import be.simplenotes.views.NoteView
|
||||
import org.http4k.core.Method
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.Response
|
||||
@@ -18,25 +17,35 @@ import org.http4k.core.Status.Companion.OK
|
||||
import org.http4k.core.body.form
|
||||
import org.http4k.routing.path
|
||||
import java.util.*
|
||||
import javax.inject.Singleton
|
||||
import kotlin.math.abs
|
||||
|
||||
@Singleton
|
||||
class NoteController(
|
||||
private val view: NoteView,
|
||||
private val noteService: NoteService,
|
||||
) {
|
||||
|
||||
fun new(request: Request, jwtPayload: JwtPayload): Response {
|
||||
if (request.method == Method.GET) return Response(OK).html(view.noteEditor(jwtPayload))
|
||||
fun new(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
if (request.method == Method.GET) return Response(OK).html(view.noteEditor(loggedInUser))
|
||||
|
||||
val markdownForm = request.form("markdown") ?: ""
|
||||
|
||||
return noteService.create(jwtPayload.userId, markdownForm).fold(
|
||||
return noteService.create(loggedInUser, markdownForm).fold(
|
||||
{
|
||||
val html = when (it) {
|
||||
MissingMeta -> view.noteEditor(jwtPayload, error = "Missing note metadata", textarea = markdownForm)
|
||||
InvalidMeta -> view.noteEditor(jwtPayload, error = "Invalid note metadata", textarea = markdownForm)
|
||||
MissingMeta -> view.noteEditor(
|
||||
loggedInUser,
|
||||
error = "Missing note metadata",
|
||||
textarea = markdownForm
|
||||
)
|
||||
InvalidMeta -> view.noteEditor(
|
||||
loggedInUser,
|
||||
error = "Invalid note metadata",
|
||||
textarea = markdownForm
|
||||
)
|
||||
is ValidationError -> view.noteEditor(
|
||||
jwtPayload,
|
||||
loggedInUser,
|
||||
validationErrors = it.validationErrors,
|
||||
textarea = markdownForm
|
||||
)
|
||||
@@ -49,66 +58,73 @@ class NoteController(
|
||||
)
|
||||
}
|
||||
|
||||
fun list(request: Request, jwtPayload: JwtPayload): Response {
|
||||
fun list(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val currentPage = request.query("page")?.toIntOrNull()?.let(::abs) ?: 1
|
||||
val tag = request.query("tag")
|
||||
val (pages, notes) = noteService.paginatedNotes(jwtPayload.userId, currentPage, tag = tag)
|
||||
val deletedCount = noteService.countDeleted(jwtPayload.userId)
|
||||
return Response(OK).html(view.notes(jwtPayload, notes, currentPage, pages, deletedCount, tag = tag))
|
||||
val (pages, notes) = noteService.paginatedNotes(loggedInUser.userId, currentPage, tag = tag)
|
||||
val deletedCount = noteService.countDeleted(loggedInUser.userId)
|
||||
return Response(OK).html(view.notes(loggedInUser, notes, currentPage, pages, deletedCount, tag = tag))
|
||||
}
|
||||
|
||||
fun search(request: Request, jwtPayload: JwtPayload): Response {
|
||||
fun search(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val query = request.form("search") ?: ""
|
||||
val terms = parseSearchTerms(query)
|
||||
val notes = noteService.search(jwtPayload.userId, terms)
|
||||
val deletedCount = noteService.countDeleted(jwtPayload.userId)
|
||||
return Response(OK).html(view.search(jwtPayload, notes, query, deletedCount))
|
||||
val notes = noteService.search(loggedInUser.userId, query)
|
||||
val deletedCount = noteService.countDeleted(loggedInUser.userId)
|
||||
return Response(OK).html(view.search(loggedInUser, notes, query, deletedCount))
|
||||
}
|
||||
|
||||
fun note(request: Request, jwtPayload: JwtPayload): Response {
|
||||
fun note(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
||||
|
||||
if (request.method == Method.POST) {
|
||||
if (request.form("delete") != null) {
|
||||
return if (noteService.trash(jwtPayload.userId, noteUuid))
|
||||
return if (noteService.trash(loggedInUser.userId, noteUuid))
|
||||
Response.redirect("/notes") // TODO: flash cookie to show success ?
|
||||
else
|
||||
Response(NOT_FOUND) // TODO: show an error
|
||||
}
|
||||
if (request.form("public") != null) {
|
||||
if (!noteService.makePublic(jwtPayload.userId, noteUuid)) return Response(NOT_FOUND)
|
||||
if (!noteService.makePublic(loggedInUser.userId, noteUuid)) return Response(NOT_FOUND)
|
||||
} else if (request.form("private") != null) {
|
||||
if (!noteService.makePrivate(jwtPayload.userId, noteUuid)) return Response(NOT_FOUND)
|
||||
if (!noteService.makePrivate(loggedInUser.userId, noteUuid)) return Response(NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
||||
val note = noteService.find(jwtPayload.userId, noteUuid) ?: return Response(NOT_FOUND)
|
||||
return Response(OK).html(view.renderedNote(jwtPayload, note, shared = false))
|
||||
val note = noteService.find(loggedInUser.userId, noteUuid) ?: return Response(NOT_FOUND)
|
||||
return Response(OK).html(view.renderedNote(loggedInUser, note, shared = false))
|
||||
}
|
||||
|
||||
fun public(request: Request, jwtPayload: JwtPayload?): Response {
|
||||
fun public(request: Request, loggedInUser: LoggedInUser?): Response {
|
||||
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
||||
val note = noteService.findPublic(noteUuid) ?: return Response(NOT_FOUND)
|
||||
return Response(OK).html(view.renderedNote(jwtPayload, note, shared = true))
|
||||
return Response(OK).html(view.renderedNote(loggedInUser, note, shared = true))
|
||||
}
|
||||
|
||||
fun edit(request: Request, jwtPayload: JwtPayload): Response {
|
||||
fun edit(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val noteUuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
||||
val note = noteService.find(jwtPayload.userId, noteUuid) ?: return Response(NOT_FOUND)
|
||||
val note = noteService.find(loggedInUser.userId, noteUuid) ?: return Response(NOT_FOUND)
|
||||
|
||||
if (request.method == Method.GET) {
|
||||
return Response(OK).html(view.noteEditor(jwtPayload, textarea = note.markdown))
|
||||
return Response(OK).html(view.noteEditor(loggedInUser, textarea = note.markdown))
|
||||
}
|
||||
|
||||
val markdownForm = request.form("markdown") ?: ""
|
||||
|
||||
return noteService.update(jwtPayload.userId, note.uuid, markdownForm).fold(
|
||||
return noteService.update(loggedInUser, note.uuid, markdownForm).fold(
|
||||
{
|
||||
val html = when (it) {
|
||||
MissingMeta -> view.noteEditor(jwtPayload, error = "Missing note metadata", textarea = markdownForm)
|
||||
InvalidMeta -> view.noteEditor(jwtPayload, error = "Invalid note metadata", textarea = markdownForm)
|
||||
MissingMeta -> view.noteEditor(
|
||||
loggedInUser,
|
||||
error = "Missing note metadata",
|
||||
textarea = markdownForm
|
||||
)
|
||||
InvalidMeta -> view.noteEditor(
|
||||
loggedInUser,
|
||||
error = "Invalid note metadata",
|
||||
textarea = markdownForm
|
||||
)
|
||||
is ValidationError -> view.noteEditor(
|
||||
jwtPayload,
|
||||
loggedInUser,
|
||||
validationErrors = it.validationErrors,
|
||||
textarea = markdownForm
|
||||
)
|
||||
@@ -121,21 +137,21 @@ class NoteController(
|
||||
)
|
||||
}
|
||||
|
||||
fun trash(request: Request, jwtPayload: JwtPayload): Response {
|
||||
fun trash(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val currentPage = request.query("page")?.toIntOrNull()?.let(::abs) ?: 1
|
||||
val tag = request.query("tag")
|
||||
val (pages, notes) = noteService.paginatedNotes(jwtPayload.userId, currentPage, tag = tag, deleted = true)
|
||||
return Response(OK).html(view.trash(jwtPayload, notes, currentPage, pages))
|
||||
val (pages, notes) = noteService.paginatedNotes(loggedInUser.userId, currentPage, tag = tag, deleted = true)
|
||||
return Response(OK).html(view.trash(loggedInUser, notes, currentPage, pages))
|
||||
}
|
||||
|
||||
fun deleted(request: Request, jwtPayload: JwtPayload): Response {
|
||||
fun deleted(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val uuid = request.uuidPath() ?: return Response(NOT_FOUND)
|
||||
return if (request.form("delete") != null)
|
||||
if (noteService.delete(jwtPayload.userId, uuid))
|
||||
if (noteService.delete(loggedInUser.userId, uuid))
|
||||
Response.redirect("/notes/trash")
|
||||
else
|
||||
Response(NOT_FOUND)
|
||||
else if (noteService.restore(jwtPayload.userId, uuid))
|
||||
else if (noteService.restore(loggedInUser.userId, uuid))
|
||||
Response.redirect("/notes/$uuid")
|
||||
else
|
||||
Response(NOT_FOUND)
|
||||
+19
-14
@@ -2,24 +2,26 @@ package be.simplenotes.app.controllers
|
||||
|
||||
import be.simplenotes.app.extensions.html
|
||||
import be.simplenotes.app.extensions.redirect
|
||||
import be.simplenotes.app.views.SettingView
|
||||
import be.simplenotes.domain.security.JwtPayload
|
||||
import be.simplenotes.domain.usecases.UserService
|
||||
import be.simplenotes.domain.usecases.users.delete.DeleteError
|
||||
import be.simplenotes.domain.usecases.users.delete.DeleteForm
|
||||
import be.simplenotes.types.LoggedInUser
|
||||
import be.simplenotes.views.SettingView
|
||||
import org.http4k.core.*
|
||||
import org.http4k.core.body.form
|
||||
import org.http4k.core.cookie.invalidateCookie
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SettingsController(
|
||||
private val userService: UserService,
|
||||
private val settingView: SettingView,
|
||||
) {
|
||||
fun settings(request: Request, jwtPayload: JwtPayload): Response {
|
||||
fun settings(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
if (request.method == Method.GET)
|
||||
return Response(Status.OK).html(settingView.settings(jwtPayload))
|
||||
return Response(Status.OK).html(settingView.settings(loggedInUser))
|
||||
|
||||
val deleteForm = request.deleteForm(jwtPayload)
|
||||
val deleteForm = request.deleteForm(loggedInUser)
|
||||
val result = userService.delete(deleteForm)
|
||||
|
||||
return result.fold(
|
||||
@@ -28,13 +30,13 @@ class SettingsController(
|
||||
DeleteError.Unregistered -> Response.redirect("/").invalidateCookie("Bearer")
|
||||
DeleteError.WrongPassword -> Response(Status.OK).html(
|
||||
settingView.settings(
|
||||
jwtPayload,
|
||||
loggedInUser,
|
||||
error = "Wrong password"
|
||||
)
|
||||
)
|
||||
is DeleteError.InvalidForm -> Response(Status.OK).html(
|
||||
settingView.settings(
|
||||
jwtPayload,
|
||||
loggedInUser,
|
||||
validationErrors = it.validationErrors
|
||||
)
|
||||
)
|
||||
@@ -53,23 +55,26 @@ class SettingsController(
|
||||
.header("Content-Type", contentType)
|
||||
}
|
||||
|
||||
fun export(request: Request, jwtPayload: JwtPayload): Response {
|
||||
fun export(request: Request, loggedInUser: LoggedInUser): Response {
|
||||
val isDownload = request.form("download") != null
|
||||
|
||||
return if (isDownload) {
|
||||
val filename = "simplenotes-export-${jwtPayload.username}"
|
||||
val filename = "simplenotes-export-${loggedInUser.username}"
|
||||
if (request.form("format") == "zip") {
|
||||
val zip = userService.exportAsZip(jwtPayload.userId)
|
||||
val zip = userService.exportAsZip(loggedInUser.userId)
|
||||
Response(Status.OK)
|
||||
.with(attachment("$filename.zip", "application/zip"))
|
||||
.body(zip)
|
||||
} else
|
||||
Response(Status.OK)
|
||||
.with(attachment("$filename.json", "application/json"))
|
||||
.body(userService.exportAsJson(jwtPayload.userId))
|
||||
} else Response(Status.OK).body(userService.exportAsJson(jwtPayload.userId)).header("Content-Type", "application/json")
|
||||
.body(userService.exportAsJson(loggedInUser.userId))
|
||||
} else Response(Status.OK).body(userService.exportAsJson(loggedInUser.userId)).header(
|
||||
"Content-Type",
|
||||
"application/json"
|
||||
)
|
||||
}
|
||||
|
||||
private fun Request.deleteForm(jwtPayload: JwtPayload) =
|
||||
DeleteForm(jwtPayload.username, form("password"), form("checked") != null)
|
||||
private fun Request.deleteForm(loggedInUser: LoggedInUser) =
|
||||
DeleteForm(loggedInUser.username, form("password"), form("checked") != null)
|
||||
}
|
||||
+14
-12
@@ -3,14 +3,14 @@ package be.simplenotes.app.controllers
|
||||
import be.simplenotes.app.extensions.html
|
||||
import be.simplenotes.app.extensions.isSecure
|
||||
import be.simplenotes.app.extensions.redirect
|
||||
import be.simplenotes.app.views.UserView
|
||||
import be.simplenotes.domain.security.JwtPayload
|
||||
import be.simplenotes.config.JwtConfig
|
||||
import be.simplenotes.domain.usecases.UserService
|
||||
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.shared.config.JwtConfig
|
||||
import be.simplenotes.types.LoggedInUser
|
||||
import be.simplenotes.views.UserView
|
||||
import org.http4k.core.Method.GET
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.Response
|
||||
@@ -21,15 +21,17 @@ import org.http4k.core.cookie.SameSite
|
||||
import org.http4k.core.cookie.cookie
|
||||
import org.http4k.core.cookie.invalidateCookie
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class UserController(
|
||||
private val userService: UserService,
|
||||
private val userView: UserView,
|
||||
private val jwtConfig: JwtConfig,
|
||||
) {
|
||||
fun register(request: Request, jwtPayload: JwtPayload?): Response {
|
||||
fun register(request: Request, loggedInUser: LoggedInUser?): Response {
|
||||
if (request.method == GET) return Response(OK).html(
|
||||
userView.register(jwtPayload)
|
||||
userView.register(loggedInUser)
|
||||
)
|
||||
|
||||
val result = userService.register(request.registerForm())
|
||||
@@ -38,12 +40,12 @@ class UserController(
|
||||
{
|
||||
val html = when (it) {
|
||||
UserExists -> userView.register(
|
||||
jwtPayload,
|
||||
loggedInUser,
|
||||
error = "User already exists"
|
||||
)
|
||||
is InvalidRegisterForm ->
|
||||
userView.register(
|
||||
jwtPayload,
|
||||
loggedInUser,
|
||||
validationErrors = it.validationErrors
|
||||
)
|
||||
}
|
||||
@@ -58,9 +60,9 @@ class UserController(
|
||||
private fun Request.registerForm() = RegisterForm(form("username"), form("password"))
|
||||
private fun Request.loginForm(): LoginForm = registerForm()
|
||||
|
||||
fun login(request: Request, jwtPayload: JwtPayload?): Response {
|
||||
fun login(request: Request, loggedInUser: LoggedInUser?): Response {
|
||||
if (request.method == GET) return Response(OK).html(
|
||||
userView.login(jwtPayload)
|
||||
userView.login(loggedInUser)
|
||||
)
|
||||
|
||||
val result = userService.login(request.loginForm())
|
||||
@@ -70,17 +72,17 @@ class UserController(
|
||||
val html = when (it) {
|
||||
Unregistered ->
|
||||
userView.login(
|
||||
jwtPayload,
|
||||
loggedInUser,
|
||||
error = "User does not exist"
|
||||
)
|
||||
WrongPassword ->
|
||||
userView.login(
|
||||
jwtPayload,
|
||||
loggedInUser,
|
||||
error = "Wrong password"
|
||||
)
|
||||
is InvalidLoginForm ->
|
||||
userView.login(
|
||||
jwtPayload,
|
||||
loggedInUser,
|
||||
validationErrors = it.validationErrors
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package be.simplenotes.app.extensions
|
||||
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.http4k.asString
|
||||
import org.http4k.core.Body
|
||||
import org.http4k.core.ContentType
|
||||
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
|
||||
import org.http4k.lens.*
|
||||
|
||||
fun Response.html(html: String) = body(html)
|
||||
.header("Content-Type", "text/html; charset=utf-8")
|
||||
.header("Cache-Control", "no-cache")
|
||||
|
||||
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
|
||||
|
||||
val bodyLens = httpBodyRoot(
|
||||
listOf(Meta(true, "body", ParamMeta.ObjectParam, "body")),
|
||||
ContentType.APPLICATION_JSON.withNoDirectives(),
|
||||
ContentNegotiation.StrictNoDirective
|
||||
).map(
|
||||
{ it.payload.asString() },
|
||||
{ Body(it) }
|
||||
)
|
||||
|
||||
inline fun <reified T> Json.auto(): BiDiBodyLensSpec<T> = bodyLens.map(
|
||||
{ decodeFromString(it) },
|
||||
{ encodeToString(it) }
|
||||
)
|
||||
+4
-2
@@ -1,8 +1,8 @@
|
||||
package be.simplenotes.app.filters
|
||||
|
||||
import be.simplenotes.app.extensions.html
|
||||
import be.simplenotes.app.views.ErrorView
|
||||
import be.simplenotes.app.views.ErrorView.Type.*
|
||||
import be.simplenotes.views.ErrorView
|
||||
import be.simplenotes.views.ErrorView.Type.*
|
||||
import org.http4k.core.*
|
||||
import org.http4k.core.Status.Companion.INTERNAL_SERVER_ERROR
|
||||
import org.http4k.core.Status.Companion.NOT_FOUND
|
||||
@@ -10,7 +10,9 @@ import org.http4k.core.Status.Companion.NOT_IMPLEMENTED
|
||||
import org.http4k.core.Status.Companion.SERVICE_UNAVAILABLE
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.sql.SQLTransientException
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ErrorFilter(private val errorView: ErrorView) : Filter {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
+5
-1
@@ -3,9 +3,13 @@ package be.simplenotes.app.filters
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.HttpHandler
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.Status.Companion.OK
|
||||
|
||||
object ImmutableFilter : Filter {
|
||||
override fun invoke(next: HttpHandler) = { request: Request ->
|
||||
next(request).header("Cache-Control", "public, max-age=31536000, immutable")
|
||||
val res = next(request)
|
||||
if (res.status == OK)
|
||||
res.header("Cache-Control", "public, max-age=31536000, immutable")
|
||||
else res
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package be.simplenotes.app.filters.auth
|
||||
|
||||
import be.simplenotes.types.LoggedInUser
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.cookie.cookie
|
||||
import org.http4k.lens.BiDiLens
|
||||
|
||||
typealias OptionalAuthLens = BiDiLens<@JvmSuppressWildcards Request, @JvmSuppressWildcards LoggedInUser?>
|
||||
typealias RequiredAuthLens = BiDiLens<@JvmSuppressWildcards Request, @JvmSuppressWildcards LoggedInUser>
|
||||
|
||||
enum class JwtSource {
|
||||
Header, Cookie
|
||||
}
|
||||
|
||||
fun Request.bearerTokenCookie(): String? = cookie("Bearer")
|
||||
?.value
|
||||
?.trim()
|
||||
|
||||
fun Request.bearerTokenHeader(): String? =
|
||||
header("Authorization")
|
||||
?.trim()
|
||||
?.takeIf { it.startsWith("Bearer") }
|
||||
?.substringAfter("Bearer")
|
||||
?.trim()
|
||||
@@ -0,0 +1,22 @@
|
||||
package be.simplenotes.app.filters.auth
|
||||
|
||||
import be.simplenotes.app.filters.auth.JwtSource.Cookie
|
||||
import be.simplenotes.domain.security.JwtPayloadExtractor
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.HttpHandler
|
||||
import org.http4k.core.with
|
||||
|
||||
class OptionalAuthFilter(
|
||||
private val extractor: JwtPayloadExtractor,
|
||||
private val lens: OptionalAuthLens,
|
||||
private val source: JwtSource = Cookie,
|
||||
) : Filter {
|
||||
override fun invoke(next: HttpHandler): HttpHandler = {
|
||||
val token = when (source) {
|
||||
JwtSource.Header -> it.bearerTokenHeader()
|
||||
Cookie -> it.bearerTokenCookie()
|
||||
}
|
||||
|
||||
next(it.with(lens of token?.let { extractor(it) }))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package be.simplenotes.app.filters.auth
|
||||
|
||||
import be.simplenotes.app.extensions.redirect
|
||||
import be.simplenotes.domain.security.JwtPayloadExtractor
|
||||
import org.http4k.core.Filter
|
||||
import org.http4k.core.HttpHandler
|
||||
import org.http4k.core.Response
|
||||
import org.http4k.core.Status.Companion.UNAUTHORIZED
|
||||
import org.http4k.core.with
|
||||
|
||||
class RequiredAuthFilter(
|
||||
private val extractor: JwtPayloadExtractor,
|
||||
private val lens: RequiredAuthLens,
|
||||
private val source: JwtSource = JwtSource.Cookie,
|
||||
private val redirect: Boolean = true,
|
||||
) : Filter {
|
||||
override fun invoke(next: HttpHandler): HttpHandler = {
|
||||
val token = when (source) {
|
||||
JwtSource.Header -> it.bearerTokenHeader()
|
||||
JwtSource.Cookie -> it.bearerTokenCookie()
|
||||
}
|
||||
val jwtPayload = token?.let { extractor(token) }
|
||||
|
||||
if (jwtPayload != null) next(it.with(lens of jwtPayload))
|
||||
else {
|
||||
if (redirect) Response.redirect("/login")
|
||||
else Response(UNAUTHORIZED)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package be.simplenotes.app.jetty
|
||||
|
||||
import org.eclipse.jetty.server.Server
|
||||
import org.eclipse.jetty.server.ServerConnector
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler.SESSIONS
|
||||
import org.eclipse.jetty.servlet.ServletHolder
|
||||
import org.http4k.core.HttpHandler
|
||||
import org.http4k.server.Http4kServer
|
||||
import org.http4k.server.ServerConfig
|
||||
import org.http4k.servlet.asServlet
|
||||
|
||||
class Jetty(private val port: Int, private val server: Server) : ServerConfig {
|
||||
constructor(port: Int = 8000) : this(port, http(port))
|
||||
constructor(port: Int, vararg inConnectors: ConnectorBuilder) : this(
|
||||
port,
|
||||
Server().apply {
|
||||
inConnectors.forEach { addConnector(it(this)) }
|
||||
}
|
||||
)
|
||||
|
||||
override fun toServer(httpHandler: HttpHandler): Http4kServer {
|
||||
server.insertHandler(httpHandler.toJettyHandler())
|
||||
|
||||
return object : Http4kServer {
|
||||
override fun start(): Http4kServer = apply {
|
||||
server.start()
|
||||
}
|
||||
|
||||
override fun stop(): Http4kServer = apply { server.stop() }
|
||||
|
||||
override fun port(): Int = if (port > 0) port else server.uri.port
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun HttpHandler.toJettyHandler() = ServletContextHandler(SESSIONS).apply {
|
||||
addServlet(ServletHolder(this@toJettyHandler.asServlet()), "/*")
|
||||
}
|
||||
|
||||
typealias ConnectorBuilder = (Server) -> ServerConnector
|
||||
|
||||
fun http(httpPort: Int): ConnectorBuilder = { server: Server -> ServerConnector(server).apply { port = httpPort } }
|
||||
@@ -0,0 +1,46 @@
|
||||
package be.simplenotes.app.modules
|
||||
|
||||
import be.simplenotes.app.filters.auth.*
|
||||
import be.simplenotes.domain.security.JwtPayloadExtractor
|
||||
import io.micronaut.context.annotation.Factory
|
||||
import io.micronaut.context.annotation.Primary
|
||||
import org.http4k.core.RequestContexts
|
||||
import org.http4k.lens.RequestContextKey
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Factory
|
||||
class AuthModule {
|
||||
|
||||
@Singleton
|
||||
@Named("optional")
|
||||
fun optionalAuthLens(ctx: RequestContexts): OptionalAuthLens = RequestContextKey.optional(ctx)
|
||||
|
||||
@Singleton
|
||||
@Named("required")
|
||||
fun requiredAuthLens(ctx: RequestContexts): RequiredAuthLens = RequestContextKey.required(ctx)
|
||||
|
||||
@Singleton
|
||||
fun optionalAuth(extractor: JwtPayloadExtractor, @Named("optional") lens: OptionalAuthLens) =
|
||||
OptionalAuthFilter(extractor, lens)
|
||||
|
||||
@Primary
|
||||
@Singleton
|
||||
fun requiredAuth(extractor: JwtPayloadExtractor, @Named("required") lens: RequiredAuthLens) =
|
||||
RequiredAuthFilter(extractor, lens)
|
||||
|
||||
@Singleton
|
||||
@Named("api")
|
||||
internal fun apiAuthFilter(
|
||||
jwtPayloadExtractor: JwtPayloadExtractor,
|
||||
@Named("required") lens: RequiredAuthLens,
|
||||
) = RequiredAuthFilter(
|
||||
extractor = jwtPayloadExtractor,
|
||||
lens = lens,
|
||||
source = JwtSource.Header,
|
||||
redirect = false
|
||||
)
|
||||
|
||||
@Singleton
|
||||
fun requestContexts() = RequestContexts()
|
||||
}
|
||||
+9
-10
@@ -2,21 +2,20 @@ package be.simplenotes.app.modules
|
||||
|
||||
import be.simplenotes.app.serialization.LocalDateTimeSerializer
|
||||
import be.simplenotes.app.serialization.UuidSerializer
|
||||
import io.micronaut.context.annotation.Factory
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import org.koin.dsl.module
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
import javax.inject.Singleton
|
||||
|
||||
val jsonModule = module {
|
||||
single {
|
||||
Json {
|
||||
prettyPrint = true
|
||||
serializersModule = get()
|
||||
}
|
||||
}
|
||||
single {
|
||||
SerializersModule {
|
||||
@Factory
|
||||
class JsonModule {
|
||||
|
||||
@Singleton
|
||||
fun json() = Json {
|
||||
prettyPrint = true
|
||||
serializersModule = SerializersModule {
|
||||
contextual(LocalDateTime::class, LocalDateTimeSerializer())
|
||||
contextual(UUID::class, UuidSerializer())
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package be.simplenotes.app.modules
|
||||
|
||||
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.config.ServerConfig
|
||||
import io.micronaut.context.annotation.Factory
|
||||
import org.eclipse.jetty.server.ServerConnector
|
||||
import org.http4k.server.Http4kServer
|
||||
import org.http4k.server.asServer
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
import org.eclipse.jetty.server.Server as JettyServer
|
||||
import org.http4k.server.ServerConfig as Http4kServerConfig
|
||||
|
||||
@Factory
|
||||
class ServerModule {
|
||||
|
||||
@Singleton
|
||||
@Named("styles")
|
||||
fun styles(resolver: StaticFileResolver) = resolver.resolve("styles.css")!!
|
||||
|
||||
@Singleton
|
||||
fun http4kServer(router: Router, serverConfig: Http4kServerConfig): Http4kServer =
|
||||
router().asServer(serverConfig)
|
||||
|
||||
@Singleton
|
||||
fun http4kServerConfig(config: ServerConfig): Http4kServerConfig {
|
||||
val builder: ConnectorBuilder = { server: JettyServer ->
|
||||
ServerConnector(server).apply {
|
||||
port = config.port
|
||||
host = config.host
|
||||
}
|
||||
}
|
||||
return Jetty(config.port, builder)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package be.simplenotes.app.routes
|
||||
|
||||
import be.simplenotes.app.api.ApiNoteController
|
||||
import be.simplenotes.app.api.ApiUserController
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthFilter
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthLens
|
||||
import org.http4k.core.Method.*
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.then
|
||||
import org.http4k.routing.PathMethod
|
||||
import org.http4k.routing.RoutingHttpHandler
|
||||
import org.http4k.routing.bind
|
||||
import org.http4k.routing.routes
|
||||
import java.util.function.Supplier
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ApiRoutes(
|
||||
private val apiUserController: ApiUserController,
|
||||
private val apiNoteController: ApiNoteController,
|
||||
@Named("api") private val auth: RequiredAuthFilter,
|
||||
@Named("required") private val authLens: RequiredAuthLens,
|
||||
) : Supplier<RoutingHttpHandler> {
|
||||
override fun get(): RoutingHttpHandler {
|
||||
|
||||
infix fun PathMethod.to(action: ProtectedHandler) =
|
||||
this to { req: Request -> action(req, authLens(req)) }
|
||||
|
||||
return routes(
|
||||
"/login" bind POST to apiUserController::login,
|
||||
|
||||
with(apiNoteController) {
|
||||
auth.then(
|
||||
routes(
|
||||
"/" bind GET to ::notes,
|
||||
"/" bind POST to ::createNote,
|
||||
"/search" bind POST to ::search,
|
||||
"/{uuid}" bind GET to ::note,
|
||||
"/{uuid}" bind PUT to ::update,
|
||||
)
|
||||
).withBasePath("/notes")
|
||||
}
|
||||
|
||||
).withBasePath("/api")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package be.simplenotes.app.routes
|
||||
|
||||
import be.simplenotes.app.controllers.BaseController
|
||||
import be.simplenotes.app.controllers.HealthCheckController
|
||||
import be.simplenotes.app.controllers.NoteController
|
||||
import be.simplenotes.app.controllers.UserController
|
||||
import be.simplenotes.app.filters.ImmutableFilter
|
||||
import be.simplenotes.app.filters.auth.OptionalAuthFilter
|
||||
import be.simplenotes.app.filters.auth.OptionalAuthLens
|
||||
import org.http4k.core.ContentType
|
||||
import org.http4k.core.Method.GET
|
||||
import org.http4k.core.Method.POST
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.then
|
||||
import org.http4k.routing.*
|
||||
import java.util.function.Supplier
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class BasicRoutes(
|
||||
private val healthCheckController: HealthCheckController,
|
||||
private val baseCtrl: BaseController,
|
||||
private val userCtrl: UserController,
|
||||
private val noteCtrl: NoteController,
|
||||
@Named("optional") private val authLens: OptionalAuthLens,
|
||||
private val auth: OptionalAuthFilter,
|
||||
) : Supplier<RoutingHttpHandler> {
|
||||
|
||||
override fun get(): RoutingHttpHandler {
|
||||
|
||||
infix fun PathMethod.to(action: PublicHandler) =
|
||||
this to { req: Request -> action(req, authLens(req)) }
|
||||
|
||||
val staticHandler = ImmutableFilter.then(
|
||||
static(
|
||||
ResourceLoader.Classpath("/static"),
|
||||
"woff2" to ContentType("font/woff2"),
|
||||
"webmanifest" to ContentType("application/manifest+json")
|
||||
)
|
||||
)
|
||||
|
||||
return routes(
|
||||
auth.then(
|
||||
routes(
|
||||
"/" bind GET to baseCtrl::index,
|
||||
"/register" bind GET to userCtrl::register,
|
||||
"/register" bind POST to userCtrl::register,
|
||||
"/login" bind GET to userCtrl::login,
|
||||
"/login" bind POST to userCtrl::login,
|
||||
"/logout" bind POST to userCtrl::logout,
|
||||
"/notes/public/{uuid}" bind GET to noteCtrl::public,
|
||||
)
|
||||
),
|
||||
|
||||
"/health" bind GET to healthCheckController::healthCheck,
|
||||
staticHandler
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package be.simplenotes.app.routes
|
||||
|
||||
import be.simplenotes.app.controllers.NoteController
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthFilter
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthLens
|
||||
import org.http4k.core.Method.GET
|
||||
import org.http4k.core.Method.POST
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.then
|
||||
import org.http4k.routing.PathMethod
|
||||
import org.http4k.routing.RoutingHttpHandler
|
||||
import org.http4k.routing.bind
|
||||
import org.http4k.routing.routes
|
||||
import java.util.function.Supplier
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class NoteRoutes(
|
||||
private val noteCtrl: NoteController,
|
||||
private val auth: RequiredAuthFilter,
|
||||
@Named("required") private val authLens: RequiredAuthLens,
|
||||
) : Supplier<RoutingHttpHandler> {
|
||||
override fun get(): RoutingHttpHandler {
|
||||
|
||||
infix fun PathMethod.to(action: ProtectedHandler) =
|
||||
this to { req: Request -> action(req, authLens(req)) }
|
||||
|
||||
return auth.then(
|
||||
with(noteCtrl) {
|
||||
routes(
|
||||
"/" bind GET to ::list,
|
||||
"/" bind POST to ::search,
|
||||
"/new" bind GET to ::new,
|
||||
"/new" bind POST to ::new,
|
||||
"/trash" bind GET to ::trash,
|
||||
"/{uuid}" bind GET to ::note,
|
||||
"/{uuid}" bind POST to ::note,
|
||||
"/{uuid}/edit" bind GET to ::edit,
|
||||
"/{uuid}/edit" bind POST to ::edit,
|
||||
"/deleted/{uuid}" bind POST to ::deleted,
|
||||
).withBasePath("/notes")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package be.simplenotes.app.routes
|
||||
|
||||
import be.simplenotes.types.LoggedInUser
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.Response
|
||||
|
||||
internal typealias PublicHandler = (Request, LoggedInUser?) -> Response
|
||||
internal typealias ProtectedHandler = (Request, LoggedInUser) -> Response
|
||||
@@ -0,0 +1,32 @@
|
||||
package be.simplenotes.app.routes
|
||||
|
||||
import be.simplenotes.app.filters.ErrorFilter
|
||||
import be.simplenotes.app.filters.SecurityFilter
|
||||
import org.http4k.core.RequestContexts
|
||||
import org.http4k.core.then
|
||||
import org.http4k.filter.ResponseFilters.GZip
|
||||
import org.http4k.filter.ServerFilters.InitialiseRequestContext
|
||||
import org.http4k.routing.RoutingHttpHandler
|
||||
import org.http4k.routing.routes
|
||||
import java.util.function.Supplier
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class Router(
|
||||
private val errorFilter: ErrorFilter,
|
||||
private val contexts: RequestContexts,
|
||||
private val subRouters: List<Supplier<RoutingHttpHandler>>,
|
||||
) {
|
||||
operator fun invoke(): RoutingHttpHandler {
|
||||
|
||||
val routes = routes(
|
||||
*subRouters.map { it.get() }.toTypedArray()
|
||||
)
|
||||
|
||||
return errorFilter
|
||||
.then(InitialiseRequestContext(contexts))
|
||||
.then(SecurityFilter)
|
||||
.then(GZip())
|
||||
.then(routes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package be.simplenotes.app.routes
|
||||
|
||||
import be.simplenotes.app.controllers.SettingsController
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthFilter
|
||||
import be.simplenotes.app.filters.auth.RequiredAuthLens
|
||||
import org.http4k.core.Method.GET
|
||||
import org.http4k.core.Method.POST
|
||||
import org.http4k.core.Request
|
||||
import org.http4k.core.then
|
||||
import org.http4k.routing.PathMethod
|
||||
import org.http4k.routing.RoutingHttpHandler
|
||||
import org.http4k.routing.bind
|
||||
import org.http4k.routing.routes
|
||||
import java.util.function.Supplier
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SettingsRoutes(
|
||||
private val settingsController: SettingsController,
|
||||
private val auth: RequiredAuthFilter,
|
||||
@Named("required") private val authLens: RequiredAuthLens,
|
||||
) : Supplier<RoutingHttpHandler> {
|
||||
override fun get(): RoutingHttpHandler {
|
||||
|
||||
infix fun PathMethod.to(action: ProtectedHandler) =
|
||||
this to { req: Request -> action(req, authLens(req)) }
|
||||
|
||||
return auth.then(
|
||||
routes(
|
||||
"/settings" bind GET to settingsController::settings,
|
||||
"/settings" bind POST to settingsController::settings,
|
||||
"/export" bind POST to settingsController::export,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -10,7 +10,7 @@ import java.util.*
|
||||
|
||||
internal class UuidSerializer : KSerializer<UUID> {
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
|
||||
get() = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: UUID) {
|
||||
encoder.encodeString(value.toString())
|
||||
+2
@@ -3,11 +3,13 @@ package be.simplenotes.app.utils
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import javax.inject.Singleton
|
||||
|
||||
interface StaticFileResolver {
|
||||
fun resolve(name: String): String?
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class StaticFileResolverImpl(json: Json) : StaticFileResolver {
|
||||
private val mappings: Map<String, String>
|
||||
|
||||
+2
@@ -13,4 +13,6 @@
|
||||
<logger name="me.liuwj.ktorm.database" level="INFO"/>
|
||||
<logger name="com.zaxxer.hikari" level="INFO"/>
|
||||
<logger name="org.flywaydb.core" level="INFO"/>
|
||||
<logger name="io.micronaut" level="INFO"/>
|
||||
<logger name="io.micronaut.context.lifecycle" level="DEBUG"/>
|
||||
</configuration>
|
||||
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user