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<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.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"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
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 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')
|
||||
@ -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
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"
|
||||
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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user