Merge branch 'feature/account-creation'
This commit is contained in:
commit
af1d01e4a3
@ -13,4 +13,5 @@ val controllerModule = Kodein.Module(name = "Controller") {
|
|||||||
bind() from setBinding<KodeinController>()
|
bind() from setBinding<KodeinController>()
|
||||||
|
|
||||||
bind<KodeinController>().inSet() with singleton { UserController(this.kodein) }
|
bind<KodeinController>().inSet() with singleton { UserController(this.kodein) }
|
||||||
|
bind<KodeinController>().inSet() with singleton { HealthCheckController(this.kodein) }
|
||||||
}
|
}
|
||||||
21
api/src/controllers/HealthCheckController.kt
Normal file
21
api/src/controllers/HealthCheckController.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,10 +13,7 @@ import io.ktor.request.receive
|
|||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.routing.Routing
|
import io.ktor.routing.Routing
|
||||||
import me.liuwj.ktorm.database.Database
|
import me.liuwj.ktorm.database.Database
|
||||||
import me.liuwj.ktorm.dsl.eq
|
import me.liuwj.ktorm.dsl.*
|
||||||
import me.liuwj.ktorm.dsl.from
|
|
||||||
import me.liuwj.ktorm.dsl.select
|
|
||||||
import me.liuwj.ktorm.dsl.where
|
|
||||||
import me.liuwj.ktorm.entity.add
|
import me.liuwj.ktorm.entity.add
|
||||||
import me.liuwj.ktorm.entity.sequenceOf
|
import me.liuwj.ktorm.entity.sequenceOf
|
||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
@ -39,11 +36,11 @@ class UserController(kodein: Kodein) : KodeinController(kodein) {
|
|||||||
.where { Users.username eq credential.username }
|
.where { Users.username eq credential.username }
|
||||||
.map { row -> row[Users.email]!! to row[Users.password]!! }
|
.map { row -> row[Users.email]!! to row[Users.password]!! }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?: return@post call.respond(HttpStatusCode.BadRequest, ApiError.InvalidCredentialError())
|
?: return@post call.respond(HttpStatusCode.BadRequest, ApiError.InvalidCredentialError)
|
||||||
|
|
||||||
|
|
||||||
if (!BCrypt.checkpw(credential.password, password)) {
|
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)))
|
return@post call.respond(Response(simpleJwt.sign(email)))
|
||||||
@ -54,8 +51,14 @@ class UserController(kodein: Kodein) : KodeinController(kodein) {
|
|||||||
|
|
||||||
val user = call.receive<SignUpInfo>()
|
val user = call.receive<SignUpInfo>()
|
||||||
|
|
||||||
// TODO check if user does not already exists
|
val exists = db.from(Users)
|
||||||
// db won't let you insert it anyway
|
.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())
|
val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
|
||||||
|
|
||||||
@ -68,7 +71,7 @@ class UserController(kodein: Kodein) : KodeinController(kodein) {
|
|||||||
|
|
||||||
db.sequenceOf(Users).add(newUser)
|
db.sequenceOf(Users).add(newUser)
|
||||||
|
|
||||||
call.respond(HttpStatusCode.Created, Response("User created successfully"))
|
return@post call.respond(HttpStatusCode.Created, Response("User created successfully"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package be.vandewalleh.errors
|
package be.vandewalleh.errors
|
||||||
|
|
||||||
sealed class ApiError(val message: String){
|
sealed class ApiError(val message: String){
|
||||||
class InvalidCredentialError : ApiError("Invalid credentials")
|
object InvalidCredentialError : ApiError("Invalid credentials")
|
||||||
|
object ExistingUserError : ApiError("User already exists")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,11 @@ package be.vandewalleh.features
|
|||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.application.install
|
import io.ktor.application.install
|
||||||
import io.ktor.features.CORS
|
import io.ktor.features.CORS
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
|
|
||||||
fun Application.corsFeature() {
|
fun Application.corsFeature() {
|
||||||
install(CORS) {
|
install(CORS) {
|
||||||
anyHost()
|
anyHost()
|
||||||
|
header(HttpHeaders.ContentType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8,6 +8,7 @@
|
|||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.19.2",
|
||||||
"bootstrap-vue": "^2.11.0",
|
"bootstrap-vue": "^2.11.0",
|
||||||
"bootswatch": "^4.4.1",
|
"bootswatch": "^4.4.1",
|
||||||
"core-js": "^3.6.4",
|
"core-js": "^3.6.4",
|
||||||
|
|||||||
9
web/src/api/index.js
Normal file
9
web/src/api/index.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export default axios.create({
|
||||||
|
baseURL: 'http://localhost:8081',
|
||||||
|
timeout: 4000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
130
web/src/components/SignupForm.vue
Normal file
130
web/src/components/SignupForm.vue
Normal 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>
|
||||||
@ -2,6 +2,8 @@ import Vue from 'vue'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
|
import Api from './api'
|
||||||
|
|
||||||
import BootstrapVue from 'bootstrap-vue'
|
import BootstrapVue from 'bootstrap-vue'
|
||||||
|
|
||||||
import 'bootswatch/dist/minty/bootstrap.css'
|
import 'bootswatch/dist/minty/bootstrap.css'
|
||||||
@ -15,5 +17,6 @@ Vue.config.productionTip = false
|
|||||||
new Vue({
|
new Vue({
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
|
Api,
|
||||||
render: h => h(App)
|
render: h => h(App)
|
||||||
}).$mount('#app')
|
}).$mount('#app')
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import VueRouter from 'vue-router'
|
import VueRouter from 'vue-router'
|
||||||
import Home from '../views/Home.vue'
|
import Home from '../views/Home.vue'
|
||||||
|
import Signup from '../views/Signup.vue'
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
@ -9,6 +10,11 @@ const routes = [
|
|||||||
path: '/',
|
path: '/',
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: Home
|
component: Home
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/signup',
|
||||||
|
name: 'Signup',
|
||||||
|
component: Signup
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
21
web/src/views/Signup.vue
Normal file
21
web/src/views/Signup.vue
Normal 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>
|
||||||
@ -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"
|
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=
|
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:
|
babel-eslint@^10.1.0:
|
||||||
version "10.1.0"
|
version "10.1.0"
|
||||||
resolved "https://registry.npm.taobao.org/babel-eslint/download/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
|
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:
|
dependencies:
|
||||||
ms "2.0.0"
|
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:
|
debug@^3.0.0, debug@^3.1.1, debug@^3.2.5:
|
||||||
version "3.2.6"
|
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"
|
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"
|
inherits "^2.0.3"
|
||||||
readable-stream "^2.3.6"
|
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:
|
follow-redirects@^1.0.0:
|
||||||
version "1.11.0"
|
version "1.11.0"
|
||||||
resolved "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb"
|
resolved "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user