Merge pull request #2 from jlengrand/feature/login

WIP: Feature/login
This commit is contained in:
julien Lengrand-Lambert
2022-08-19 15:22:28 +02:00
committed by GitHub
16 changed files with 4534 additions and 1037 deletions

View File

@@ -2,6 +2,8 @@ val ktor_version: String by project
val kotlin_version: String by project
val logback_version: String by project
val exposedVersion: String by project
val postgresqlVersion: String by project
val postgisVersion: String by project
plugins {
application
@@ -25,20 +27,26 @@ repositories {
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
implementation("ch.qos.logback:logback-classic:$logback_version")
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("org.mindrot:jbcrypt:0.4")
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-java-time:$exposedVersion")
implementation("io.ktor:ktor-server-content-negotiation:$ktor_version")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
implementation("org.postgresql:postgresql:42.3.6")
implementation("net.postgis:postgis-jdbc:2021.1.0")
implementation("org.postgresql:postgresql:$postgresqlVersion")
implementation("net.postgis:postgis-jdbc:$postgisVersion")
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")

View File

@@ -3,3 +3,5 @@ kotlin_version=1.7.0
logback_version=1.2.11
kotlin.code.style=official
exposedVersion=0.38.2
postgresqlVersion=42.3.6
postgisVersion=2021.1.0

View File

@@ -0,0 +1,13 @@
module.exports = {
parserOptions: {
ecmaVersion: 13,
},
extends: ['@open-wc', 'prettier'],
rules: {
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: ['rollup.mapbox.js', 'rollup.config.js'] },
],
'no-console': ['error', { allow: ['warn', 'error'] }],
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -15,9 +15,17 @@
},
"dependencies": {
"@mapbox/mapbox-gl-geocoder": "^5.0.1",
"@vaadin/app-layout": "^23.1.3",
"@vaadin/button": "^23.1.3",
"@vaadin/dialog": "^23.1.4",
"@vaadin/form-layout": "^23.1.4",
"@vaadin/horizontal-layout": "^23.1.3",
"@vaadin/icon": "^23.1.3",
"@vaadin/icons": "^23.1.3",
"@vaadin/text-field": "^23.1.3",
"@vaadin/password-field": "^23.1.4",
"@vaadin/progress-bar": "^23.1.4",
"@vaadin/tabs": "^23.1.3",
"@vaadin/text-field": "^23.1.4",
"leaflet": "^1.8.0",
"leaflet-geosearch": "jlengrand/leaflet-geosearch#update",
"lit": "^2.0.2",
@@ -27,7 +35,7 @@
"@babel/preset-env": "^7.16.4",
"@custom-elements-manifest/analyzer": "^0.4.17",
"@open-wc/building-rollup": "^2.0.1",
"@open-wc/eslint-config": "^4.3.0",
"@open-wc/eslint-config": "^8.0.2",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^22.0.1",
"@rollup/plugin-node-resolve": "^13.0.6",
@@ -36,23 +44,17 @@
"@web/rollup-plugin-import-meta-assets": "^1.0.7",
"babel-plugin-template-html-minifier": "^4.1.0",
"deepmerge": "^4.2.2",
"eslint": "^7.32.0",
"eslint": "^8.22.0",
"eslint-config-prettier": "^8.3.0",
"husky": "^4.3.8",
"koa-proxies": "^0.12.2",
"lint-staged": "^10.5.4",
"prettier": "^2.4.1",
"prettier": "^2.7.1",
"rimraf": "^3.0.2",
"rollup": "^2.60.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-workbox": "^6.2.0"
},
"eslintConfig": {
"extends": [
"@open-wc",
"prettier"
]
},
"prettier": {
"singleQuote": true,
"arrowParens": "avoid"

View File

@@ -1,8 +1,15 @@
import { LitElement, html, css } from 'lit';
import './pluckr-login.js';
import '@vaadin/text-field';
import '@vaadin/icons';
import mapbox from '../dist/mapbox-gl.esm.js'
import MapboxGeocoder from '../dist/mapbox-gl-geocoder.esm.min.js'
import '@vaadin/app-layout';
import '@vaadin/app-layout/vaadin-drawer-toggle.js';
import '@vaadin/tabs';
import '@vaadin/button';
import mapboxgl from '../dist/mapbox-gl.esm.js';
import MapboxGeocoder from '../dist/mapbox-gl-geocoder.esm.min.js';
export class PluckrApp extends LitElement {
map = null;
@@ -10,7 +17,7 @@ export class PluckrApp extends LitElement {
static get properties() {
return {
title: { type: String },
location: {type: Object},
location: { type: Object },
};
}
@@ -38,30 +45,36 @@ export class PluckrApp extends LitElement {
height: 1096px;
width: 1096px;
}
pluckr-login {
margin-left: auto;
}
`;
}
constructor() {
super();
this.title = 'My app';
this.location = { x: 52.0474828687443, y: 5.080036739440433};
this.title = 'Pluckr';
this.location = { x: 52.0474828687443, y: 5.080036739440433 };
}
firstUpdated(_changedProperties) {
super.firstUpdated(_changedProperties);
this.map = new mapbox.Map({
accessToken: 'pk.eyJ1IjoiamxlbmdyYW5kIiwiYSI6ImNsNWM3YTl3YjBla3ozYm8yMHo3NTRtbHkifQ.mhHRpOn0v-v59tXbvEYnlQ',
this.map = new mapboxgl.Map({
accessToken:
'pk.eyJ1IjoiamxlbmdyYW5kIiwiYSI6ImNsNWM3YTl3YjBla3ozYm8yMHo3NTRtbHkifQ.mhHRpOn0v-v59tXbvEYnlQ',
container: this.renderRoot.querySelector('#map'),
style: 'mapbox://styles/mapbox/streets-v11',
center: [this.location.y, this.location.x],
zoom: 13
zoom: 13,
});
this.map.addControl(
new MapboxGeocoder({
accessToken: 'pk.eyJ1IjoiamxlbmdyYW5kIiwiYSI6ImNsNWM3YTl3YjBla3ozYm8yMHo3NTRtbHkifQ.mhHRpOn0v-v59tXbvEYnlQ',
mapboxgl: mapbox
accessToken:
'pk.eyJ1IjoiamxlbmdyYW5kIiwiYSI6ImNsNWM3YTl3YjBla3ozYm8yMHo3NTRtbHkifQ.mhHRpOn0v-v59tXbvEYnlQ',
mapboxgl,
})
);
@@ -70,19 +83,19 @@ export class PluckrApp extends LitElement {
this.loadMarkers(this.map.getBounds());
}
loadMarkers(bounds){
fetch(`/api/trees?bbox=${bounds._ne.lat},${bounds._ne.lng},${bounds._sw.lat},${bounds._sw.lng}`)
loadMarkers(bounds) {
fetch(
`/api/trees?bbox=${bounds._ne.lat},${bounds._ne.lng},${bounds._sw.lat},${bounds._sw.lng}`
)
.then(response => response.json())
.then(data => {
console.log('Loaded POIs', data);
data.map(p =>
new mapbox.Marker()
new mapboxgl.Marker()
.setLngLat([p.location.y, p.location.x])
.addTo(this.map)
);
})
.catch((error) => {
.catch(error => {
console.error('Impossible to log Points of Interests:', error);
});
}
@@ -99,15 +112,27 @@ export class PluckrApp extends LitElement {
integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
crossorigin=""
/>
<link href='https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css' rel='stylesheet' />
<link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.css" type="text/css">
<main>
<h1>${this.title}</h1>
<vaadin-text-field placeholder="Search">
<vaadin-icon slot="prefix" icon="vaadin:search"></vaadin-icon>
</vaadin-text-field>
<div id="map"></div>
</main>
<link
href="https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.0/mapbox-gl-geocoder.css"
type="text/css"
/>
<vaadin-app-layout>
<vaadin-drawer-toggle slot="navbar touch-optimized">
</vaadin-drawer-toggle>
<h3 slot="navbar touch-optimized">${this.title}</h3>
<pluckr-login slot="navbar"></pluckr-login>
<div>
<vaadin-text-field placeholder="Search">
<vaadin-icon slot="prefix" icon="vaadin:search"></vaadin-icon>
</vaadin-text-field>
<div id="map"></div>
</div>
</vaadin-app-layout>
`;
}
}

View File

@@ -0,0 +1,272 @@
import { css, html, LitElement } from 'lit';
import '@vaadin/horizontal-layout';
import '@vaadin/button';
import '@vaadin/dialog';
import '@vaadin/form-layout';
import '@vaadin/text-field';
import '@vaadin/password-field';
import { dialogRenderer } from '@vaadin/dialog/lit';
import '@vaadin/progress-bar';
export class PluckrLogin extends LitElement {
responsiveSteps = [{ minWidth: 0, columns: 1 }];
static get properties() {
return {
signUpOpened: { type: Boolean },
logInOpened: { type: Boolean },
username: { type: String },
password: { type: String },
passwordConfirm: { type: String },
loadingBarActive: { type: Boolean },
errorMessage: { type: String },
};
}
static get styles() {
return css``;
}
constructor() {
super();
this.signUpOpened = false;
this.logInOpened = false;
this.loadingBarActive = false;
}
render() {
return html`
<vaadin-horizontal-layout
slot="navbar touch-optimized"
theme="spacing padding"
>
<vaadin-button theme="primary" @click="${this.signUpClicked}"
>Sign up</vaadin-button
>
<vaadin-button theme="secondary" @click="${this.logInClicked}"
>Login</vaadin-button
>
</vaadin-horizontal-layout>
<vaadin-dialog
header-title="SignUp"
.opened="${this.signUpOpened}"
@opened-changed="${e => {
this.signUpOpened = e.detail.value;
}}"
${dialogRenderer(this.signUpRenderer, [
this.username,
this.signUpOpened,
this.password,
this.loadingBarActive,
this.errorMessage,
])}
id="signUp"
>
</vaadin-dialog>
<vaadin-dialog
header-title="LogIn"
.opened="${this.logInOpened}"
@opened-changed="${e => {
this.logInOpened = e.detail.value;
}}"
${dialogRenderer(this.logInRenderer, [
this.username,
this.logInOpened,
this.password,
this.loadingBarActive,
this.errorMessage,
])}
id="logIn"
>
</vaadin-dialog>
`;
}
signUpClicked() {
this.signUpOpened = true;
}
logInClicked() {
this.logInOpened = true;
}
logInRenderer() {
return html`
<vaadin-form-layout .responsiveSteps="${this.responsiveSteps}">
<vaadin-text-field
value="${this.username}"
@change="${e => {
this.username = e.target.value;
}}"
label="email"
></vaadin-text-field>
<vaadin-password-field
value="${this.password}"
@change="${e => {
this.password = e.target.value;
}}"
label="Password"
></vaadin-password-field>
${this.loadingBarActive
? html`<vaadin-progress-bar indeterminate></vaadin-progress-bar>`
: undefined}
${this.errorMessage
? html`<p class="error" style="color:red">${this.errorMessage}</p>`
: undefined}
<vaadin-horizontal-layout
theme="spacing padding"
style="justify-content: end"
>
<vaadin-button
theme="secondary"
@click="${() => {
this.logInOpened = false;
}}"
>Cancel</vaadin-button
>
<vaadin-button theme="primary" @click="${this.logIn}"
>LogIn</vaadin-button
>
</vaadin-horizontal-layout>
</vaadin-form-layout>
`;
}
signUpRenderer() {
return html`
<vaadin-form-layout .responsiveSteps="${this.responsiveSteps}">
<vaadin-text-field
value="${this.username}"
@change="${e => {
this.username = e.target.value;
}}"
label="email"
></vaadin-text-field>
<vaadin-password-field
value="${this.password}"
@change="${e => {
this.password = e.target.value;
}}"
label="Password"
></vaadin-password-field>
<vaadin-password-field
value="${this.passwordConfirm}"
@change="${e => {
this.passwordConfirm = e.target.value;
}}"
label="Confirm password"
></vaadin-password-field>
${this.loadingBarActive
? html`<vaadin-progress-bar indeterminate></vaadin-progress-bar>`
: undefined}
${this.errorMessage
? html`<p class="error" style="color:red">${this.errorMessage}</p>`
: undefined}
<vaadin-horizontal-layout
theme="spacing padding"
style="justify-content: end"
>
<vaadin-button
theme="secondary"
@click="${() => {
this.signUpOpened = false;
}}"
>Cancel
</vaadin-button>
<vaadin-button theme="primary" @click="${this.signUp}"
>Sign up</vaadin-button
>
</vaadin-horizontal-layout>
</vaadin-form-layout>
`;
}
logIn() {
this.loadingBarActive = true;
fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
username: this.username,
password: this.password,
}),
})
.then(response => {
this.loadingBarActive = false;
if (!response.ok) {
this.errorMessage = response.statusText;
console.error(
'There has been a problem logging in the user:',
response.statusText
);
} else {
this.username = null;
this.password = null;
this.passwordConfirm = null;
this.logInOpened = false;
}
})
.catch(error => {
this.errorMessage =
'There has been an issue contacting the server. Please try again later';
console.error(
'There has been a problem with your fetch operation:',
error
);
this.username = null;
this.password = null;
this.passwordConfirm = null;
});
}
signUp() {
this.loadingBarActive = true;
fetch('/api/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
username: this.username,
password: this.password,
}),
})
.then(response => {
this.loadingBarActive = false;
if (!response.ok) {
this.errorMessage = response.statusText;
console.error(
'There has been a problem saving the user:',
response.statusText
);
} else {
this.username = null;
this.password = null;
this.passwordConfirm = null;
this.signUpOpened = false;
}
})
.catch(error => {
this.errorMessage =
'There has been an issue contacting the server. Please try again later';
// eslint-disable-next-line no-console
console.error(
'There has been a problem with your fetch operation:',
error
);
this.username = null;
this.password = null;
this.passwordConfirm = null;
});
}
}

