Merge branch 'feature/account-creation'

This commit is contained in:
Hubert Van De Walle 2020-04-12 16:37:11 +02:00
commit af1d01e4a3
12 changed files with 229 additions and 10 deletions

View File

@ -13,4 +13,5 @@ val controllerModule = Kodein.Module(name = "Controller") {
bind() from setBinding<KodeinController>()
bind<KodeinController>().inSet() with singleton { UserController(this.kodein) }
bind<KodeinController>().inSet() with singleton { HealthCheckController(this.kodein) }
}

View File

@ -0,0 +1,21 @@
package be.vandewalleh.controllers
import io.ktor.application.call
import io.ktor.locations.Location
import io.ktor.locations.get
import io.ktor.response.respondText
import io.ktor.routing.Routing
import org.kodein.di.Kodein
class HealthCheckController(kodein: Kodein) : KodeinController(kodein) {
override fun Routing.registerRoutes() {
get<Routes.Ping> {
call.respondText("pong")
}
}
object Routes {
@Location("/ping")
class Ping
}
}

View File

@ -13,10 +13,7 @@ import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Routing
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.eq
import me.liuwj.ktorm.dsl.from
import me.liuwj.ktorm.dsl.select
import me.liuwj.ktorm.dsl.where
import me.liuwj.ktorm.dsl.*
import me.liuwj.ktorm.entity.add
import me.liuwj.ktorm.entity.sequenceOf
import org.kodein.di.Kodein
@ -39,11 +36,11 @@ class UserController(kodein: Kodein) : KodeinController(kodein) {
.where { Users.username eq credential.username }
.map { row -> row[Users.email]!! to row[Users.password]!! }
.firstOrNull()
?: return@post call.respond(HttpStatusCode.BadRequest, ApiError.InvalidCredentialError())
?: return@post call.respond(HttpStatusCode.BadRequest, ApiError.InvalidCredentialError)
if (!BCrypt.checkpw(credential.password, password)) {
return@post call.respond(HttpStatusCode.BadRequest, ApiError.InvalidCredentialError())
return@post call.respond(HttpStatusCode.BadRequest, ApiError.InvalidCredentialError)
}
return@post call.respond(Response(simpleJwt.sign(email)))
@ -54,8 +51,14 @@ class UserController(kodein: Kodein) : KodeinController(kodein) {
val user = call.receive<SignUpInfo>()
// TODO check if user does not already exists
// db won't let you insert it anyway
val exists = db.from(Users)
.select()
.where { (Users.username eq user.username) or (Users.email eq user.email) }
.any()
if (exists) {
return@post call.respond(HttpStatusCode.Conflict, ApiError.ExistingUserError)
}
val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
@ -68,7 +71,7 @@ class UserController(kodein: Kodein) : KodeinController(kodein) {
db.sequenceOf(Users).add(newUser)
call.respond(HttpStatusCode.Created, Response("User created successfully"))
return@post call.respond(HttpStatusCode.Created, Response("User created successfully"))
}
}

View File

@ -1,5 +1,6 @@
package be.vandewalleh.errors
sealed class ApiError(val message: String){
class InvalidCredentialError : ApiError("Invalid credentials")
object InvalidCredentialError : ApiError("Invalid credentials")
object ExistingUserError : ApiError("User already exists")
}

View File

@ -3,9 +3,11 @@ package be.vandewalleh.features
import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.features.CORS
import io.ktor.http.HttpHeaders
fun Application.corsFeature() {
install(CORS) {
anyHost()
header(HttpHeaders.ContentType)
}
}

View File

@ -8,6 +8,7 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.19.2",
"bootstrap-vue": "^2.11.0",
"bootswatch": "^4.4.1",
"core-js": "^3.6.4",

9
web/src/api/index.js Normal file
View File

@ -0,0 +1,9 @@
import axios from 'axios'
export default axios.create({
baseURL: 'http://localhost:8081',
timeout: 4000,
headers: {
'Content-Type': 'application/json'
}
})

View File

@ -0,0 +1,130 @@
<template>
<div>
<b-card class="mt-3" header="Create an account">
<b-form @submit.prevent="handleSubmit">
<b-form-group
id="email-group"
label="Email address:"
label-for="email"
description="We'll never share your email with anyone else."
>
<b-form-input
id="email"
v-model="form.email"
type="email"
required
placeholder="Enter email"
></b-form-input>
</b-form-group>
<b-form-group id="username-group" label="Username:" label-for="username">
<b-form-input
id="username"
v-model="form.username"
required
placeholder="Enter a username"
:state="validUsername"
></b-form-input>
<b-form-invalid-feedback :state="validUsername">
Your username must be at least 5 characters long.
</b-form-invalid-feedback>
</b-form-group>
<b-form-group id="password-group" label="Password:" label-for="password">
<b-form-input
id="password"
v-model="form.password"
required
placeholder="Enter a password"
:state="validPassword"
type="password"
></b-form-input>
<b-form-invalid-feedback :state="validPassword">
Your password must be at least 6 characters long.
</b-form-invalid-feedback>
</b-form-group>
<b-form-group id="password-confirm-group" label="Confirm password:" label-for="password-confirm">
<b-form-input
id="password-confirm"
v-model="form.passwordConfirm"
required
placeholder="Confirm your password"
:state="passwordEquals"
type="password"
></b-form-input>
<b-form-invalid-feedback :state="passwordEquals">
Both passwords must be equals.
</b-form-invalid-feedback>
</b-form-group>
<b-button type="submit" variant="primary">Submit</b-button>
</b-form>
<b-alert :show="exists" variant="danger" dismissible class="mt-3">
A user with that username or email already exists
</b-alert>
<b-alert :show="error" variant="danger" dismissible class="mt-3">
An error occurred while attempting to create your account
</b-alert>
</b-card>
</div>
</template>
<script>
import Api from '@/api'
export default {
name: "SignupForm",
data() {
return {
form: {
username: '',
email: '',
password: '',
passwordConfirm: ''
},
error: false,
exists: false,
}
},
computed: {
passwordEquals: function () {
return this.form.password === this.form.passwordConfirm
},
validUsername: function () {
return this.form.username.length >= 5
},
validPassword: function () {
return this.form.password.length >= 6
},
validInput() {
return this.validUsername && this.passwordEquals && this.validPassword;
}
},
methods: {
handleSubmit() {
if (this.validInput) {
this.error = false
this.exists = false
this.signup()
}
},
signup() {
Api.post('/signup', {
username: this.form.username,
email: this.form.email,
password: this.form.password
})
.then(response => console.log(response.data))
.catch(error => {
if (error.response && error.response.status === 409)
this.exists = true
else
this.error = true
})
}
}
}
</script>

View File

@ -2,6 +2,8 @@ import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Api from './api'
import BootstrapVue from 'bootstrap-vue'
import 'bootswatch/dist/minty/bootstrap.css'
@ -15,5 +17,6 @@ Vue.config.productionTip = false
new Vue({
router,
store,
Api,
render: h => h(App)
}).$mount('#app')

View File

@ -1,6 +1,7 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Signup from '../views/Signup.vue'
Vue.use(VueRouter)
@ -9,6 +10,11 @@ const routes = [
path: '/',
name: 'Home',
component: Home
},
{
path: '/signup',
name: 'Signup',
component: Signup
}
]

