Merge pull request #3 from jlengrand/feature/clean-project

feature/clean project
This commit is contained in:
julien Lengrand-Lambert
2022-08-29 22:17:54 +02:00
committed by GitHub
14 changed files with 850 additions and 54 deletions

View File

@@ -13,6 +13,7 @@ jobs:
build:
runs-on: ubuntu-latest
environment: test
steps:
- uses: actions/checkout@v3
@@ -25,3 +26,9 @@ jobs:
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: build
env:
PLUCKR_PASSWORD: ${{ secrets.PLUCKR_PASSWORD }}
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
files: build/reports/kover/report.xml

30
.github/workflows/build-frontend.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Build frontend
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
environment: test
strategy:
matrix:
node-version: [17.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- run: npm ci
working-directory: ./src/main/js/pluckr-app
- run: npm run build --if-present
working-directory: ./src/main/js/pluckr-app

View File

@@ -1,5 +1,36 @@
# Pluckr
```
./gradlew run
```
Pluckr is a (currently non-functional) project aiming at helping folks find available trees, plants and spices to pluck from.
## About the project
This project is build with [Ktor](https://ktor.io/) for the backend, backed by a PostgreSQL database and using [Exposed](https://github.com/JetBrains/Exposed) to interact with the database.
The frontend is buildt with [Open-WC](https://open-wc.org/), [Lit](http://lit.dev/) and [Leaflet](https://leafletjs.com/examples/quick-start/) in Javascript.
## Running the project locally
The project is in 3 separate pieces. You need to :
* Fire up a PostgreSQL database, I use a local Docker image for the moment
* Fire the backend. You do this using `./gradlew run`.
* By default, the app will run with a H2 in memory database. Change the config file to run another configuration
* ( ex: `$./gradlew run --args="-config=src/main/resources/application.test.conf`)
* Fire the frontend. It is located in `src/js/pluckr-app`. Run `npm install` and then `npm start`
## TODOs
* Adds tests and install [Kover](https://lengrand.fr/kover-code-coverage-plugin-for-kotlin/)
* Look into Qonada and Detekt
* Automated deployment
* Setup local and remote db
* Build the freaking project
* How to see trends with Detekt?
## License
This is a personal project, you may not do anything with it without my permission 😊.
All rights reserved.
## Author
[Julien Lengrand-Lambert](https://twitter.com/jlengrand)

View File

@@ -9,6 +9,11 @@ plugins {
application
kotlin("jvm") version "1.7.0"
kotlin("plugin.serialization") version "1.6.21"
// Clean project
id("io.gitlab.arturbosch.detekt").version("1.21.0")
id("org.jetbrains.kotlinx.kover") version "0.4.2"
}
@@ -34,8 +39,8 @@ dependencies {
implementation("io.ktor:ktor-server-call-logging:$ktor_version")
implementation("io.ktor:ktor-server-metrics-micrometer:$ktor_version")
implementation("io.ktor:ktor-server-cors:$ktor_version")
implementation("io.ktor:ktor-server-sessions:$ktor_version")
implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation("io.ktor:ktor-server-sessions-jvm:2.1.0")
implementation("org.mindrot:jbcrypt:0.4")
@@ -47,7 +52,11 @@ dependencies {
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
implementation("org.postgresql:postgresql:$postgresqlVersion")
implementation("net.postgis:postgis-jdbc:$postgisVersion")
implementation("com.h2database:h2:2.1.214")
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
testImplementation("io.ktor:ktor-server-test-host-jvm:2.1.0")
}

656
config/detekt/detekt.yml Normal file
View File

@@ -0,0 +1,656 @@
build:
maxIssues: 10
excludeCorrectable: false
weights:
# complexity: 2
# LongParameterList: 1
# style: 1
# comments: 1
config:
validation: true
warningsAsErrors: false
# when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
excludes: ''
console-reports:
active: true
exclude:
- 'ProjectStatisticsReport'
- 'ComplexityReport'
- 'NotificationReport'
- 'FindingsReport'
- 'FileBasedFindingsReport'
# - 'LiteFindingsReport'
output-reports:
active: true
exclude:
# - 'TxtOutputReport'
# - 'XmlOutputReport'
# - 'HtmlOutputReport'
# - 'MdOutputReport'
complexity:
active: true
ComplexCondition:
active: true
threshold: 4
ComplexInterface:
active: false
threshold: 10
includeStaticDeclarations: false
includePrivateDeclarations: false
ComplexMethod:
active: true
threshold: 15
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false
nestingFunctions:
- 'also'
- 'apply'
- 'forEach'
- 'isNotNull'
- 'ifNull'
- 'let'
- 'run'
- 'use'
- 'with'
LabeledExpression:
active: false
ignoredLabels: []
LargeClass:
active: true
threshold: 600
LongMethod:
active: true
threshold: 60
LongParameterList:
active: true
functionThreshold: 6
constructorThreshold: 7
ignoreDefaultParameters: false
ignoreDataClasses: true
ignoreAnnotatedParameter: []
MethodOverloading:
active: false
threshold: 6
NamedArguments:
active: false
threshold: 3
ignoreArgumentsMatchingNames: false
NestedBlockDepth:
active: true
threshold: 4
NestedScopeFunctions:
active: false
threshold: 1
functions:
- 'kotlin.apply'
- 'kotlin.run'
- 'kotlin.with'
- 'kotlin.let'
- 'kotlin.also'
ReplaceSafeCallChainWithRun:
active: false
StringLiteralDuplication:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
thresholdInObjects: 11
thresholdInEnums: 11
ignoreDeprecated: false
ignorePrivate: false
ignoreOverridden: false
coroutines:
active: true
GlobalCoroutineUsage:
active: false
InjectDispatcher:
active: true
dispatcherNames:
- 'IO'
- 'Default'
- 'Unconfined'
RedundantSuspendModifier:
active: true
SleepInsteadOfDelay:
active: true
SuspendFunWithCoroutineScopeReceiver:
active: false
SuspendFunWithFlowReturnType:
active: true
empty-blocks:
active: true
EmptyCatchBlock:
active: true
allowedExceptionNameRegex: '_|(ignore|expected).*'
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
active: true
EmptyDoWhileBlock:
active: true
EmptyElseBlock:
active: true
EmptyFinallyBlock:
active: true
EmptyForBlock:
active: true
EmptyFunctionBlock:
active: true
ignoreOverridden: false
EmptyIfBlock:
active: true
EmptyInitBlock:
active: true
EmptyKtFile:
active: true
EmptySecondaryConstructor:
active: true
EmptyTryBlock:
active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
active: true
exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: true
methodNames:
- 'equals'
- 'finalize'
- 'hashCode'
- 'toString'
InstanceOfCheckForException:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
NotImplementedDeclaration:
active: false
ObjectExtendsThrowable:
active: false
PrintStackTrace:
active: true
RethrowCaughtException:
active: true
ReturnFromFinally:
active: true
ignoreLabeled: false
SwallowedException:
active: true
ignoredExceptionTypes:
- 'InterruptedException'
- 'MalformedURLException'
- 'NumberFormatException'
- 'ParseException'
allowedExceptionNameRegex: '_|(ignore|expected).*'
ThrowingExceptionFromFinally:
active: true
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
exceptions:
- 'ArrayIndexOutOfBoundsException'
- 'Exception'
- 'IllegalArgumentException'
- 'IllegalMonitorStateException'
- 'IllegalStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
ThrowingNewInstanceOfSameException:
active: true
TooGenericExceptionCaught:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
exceptionNames:
- 'ArrayIndexOutOfBoundsException'
- 'Error'
- 'Exception'
- 'IllegalMonitorStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
allowedExceptionNameRegex: '_|(ignore|expected).*'
TooGenericExceptionThrown:
active: true
exceptionNames:
- 'Error'
- 'Exception'
- 'RuntimeException'
- 'Throwable'
naming:
active: true
BooleanPropertyNaming:
active: false
allowedPattern: '^(is|has|are)'
ignoreOverridden: true
ClassNaming:
active: true
classPattern: '[A-Z][a-zA-Z0-9]*'
ConstructorParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
EnumNaming:
active: true
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
forbiddenName: []
FunctionMaxLength:
active: false
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
minimumFunctionNameLength: 3
FunctionNaming:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
functionPattern: '[a-z][a-zA-Z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
FunctionParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
InvalidPackageDeclaration:
active: true
rootPackage: ''
requireRootInDeclaration: false
LambdaParameterNaming:
active: false
parameterPattern: '[a-z][A-Za-z0-9]*|_'
MatchingDeclarationName:
active: true
mustBeFirst: true
MemberNameEqualsClassName:
active: true
ignoreOverridden: true
NoNameShadowing:
active: true
NonBooleanPropertyPrefixedWithIs:
active: false
ObjectPropertyNaming:
active: true
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
TopLevelPropertyNaming:
active: true
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: false
maximumVariableNameLength: 64
VariableMinLength:
active: false
minimumVariableNameLength: 1
VariableNaming:
active: true
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
performance:
active: true
ArrayPrimitive:
active: true
CouldBeSequence:
active: false
threshold: 3
ForEachOnRange:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
SpreadOperator:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
AvoidReferentialEquality:
active: true
forbiddenTypePatterns:
- 'kotlin.String'
CastToNullableType:
active: false
Deprecation:
active: false
DontDowncastCollectionTypes:
active: false
DoubleMutabilityForCollection:
active: true
mutableTypes:
- 'kotlin.collections.MutableList'
- 'kotlin.collections.MutableMap'
- 'kotlin.collections.MutableSet'
- 'java.util.ArrayList'
- 'java.util.LinkedHashSet'
- 'java.util.HashSet'
- 'java.util.LinkedHashMap'
- 'java.util.HashMap'
DuplicateCaseInWhenExpression:
active: true
ElseCaseInsteadOfExhaustiveWhen:
active: false
EqualsAlwaysReturnsTrueOrFalse:
active: true
EqualsWithHashCodeExist:
active: true
ExitOutsideMain:
active: false
ExplicitGarbageCollectionCall:
active: true
HasPlatformType:
active: true
IgnoredReturnValue:
active: true
restrictToAnnotatedMethods: true
returnValueAnnotations:
- '*.CheckResult'
- '*.CheckReturnValue'
ignoreReturnValueAnnotations:
- '*.CanIgnoreReturnValue'
ignoreFunctionCall: []
ImplicitDefaultLocale:
active: true
ImplicitUnitReturnType:
active: false
allowExplicitReturnType: true
InvalidRange:
active: true
IteratorHasNextCallsNextMethod:
active: true
IteratorNotThrowingNoSuchElementException:
active: true
LateinitUsage:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
ignoreOnClassesPattern: ''
MapGetWithNotNullAssertionOperator:
active: true
MissingPackageDeclaration:
active: false
excludes: ['**/*.kts']
MissingWhenCase:
active: true
allowElseExpression: true
NullCheckOnMutableProperty:
active: false
NullableToStringCall:
active: false
RedundantElseInWhen:
active: true
UnconditionalJumpStatementInLoop:
active: false
UnnecessaryNotNullOperator:
active: true
UnnecessarySafeCall:
active: true
UnreachableCatchBlock:
active: true
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: true
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
UnsafeCast:
active: true
UnusedUnaryOperator:
active: true
UselessPostfixExpression:
active: true
WrongEqualsTypeParameter:
active: true
style:
active: true
CanBeNonNullable:
active: false
CascadingCallWrapping:
active: false
includeElvis: true
ClassOrdering:
active: false
CollapsibleIfStatements:
active: false
DataClassContainsFunctions:
active: false
conversionFunctionPrefix: 'to'
DataClassShouldBeImmutable:
active: false
DestructuringDeclarationWithTooManyEntries:
active: true
maxDestructuringEntries: 3
EqualsNullCall:
active: true
EqualsOnSignatureLine:
active: false
ExplicitCollectionElementAccessMethod:
active: false
ExplicitItLambdaParameter:
active: true
ExpressionBodySyntax:
active: false
includeLineWrapping: false
ForbiddenComment:
active: true
values:
- 'FIXME:'
- 'STOPSHIP:'
- 'TODO:'
allowedPatterns: ''
customMessage: ''
ForbiddenImport:
active: false
imports: []
forbiddenPatterns: ''
ForbiddenMethodCall:
active: false
methods:
- 'kotlin.io.print'
- 'kotlin.io.println'
ForbiddenPublicDataClass:
active: true
excludes: ['**']
ignorePackages:
- '*.internal'
- '*.internal.*'
ForbiddenSuppress:
active: false
rules: []
ForbiddenVoid:
active: true
ignoreOverridden: false
ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
active: true
ignoreOverridableFunction: true
ignoreActualFunction: true
excludedFunctions: ''
LibraryCodeMustSpecifyReturnType:
active: true
excludes: ['**']
LibraryEntitiesShouldNotBePublic:
active: true
excludes: ['**']
LoopWithTooManyJumpStatements:
active: true
maxJumpCount: 1
MagicNumber:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts']
ignoreNumbers:
- '-1'
- '0'
- '1'
- '2'
ignoreHashCodeFunction: true
ignorePropertyDeclaration: false
ignoreLocalVariableDeclaration: false
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
ignoreRanges: false
ignoreExtensionFunctions: true
MandatoryBracesIfStatements:
active: false
MandatoryBracesLoops:
active: false
MaxChainedCallsOnSameLine:
active: false
maxChainedCalls: 5
MaxLineLength:
active: true
maxLineLength: 120
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false
MayBeConst:
active: true
ModifierOrder:
active: true
MultilineLambdaItParameter:
active: false
NestedClassesVisibility:
active: true
NoTabs:
active: false
NullableBooleanCheck:
active: false
ObjectLiteralToLambda:
active: true
OptionalAbstractKeyword:
active: true
OptionalUnit:
active: false
OptionalWhenBraces:
active: false
PreferToOverPairSyntax:
active: false
ProtectedMemberInFinalClass:
active: true
RedundantExplicitType:
active: false
RedundantHigherOrderMapUsage:
active: true
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: true
max: 2
excludedFunctions: 'equals'
excludeLabeled: false
excludeReturnFromLambda: true
excludeGuardClauses: false
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
active: true
SpacingBetweenPackageAndImports:
active: false
ThrowsCount:
active: true
max: 2
excludeGuardClauses: false
TrailingWhitespace:
active: false
UnderscoresInNumericLiterals:
active: false
acceptableLength: 4
allowNonStandardGrouping: false
UnnecessaryAbstractClass:
active: true
UnnecessaryAnnotationUseSiteTarget:
active: false
UnnecessaryApply:
active: true
UnnecessaryBackticks:
active: false
UnnecessaryFilter:
active: true
UnnecessaryInheritance:
active: true
UnnecessaryInnerClass:
active: false
UnnecessaryLet:
active: false
UnnecessaryParentheses:
active: false
UntilInsteadOfRangeTo:
active: false
UnusedImports:
active: false
UnusedPrivateClass:
active: true
UnusedPrivateMember:
active: true
allowedNames: '(_|ignored|expected|serialVersionUID)'
UseAnyOrNoneInsteadOfFind:
active: true
UseArrayLiteralsInAnnotations:
active: true
UseCheckNotNull:
active: true
UseCheckOrError:
active: true
UseDataClass:
active: false
allowVars: false
UseEmptyCounterpart:
active: false
UseIfEmptyOrIfBlank:
active: false
UseIfInsteadOfWhen:
active: false
UseIsNullOrEmpty:
active: true
UseOrEmpty:
active: true
UseRequire:
active: true
UseRequireNotNull:
active: true
UselessCallOnNotNull:
active: true
UtilityClassWithPublicConstructor:
active: true
VarCouldBeVal:
active: true
ignoreLateinitVar: false
WildcardImport:
active: false
excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
excludeImports:
- 'java.util.*'

View File

@@ -1,4 +1,4 @@
ktor_version=2.0.3
ktor_version=2.1.0
kotlin_version=1.7.0
logback_version=1.2.11
kotlin.code.style=official

View File

@@ -7,7 +7,7 @@ const hmr = process.argv.includes('--hmr');
export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
middleware: [
proxy('/api', {
target: 'http://localhost:9090',
target: 'http://localhost:8080',
}),
],

View File

@@ -4,11 +4,10 @@ import UserSession
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.callloging.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.sessions.*
import kotlinx.serialization.json.Json
import nl.lengrand.pluckr.plugins.configureRouting
@@ -18,9 +17,22 @@ import org.jetbrains.exposed.sql.StdOutSqlLogger
import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.transactions.transaction
fun Application.myapp(){
fun Application.module() {
val env = environment.config.propertyOrNull("ktor.environment")?.getString()
println("Running in the $env environment")
val database = initDb()
routing {
get("/api/environment") {
call.respondText(env?: "null")
}
}
val database = initDb(
environment.config.property("ktor.database.url").getString(),
environment.config.property("ktor.database.driver").getString(),
environment.config.property("ktor.database.user").getString(),
environment.config.property("ktor.database.password").getString(),
)
install(Sessions) {
cookie<UserSession>("user_session", SessionStorageMemory()) {
@@ -33,9 +45,8 @@ fun Application.myapp(){
session<UserSession>("user_session") {
println("validating session!")
validate { session ->
if(session.name.isNotEmpty()) {
if (session.name.isNotEmpty()) {
println("Valid!")
println(session)
session
@@ -53,7 +64,7 @@ fun Application.myapp(){
}
// install(CORS)
install(ContentNegotiation){
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
@@ -64,22 +75,14 @@ fun Application.myapp(){
configureRouting(database)
}
fun initDb(): Database {
val database = Database.connect("jdbc:postgresql://localhost:5432/pluckr", driver = "org.postgresql.Driver",
user = "pluckr", password = System.getenv("PLUCKR_PASSWORD"))
fun initDb(url: String, driver: String, user: String, password: String): Database {
val database = Database.connect(url, driver, user , password )
transaction {
transaction(database) {
addLogger(StdOutSqlLogger)
SchemaUtils.create(Trees, Users)
}
return database
}
fun main() {
embeddedServer(
Netty,
module = Application::myapp,
port = 9090,
host = "0.0.0.0")
.start(wait = true)
}
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

View File

@@ -32,7 +32,7 @@ data class Tree(
val name: String,
val description: String?,
@Serializable(with = PointSerializer::class)
val location : Point
val location: Point
)
@Serializable
@@ -58,14 +58,20 @@ private fun ResultRow.toTree(): Tree {
}
private fun ResultRow.toUser(): User {
return User(this[Users.id], this[Users.username], this[Users.password], this[Users.createdAt].toKotlinLocalDateTime(), this[Users.updatedAt].toKotlinLocalDateTime())
return User(
this[Users.id],
this[Users.username],
this[Users.password],
this[Users.createdAt].toKotlinLocalDateTime(),
this[Users.updatedAt].toKotlinLocalDateTime()
)
}
class UserController(private val database: Database){
class UserController(private val database: Database) {
fun createUser(email: String, zepassword: String) {
val salt = gensalt()
transaction(database) {
Users.insert {
Users.insert {
it[username] = email
it[password] = hashpw(zepassword, salt);
}
@@ -76,8 +82,8 @@ class UserController(private val database: Database){
Will throw NoSuchElementException if there are no results, or IllegalArgumentException if there are more than one
*/
private fun getUser(email: String): User {
val user = transaction(database) {
Users.select{ Users.username eq email}.single().toUser()
val user = transaction(database) {
Users.select { Users.username eq email }.single().toUser()
}
return user
}
@@ -90,17 +96,17 @@ class UserController(private val database: Database){
}
class TreeController(private val database: Database) {
fun getTrees() : ArrayList<Tree> {
val trees : ArrayList<Tree> = arrayListOf()
transaction(database){
fun getTrees(): ArrayList<Tree> {
val trees: ArrayList<Tree> = arrayListOf()
transaction(database) {
Trees.selectAll().map { trees.add(it.toTree()) }
}
return trees
}
fun getTrees(bbox: List<Double>?) : ArrayList<Tree> {
fun getTrees(bbox: List<Double>?): ArrayList<Tree> {
return getTrees()
}
}
class AuthenticationException(message:String): Exception(message)
class AuthenticationException(message: String) : Exception(message)

View File

@@ -22,7 +22,7 @@ fun Application.configureRouting(database: Database) {
routing {
authenticate("user_session") {
get("/api/authenticated"){
get("/api/authenticated") {
call.respondText("Hello, ${call.principal<UserSession>()?.name}!")
}
}
@@ -30,12 +30,14 @@ fun Application.configureRouting(database: Database) {
post("/api/login") {
val formParameters = call.receiveParameters()
try{
val user = userController.getUser(formParameters["username"].toString(), formParameters["password"].toString())
try {
val user = userController.getUser(
formParameters["username"].toString(),
formParameters["password"].toString()
)
call.sessions.set(UserSession(user.username))
call.respondRedirect("/")
}
catch(e: ExposedSQLException){
} catch (e: ExposedSQLException) {
call.response.status(HttpStatusCode(500, e.message!!))
}
}
@@ -45,16 +47,16 @@ fun Application.configureRouting(database: Database) {
call.respondRedirect("/")
}
post("/api/signup"){
post("/api/signup") {
val formParameters = call.receiveParameters()
try{
try {
userController.createUser(formParameters["username"].toString(), formParameters["password"].toString())
call.response.status(HttpStatusCode.OK)
}
catch(e: ExposedSQLException){ // TODO: Should I leak exceptions here?
} catch (e: ExposedSQLException) { // TODO: Should I leak exceptions here?
val message = when (e.sqlState) {
"23505" ->
"User already exists"
else ->
"Unknown error, please retry later"
}
@@ -63,11 +65,10 @@ fun Application.configureRouting(database: Database) {
}
get("/api/trees") {
if(call.request.queryParameters["bbox"] != null){
if (call.request.queryParameters["bbox"] != null) {
val bbox = call.request.queryParameters["bbox"]?.split(",")?.map { it.toDouble() }
call.respond(treeController.getTrees(bbox))
}
else{
} else {
call.respond(treeController.getTrees())
}
}

View File

@@ -0,0 +1,16 @@
ktor {
environment = test
deployment {
port = 8080
}
application {
modules = [ nl.lengrand.pluckr.ApplicationKt.module ]
}
database {
url = "jdbc:h2:mem:test"
driver = "org.h2.Driver"
user = "pluckr"
password = ${PLUCKR_PASSWORD}
}
}

View File

@@ -0,0 +1,16 @@
ktor {
environment = dev
deployment {
port = 8080
}
application {
modules = [ nl.lengrand.pluckr.ApplicationKt.module ]
}
database{
url = "jdbc:postgresql://localhost:5432/pluckr"
driver = "org.postgresql.Driver"
user = "pluckr"
password = ${PLUCKR_PASSWORD}
}
}

View File

@@ -0,0 +1,16 @@
ktor {
environment = test
deployment {
port = 8080
}
application {
modules = [ nl.lengrand.pluckr.ApplicationKt.module ]
}
database{
url = "jdbc:h2:mem:test"
driver = "org.h2.Driver"
user = "pluckr"
password = ${PLUCKR_PASSWORD}
}
}

View File

@@ -1,13 +1,18 @@
package nl.lengrand.pluckr
import io.ktor.server.routing.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.request.*
import kotlin.test.*
import io.ktor.server.testing.*
import nl.lengrand.pluckr.plugins.*
import kotlin.test.Test
import kotlin.test.assertEquals
class ApplicationTest {
@Test
fun testRoot() = testApplication {
application {}
val response = client.get("/api/hello")
assertEquals(HttpStatusCode.OK, response.status)
assertEquals("Hello the World!", response.bodyAsText())
}
}