View File

@@ -0,0 +1,3 @@
import {PluckrLogin} from "./PluckrLogin.js";
customElements.define('pluckr-login', PluckrLogin);

View File

@@ -1,24 +1,58 @@
package nl.lengrand.pluckr
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.metrics.micrometer.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.callloging.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.response.*
import io.ktor.server.sessions.*
import kotlinx.serialization.json.Json
import net.postgis.jdbc.geometry.Point
import nl.lengrand.pluckr.plugins.*
import org.jetbrains.exposed.sql.*
import nl.lengrand.pluckr.plugins.configureRouting
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.StdOutSqlLogger
import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.transactions.transaction
fun Application.myapp(){
val database = initDb()
install(CORS)
install(Sessions) {
cookie<UserSession>("user_session", SessionStorageMemory()) {
cookie.path = "/"
cookie.maxAgeInSeconds = 6000
}
}
install(Authentication) {
session<UserSession>("user_session") {
println("validating session!")
validate { session ->
if(session.name.isNotEmpty()) {
println("Valid!")
println(session)
session
} else {
println("Not valid!")
println(session)
null
}
}
challenge {
println("redirecting")
call.respondRedirect("/")
}
}
}
// install(CORS)
install(ContentNegotiation){
json(Json {
prettyPrint = true
@@ -27,7 +61,6 @@ fun Application.myapp(){
}
install(CallLogging)
// install(MicrometerMetrics)
configureRouting(database)
}
@@ -37,24 +70,7 @@ fun initDb(): Database {
transaction {
addLogger(StdOutSqlLogger)
SchemaUtils.create(Trees)
// val first = Tree.new {
// name = "Laurier"
// description = "un laurier accessible à tous"
// location = Point(52.04681865145196, 5.079779509938945)
// }
// Trees.insert {
// it[name] = "Laurier 2"
// it[description] = "un laurier accessible à tous"
// it[location] = Point(52.04681865145196, 5.079779509938945)
// }
// println("Trees: ${Tree.all().joinToString {it.location.value}}")
SchemaUtils.create(Trees, Users)
}
return database
}

View File

@@ -1,59 +0,0 @@
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import net.postgis.jdbc.geometry.Point
import nl.lengrand.pluckr.Trees
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
@Serializable
data class Tree(
val id: Int? = null,
val name: String,
val description: String?,
@Serializable(with = PointSerializer::class)
val location : Point
)
@Serializable
@SerialName("Point")
private class PointSurrogate(val srid: Int, val x: Double, val y: Double)
object PointSerializer : KSerializer<Point> {
override val descriptor: SerialDescriptor = PointSurrogate.serializer().descriptor
override fun serialize(encoder: Encoder, value: Point) {
val surrogate = PointSurrogate(value.srid, value.x, value.y)
encoder.encodeSerializableValue(PointSurrogate.serializer(), surrogate)
}
override fun deserialize(decoder: Decoder): Point {
val surrogate = decoder.decodeSerializableValue(PointSurrogate.serializer())
return Point(surrogate.x, surrogate.y)
}
}
private fun fromRow(it: ResultRow): Tree {
return Tree(it[Trees.id], it[Trees.name], it[Trees.description], it[Trees.location])
}
class Controller(private val database: Database) {
fun getTrees() : ArrayList<Tree> {
val trees : ArrayList<Tree> = arrayListOf()
transaction(database){
Trees.selectAll().map { trees.add(fromRow(it)) }
}
return trees
}
fun getTrees(bbox: List<Double>?) : ArrayList<Tree> {
println(bbox)
return getTrees()
}
}

View File

@@ -5,6 +5,8 @@ import net.postgis.jdbc.geometry.Point
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.ColumnType
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.javatime.datetime
import java.time.LocalDateTime
object Trees : Table() {
val id = integer("id").autoIncrement()
@@ -15,6 +17,14 @@ object Trees : Table() {
override val primaryKey = PrimaryKey(id) // name is optional here
}
object Users: Table() {
val id = integer("id").autoIncrement()
val username = varchar("username", 100).uniqueIndex()
val password = varchar("password", 100)
val createdAt = datetime("created_at").clientDefault{ LocalDateTime.now() }
val updatedAt = datetime("updatedAt").clientDefault{ LocalDateTime.now() }
}
fun Table.point(name: String, srid: Int = 4326): Column<Point>
= registerColumn(name, PointColumnType())

View File

@@ -0,0 +1,106 @@
import io.ktor.server.auth.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import net.postgis.jdbc.geometry.Point
import nl.lengrand.pluckr.Trees
import org.jetbrains.exposed.sql.transactions.transaction
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.toKotlinLocalDateTime
import nl.lengrand.pluckr.Users
import org.jetbrains.exposed.sql.*
import org.mindrot.jbcrypt.BCrypt.*
@Serializable
data class UserSession(val name: String) : Principal
@Serializable
data class User(
val id: Int? = null,
val username: String,
val password: String,
val createdAt: LocalDateTime,
val updatedAt: LocalDateTime,
)
@Serializable
data class Tree(
val id: Int? = null,
val name: String,
val description: String?,
@Serializable(with = PointSerializer::class)
val location : Point
)
@Serializable
@SerialName("Point")
private class PointSurrogate(val srid: Int, val x: Double, val y: Double)
object PointSerializer : KSerializer<Point> {
override val descriptor: SerialDescriptor = PointSurrogate.serializer().descriptor
override fun serialize(encoder: Encoder, value: Point) {
val surrogate = PointSurrogate(value.srid, value.x, value.y)
encoder.encodeSerializableValue(PointSurrogate.serializer(), surrogate)
}
override fun deserialize(decoder: Decoder): Point {
val surrogate = decoder.decodeSerializableValue(PointSurrogate.serializer())
return Point(surrogate.x, surrogate.y)
}
}
private fun ResultRow.toTree(): Tree {
return Tree(this[Trees.id], this[Trees.name], this[Trees.description], this[Trees.location])
}
private fun ResultRow.toUser(): User {
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){
fun createUser(email: String, zepassword: String) {
val salt = gensalt()
transaction(database) {
Users.insert {
it[username] = email
it[password] = hashpw(zepassword, salt);
}
}
}
/*
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()
}
return user
}
fun getUser(email: String, zepassword: String): User {
val user = getUser(email)
if (!checkpw(zepassword, user.password)) throw AuthenticationException("Incorrect password detected")
return user
}
}
class TreeController(private val database: 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> {
return getTrees()
}
}
class AuthenticationException(message:String): Exception(message)

View File

@@ -1,32 +1,78 @@
package nl.lengrand.pluckr.plugins
import Controller
import TreeController
import UserController
import UserSession
import io.ktor.http.*
import io.ktor.server.routing.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.http.content.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.sessions.*
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.jetbrains.exposed.sql.Database
fun Application.configureRouting(database: Database) {
val controller = Controller(database)
val treeController = TreeController(database)
val userController = UserController(database)
routing {
get("/api/trees") {
println("IN HERE FIRST")
if(call.request.queryParameters["bbox"] != null){
println("IN HERE")
val bbox = call.request.queryParameters["bbox"]?.split(",")?.map { it.toDouble() }
call.respond(controller.getTrees(bbox))
}
else{
call.respond(controller.getTrees())
authenticate("user_session") {
get("/api/authenticated"){
call.respondText("Hello, ${call.principal<UserSession>()?.name}!")
}
}
post("/api/login") {
val formParameters = call.receiveParameters()
try{
val user = userController.getUser(formParameters["username"].toString(), formParameters["password"].toString())
call.sessions.set(UserSession(user.username))
call.respondRedirect("/")
}
catch(e: ExposedSQLException){
call.response.status(HttpStatusCode(500, e.message!!))
}
}
get("/hello") {
post("/api/logout") {
call.sessions.clear<UserSession>()
call.respondRedirect("/")
}
post("/api/signup"){
val formParameters = call.receiveParameters()
try{
userController.createUser(formParameters["username"].toString(), formParameters["password"].toString())
call.response.status(HttpStatusCode.OK)
}
catch(e: ExposedSQLException){ // TODO: Should I leak exceptions here?
val message = when (e.sqlState) {
"23505" ->
"User already exists"
else ->
"Unknown error, please retry later"
}
call.response.status(HttpStatusCode(500, message))
}
}
get("/api/trees") {
if(call.request.queryParameters["bbox"] != null){
val bbox = call.request.queryParameters["bbox"]?.split(",")?.map { it.toDouble() }
call.respond(treeController.getTrees(bbox))
}
else{
call.respond(treeController.getTrees())
}
}
get("/api/hello") {
call.respondText("Hello the World!")
}

File diff suppressed because one or more lines are too long

3156
src/main/resources/dist/c3e72859.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"sw.js","sources":["../../../../../../../../private/var/folders/4b/jjn7wslx5fl1r5npk147_9b80000gn/T/a8724be573e0d97f416c1dabfa1c598c/sw.js"],"sourcesContent":["import {clientsClaim as workbox_core_clientsClaim} from '/Users/julienlengrand-lambert/Developer/pluckr/src/main/js/pluckr-app/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/julienlengrand-lambert/Developer/pluckr/src/main/js/pluckr-app/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {registerRoute as workbox_routing_registerRoute} from '/Users/julienlengrand-lambert/Developer/pluckr/src/main/js/pluckr-app/node_modules/workbox-routing/registerRoute.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/Users/julienlengrand-lambert/Developer/pluckr/src/main/js/pluckr-app/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/Users/julienlengrand-lambert/Developer/pluckr/src/main/js/pluckr-app/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"bd0a6475.js\",\n \"revision\": \"b4aea298ab049d9d93db98f2f89dc5f4\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"86949185ebc1ce841945a76fa5f21673\"\n },\n {\n \"url\": \"mapbox-gl-geocoder.esm.min.js\",\n \"revision\": \"4d4a445462472b4e53d5a0da309c261e\"\n },\n {\n \"url\": \"mapbox-gl.esm.js\",\n \"revision\": \"9c3ae05af3ab19e62b91b371aae8efbe\"\n },\n {\n \"url\": \"mapbox-gl.esm.min.js\",\n \"revision\": \"289a426b3421209ddd1c11c35e258de0\"\n }\n], {});\n\nworkbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"/index.html\")));\n\n\n\n\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","url","revision","workbox","registerRoute","workbox_routing_NavigationRoute","NavigationRoute","workbox_precaching_createHandlerBoundToURL"],"mappings":"0nBAuBAA,KAAKC,cAELC,EAAAA,eAQAC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,gCACPC,SAAY,oCAEd,CACED,IAAO,mBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,qCAEb,IAE0BC,EAAAC,cAAC,IAAIC,EAAJC,gBAAoCC,0BAA2C"}
{"version":3,"file":"sw.js","sources":["../../../../../../../../private/var/folders/4b/jjn7wslx5fl1r5npk147_9b80000gn/T/bec2369c6dd1d05ca69900335130d7e5/sw.js"],"sourcesContent":["import {clientsClaim as workbox_core_clientsClaim} from '/Users/julienlengrand-lambert/Developer/pluckr/src/main/js/pluckr-app/node_modules/workbox-core/clientsClaim.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/julienlengrand-lambert/Developer/pluckr/src/main/js/pluckr-app/node_modules/workbox-precaching/precacheAndRoute.mjs';\nimport {registerRoute as workbox_routing_registerRoute} from '/Users/julienlengrand-lambert/Developer/pluckr/src/main/js/pluckr-app/node_modules/workbox-routing/registerRoute.mjs';\nimport {NavigationRoute as workbox_routing_NavigationRoute} from '/Users/julienlengrand-lambert/Developer/pluckr/src/main/js/pluckr-app/node_modules/workbox-routing/NavigationRoute.mjs';\nimport {createHandlerBoundToURL as workbox_precaching_createHandlerBoundToURL} from '/Users/julienlengrand-lambert/Developer/pluckr/src/main/js/pluckr-app/node_modules/workbox-precaching/createHandlerBoundToURL.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"bd0a6475.js\",\n \"revision\": \"b4aea298ab049d9d93db98f2f89dc5f4\"\n },\n {\n \"url\": \"index.html\",\n \"revision\": \"86949185ebc1ce841945a76fa5f21673\"\n },\n {\n \"url\": \"mapbox-gl-geocoder.esm.min.js\",\n \"revision\": \"4d4a445462472b4e53d5a0da309c261e\"\n },\n {\n \"url\": \"mapbox-gl.esm.js\",\n \"revision\": \"9c3ae05af3ab19e62b91b371aae8efbe\"\n },\n {\n \"url\": \"mapbox-gl.esm.min.js\",\n \"revision\": \"289a426b3421209ddd1c11c35e258de0\"\n }\n], {});\n\nworkbox_routing_registerRoute(new workbox_routing_NavigationRoute(workbox_precaching_createHandlerBoundToURL(\"/index.html\")));\n\n\n\n\n\n\n"],"names":["self","skipWaiting","workbox_core_clientsClaim","workbox_precaching_precacheAndRoute","url","revision","workbox","registerRoute","workbox_routing_NavigationRoute","NavigationRoute","workbox_precaching_createHandlerBoundToURL"],"mappings":"0nBAuBAA,KAAKC,cAELC,EAAAA,eAQAC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,cACPC,SAAY,oCAEd,CACED,IAAO,aACPC,SAAY,oCAEd,CACED,IAAO,gCACPC,SAAY,oCAEd,CACED,IAAO,mBACPC,SAAY,oCAEd,CACED,IAAO,uBACPC,SAAY,qCAEb,IAE0BC,EAAAC,cAAC,IAAIC,EAAJC,gBAAoCC,0BAA2C"}