21
web/src/views/Signup.vue Normal file
View File

@ -0,0 +1,21 @@
<template>
<div id="app">
<Navbar/>
<b-container class="mt-5">
<SignupForm/>
</b-container>
</div>
</template>
<script>
import Navbar from "@/components/Navbar";
import SignupForm from "@/components/SignupForm";
export default {
name: 'Home',
components: {
Navbar,
SignupForm
}
}
</script>

View File

@ -1580,6 +1580,13 @@ aws4@^1.8.0:
resolved "https://registry.npm.taobao.org/aws4/download/aws4-1.9.1.tgz?cache=0&sync_timestamp=1578958168482&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faws4%2Fdownload%2Faws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
integrity sha1-fjPY99RJs/ZzzXLeuavcVS2+Uo4=
axios@^0.19.2:
version "0.19.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
dependencies:
follow-redirects "1.5.10"
babel-eslint@^10.1.0:
version "10.1.0"
resolved "https://registry.npm.taobao.org/babel-eslint/download/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
@ -2706,6 +2713,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
debug@^3.0.0, debug@^3.1.1, debug@^3.2.5:
version "3.2.6"
resolved "https://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdebug%2Fdownload%2Fdebug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@ -3614,6 +3628,13 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
dependencies:
debug "=3.1.0"
follow-redirects@^1.0.0:
version "1.11.0"
resolved "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb"