diff --git a/api/pom.xml b/api/pom.xml
index 63c2e02..06de466 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -12,7 +12,7 @@
1.3.70
1.2.1
5.6.2
- 2.7.2
+ 3.0.0
2.6.0
6.5.4
6.3.3
diff --git a/api/resources/db/migration/V1__Create_user_table.sql b/api/resources/db/migration/V1__Create_user_table.sql
index 14a6c98..622f670 100644
--- a/api/resources/db/migration/V1__Create_user_table.sql
+++ b/api/resources/db/migration/V1__Create_user_table.sql
@@ -1,13 +1,9 @@
create table Users
(
- id int auto_increment primary key,
- username varchar(50) not null,
- email varchar(255) not null,
- password varchar(255) not null,
- created_at datetime not null,
- last_login datetime null,
+ id int auto_increment primary key,
+ username varchar(50) not null,
+ password varchar(255) not null,
- constraint email unique (email),
constraint username unique (username)
);
diff --git a/api/src/entities/User.kt b/api/src/entities/User.kt
index 0400eb2..6f12fdb 100644
--- a/api/src/entities/User.kt
+++ b/api/src/entities/User.kt
@@ -1,15 +1,14 @@
package be.vandewalleh.entities
+import com.fasterxml.jackson.annotation.JsonProperty
import me.liuwj.ktorm.entity.*
-import java.time.LocalDateTime
interface User : Entity {
companion object : Entity.Factory()
val id: Int
var username: String
- var email: String
+
+ @get:JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
var password: String
- var createdAt: LocalDateTime
- var lastLogin: LocalDateTime?
}
diff --git a/api/src/extensions/KtormExtensions.kt b/api/src/extensions/KtormExtensions.kt
index 2d3c642..f17ba49 100644
--- a/api/src/extensions/KtormExtensions.kt
+++ b/api/src/extensions/KtormExtensions.kt
@@ -5,7 +5,6 @@ import java.nio.ByteBuffer
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.Types
-import java.util.*
import java.util.UUID as JavaUUID
class UuidBinarySqlType : SqlType(Types.BINARY, typeName = "uuidBinary") {
@@ -23,6 +22,6 @@ class UuidBinarySqlType : SqlType(Types.BINARY, typeName = "uuidBinary
}
}
-fun BaseTable.uuidBinary(name: String): BaseTable.ColumnRegistration {
+fun BaseTable.uuidBinary(name: String): Column {
return registerColumn(name, UuidBinarySqlType())
}
diff --git a/api/src/routing/AuthController.kt b/api/src/routing/AuthController.kt
index d9f3810..65f767c 100644
--- a/api/src/routing/AuthController.kt
+++ b/api/src/routing/AuthController.kt
@@ -27,7 +27,7 @@ fun Routing.auth(kodein: Kodein) {
post("/user/login") {
val credential = call.receive()
- val user = userService.getFromUsername(credential.username)
+ val user = userService.find(credential.username)
?: return@post call.respondStatus(HttpStatusCode.Unauthorized)
if (!BCrypt.checkpw(credential.password, user.password)) {
@@ -51,7 +51,7 @@ fun Routing.auth(kodein: Kodein) {
return@post call.respondStatus(HttpStatusCode.Unauthorized)
}
- if (!userService.userExists(id))
+ if (!userService.exists(id))
return@post call.respondStatus(HttpStatusCode.Unauthorized)
val response = DualToken(
@@ -63,9 +63,8 @@ fun Routing.auth(kodein: Kodein) {
authenticate {
get("/user/me") {
- // retrieve email from token
val id = call.principal()!!.id
- val info = userService.getUserInfo(id)
+ val info = userService.find(id)
if (info != null) call.respond(mapOf("user" to info))
else call.respondStatus(HttpStatusCode.Unauthorized)
}
diff --git a/api/src/routing/UserController.kt b/api/src/routing/UserController.kt
index 4205f85..d03eac9 100644
--- a/api/src/routing/UserController.kt
+++ b/api/src/routing/UserController.kt
@@ -1,6 +1,5 @@
package be.vandewalleh.routing
-import be.vandewalleh.entities.User
import be.vandewalleh.extensions.respondStatus
import be.vandewalleh.extensions.userId
import be.vandewalleh.services.UserService
@@ -9,13 +8,11 @@ import be.vandewalleh.validation.user.registerValidator
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.*
-import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import org.kodein.di.Kodein
import org.kodein.di.generic.instance
import org.mindrot.jbcrypt.BCrypt
-import java.time.LocalDateTime
fun Routing.user(kodein: Kodein) {
val userService by kodein.instance()
@@ -24,32 +21,20 @@ fun Routing.user(kodein: Kodein) {
post {
val user = call.receiveValidated(registerValidator)
- if (userService.userExists(user.username, user.email))
+ if (userService.exists(user.username))
return@post call.respondStatus(HttpStatusCode.Conflict)
val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
- userService.createUser(user.username, user.email, hashedPassword)
+ val newUser = userService.create(user.username, hashedPassword)
+ ?: return@post call.respondStatus(HttpStatusCode.Conflict)
- call.respondStatus(HttpStatusCode.Created)
+ call.respond(HttpStatusCode.Created, newUser)
}
authenticate {
- put {
- val user = call.receiveValidated(registerValidator)
-
- if (userService.userExists(user.username, user.email))
- return@put call.respond(HttpStatusCode.Conflict)
-
- val hashedPassword = BCrypt.hashpw(user.password, BCrypt.gensalt())
-
- userService.updateUser(call.userId(), user.username, user.email, hashedPassword)
-
- call.respondStatus(HttpStatusCode.OK)
- }
-
delete {
- val status = if (userService.deleteUser(call.userId()))
+ val status = if (userService.delete(call.userId()))
HttpStatusCode.OK
else
HttpStatusCode.NotFound
diff --git a/api/src/services/NotesService.kt b/api/src/services/NotesService.kt
index cd87a59..eecdca1 100644
--- a/api/src/services/NotesService.kt
+++ b/api/src/services/NotesService.kt
@@ -47,12 +47,11 @@ class NotesService(override val kodein: Kodein) : KodeinAware {
}
fun noteExists(userId: Int, uuid: UUID): Boolean {
- return db.from(Notes)
- .select(Notes.uuid)
- .where { Notes.userId eq userId }
- .where { Notes.uuid eq uuid }
- .limit(0, 1)
- .toList().size == 1
+ return db.sequenceOf(Notes)
+ .filterColumns { listOf(it.uuid) }
+ .find {
+ it.userId eq userId and (it.uuid eq uuid)
+ } == null
}
fun createNote(userId: Int, note: FullNoteCreateDTO): UUID {
diff --git a/api/src/services/UserService.kt b/api/src/services/UserService.kt
index 3e00482..a5d7d26 100644
--- a/api/src/services/UserService.kt
+++ b/api/src/services/UserService.kt
@@ -20,62 +20,39 @@ import java.time.LocalDateTime
class UserService(override val kodein: Kodein) : KodeinAware {
private val db by instance()
- /**
- * returns a user ID if present or null
- */
- suspend fun getUserId(userEmail: String): Int? = launchIo {
- db.from(Users)
- .select(Users.id)
- .where { Users.email eq userEmail }
- .map { it[Users.id] }
- .firstOrNull()
- }
/**
- * returns a user email and password from it's username if found or null
+ * returns a user from it's username if found or null
*/
- suspend fun getFromUsername(username: String): User? = launchIo {
- db.from(Users)
- .select(Users.email, Users.password, Users.id)
- .where { Users.username eq username }
- .map { row ->
- Users.createEntity(row)
- }
- .firstOrNull()
+ suspend fun find(username: String): User? = launchIo {
+ db.sequenceOf(Users, withReferences = false)
+ .find { it.username eq username }
}
- suspend fun userExists(username: String, email: String): Boolean = launchIo {
- db.from(Users)
- .select(Users.id)
- .where { (Users.username eq username) or (Users.email eq email) }
- .firstOrNull() != null
+ suspend fun find(id: Int): User? = launchIo {
+ db.sequenceOf(Users, withReferences = false)
+ .find { it.id eq id }
}
- suspend fun userExists(userId: Int): Boolean = launchIo {
- db.from(Users)
- .select(Users.id)
- .where { Users.id eq userId }
- .firstOrNull() != null
+
+ suspend fun exists(username: String) = launchIo {
+ db.sequenceOf(Users, withReferences = false)
+ .any { it.username eq username }
}
- suspend fun getUserInfo(id: Int): User? = launchIo {
- db.from(Users)
- .select(Users.email, Users.username)
- .where { Users.id eq id }
- .map { Users.createEntity(it) }
- .firstOrNull()
+ suspend fun exists(userId: Int) = launchIo {
+ db.sequenceOf(Users).any { it.id eq userId }
}
+
/**
* create a new user
* password should already be hashed
*/
- suspend fun createUser(username: String, email: String, hashedPassword: String): User? {
+ suspend fun create(username: String, hashedPassword: String): User? {
val newUser = User {
this.username = username
- this.email = email
password = hashedPassword
- createdAt = LocalDateTime.now()
}
return try {
@@ -90,25 +67,11 @@ class UserService(override val kodein: Kodein) : KodeinAware {
}
}
- suspend fun updateUser(userId: Int, username: String, email: String, hashedPassword: String): Unit = launchIo {
- db.useTransaction {
- db.update(Users) {
- it.username to username
- it.email to email
- it.password to hashedPassword
- where {
- it.id eq userId
- }
- }
- }
- }
-
- suspend fun deleteUser(userId: Int): Boolean = launchIo {
- db.useTransaction {
+ suspend fun delete(userId: Int): Boolean = launchIo {
+ val updateCount = db.useTransaction {
db.delete(Users) { it.id eq userId }
}
- }.let {
- when (it) {
+ when (updateCount) {
1 -> true
0 -> false
else -> error("??")
diff --git a/api/src/tables/Chapters.kt b/api/src/tables/Chapters.kt
index ab04c9d..8f4e841 100644
--- a/api/src/tables/Chapters.kt
+++ b/api/src/tables/Chapters.kt
@@ -5,10 +5,10 @@ import be.vandewalleh.extensions.uuidBinary
import me.liuwj.ktorm.schema.*
object Chapters : Table("Chapters") {
- val id by int("id").primaryKey().bindTo { it.id }
- val number by int("number").bindTo { it.number }
- val content by text("content").bindTo { it.content }
- val title by varchar("title").bindTo { it.title }
- val noteUuid by uuidBinary("note_uuid").references(Notes) { it.note }
+ val id = int("id").primaryKey().bindTo { it.id }
+ val number = int("number").bindTo { it.number }
+ val content = text("content").bindTo { it.content }
+ val title = varchar("title").bindTo { it.title }
+ val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note }
val note get() = noteUuid.referenceTable as Notes
-}
\ No newline at end of file
+}
diff --git a/api/src/tables/Notes.kt b/api/src/tables/Notes.kt
index 37ca204..3078fff 100644
--- a/api/src/tables/Notes.kt
+++ b/api/src/tables/Notes.kt
@@ -5,9 +5,9 @@ import be.vandewalleh.extensions.uuidBinary
import me.liuwj.ktorm.schema.*
object Notes : Table("Notes") {
- val uuid by uuidBinary("uuid").primaryKey().bindTo { it.uuid }
- val title by varchar("title").bindTo { it.title }
- val userId by int("user_id").references(Users) { it.user }
- val updatedAt by datetime("updated_at").bindTo { it.updatedAt }
+ val uuid = uuidBinary("uuid").primaryKey().bindTo { it.uuid }
+ val title = varchar("title").bindTo { it.title }
+ val userId = int("user_id").references(Users) { it.user }
+ val updatedAt = datetime("updated_at").bindTo { it.updatedAt }
val user get() = userId.referenceTable as Users
-}
\ No newline at end of file
+}
diff --git a/api/src/tables/Tags.kt b/api/src/tables/Tags.kt
index 0f26bd1..e173137 100644
--- a/api/src/tables/Tags.kt
+++ b/api/src/tables/Tags.kt
@@ -5,8 +5,8 @@ import be.vandewalleh.extensions.uuidBinary
import me.liuwj.ktorm.schema.*
object Tags : Table("Tags") {
- val id by int("id").primaryKey().bindTo { it.id }
- val name by varchar("name").bindTo { it.name }
- val noteUuid by uuidBinary("note_uuid").references(Notes) { it.note }
+ val id = int("id").primaryKey().bindTo { it.id }
+ val name = varchar("name").bindTo { it.name }
+ val noteUuid = uuidBinary("note_uuid").references(Notes) { it.note }
val note get() = noteUuid.referenceTable as Notes
-}
\ No newline at end of file
+}
diff --git a/api/src/tables/Users.kt b/api/src/tables/Users.kt
index 2ae5061..1150785 100644
--- a/api/src/tables/Users.kt
+++ b/api/src/tables/Users.kt
@@ -4,10 +4,7 @@ import be.vandewalleh.entities.User
import me.liuwj.ktorm.schema.*
object Users : Table("Users") {
- val id by int("id").primaryKey().bindTo { it.id }
- val username by varchar("username").bindTo { it.username }
- val email by varchar("email").bindTo { it.email }
- val password by varchar("password").bindTo { it.password }
- val createdAt by datetime("created_at").bindTo { it.createdAt }
- val lastLogin by datetime("last_login").bindTo { it.lastLogin }
-}
\ No newline at end of file
+ val id = int("id").primaryKey().bindTo { it.id }
+ val username = varchar("username").bindTo { it.username }
+ val password = varchar("password").bindTo { it.password }
+}
diff --git a/api/src/validation/user/UserValidation.kt b/api/src/validation/user/UserValidation.kt
index 6f2063e..fb1cb47 100644
--- a/api/src/validation/user/UserValidation.kt
+++ b/api/src/validation/user/UserValidation.kt
@@ -9,9 +9,6 @@ val registerValidator: Validator = ValidatorBuilder.of()
.konstraint(User::username) {
notNull().lessThanOrEqual(50).greaterThanOrEqual(3)
}
- .konstraint(User::email) {
- notNull().notEmpty().lessThanOrEqual(255).email()
- }
.konstraint(User::password) {
notNull().greaterThanOrEqual(6)
}
diff --git a/api/test/integration/routing/AuthControllerKtTest.kt b/api/test/integration/routing/AuthControllerKtTest.kt
index 11eb503..b792897 100644
--- a/api/test/integration/routing/AuthControllerKtTest.kt
+++ b/api/test/integration/routing/AuthControllerKtTest.kt
@@ -13,7 +13,6 @@ import io.ktor.server.testing.*
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
-import io.mockk.verify
import org.amshove.kluent.*
import org.json.JSONObject
import org.junit.jupiter.api.*
@@ -37,11 +36,10 @@ class AuthControllerKtTest {
}
user["id"] = 1
- coEvery { userService.getFromUsername("existing") } returns user
- coEvery { userService.userExists(1) } returns true
- coEvery { userService.getUserInfo(1) } returns User {
+ coEvery { userService.find("existing") } returns user
+ coEvery { userService.exists(1) } returns true
+ coEvery { userService.find(1) } returns User {
username = "existing"
- email = "existing@mail.com"
}
val user2 = User {
@@ -49,12 +47,12 @@ class AuthControllerKtTest {
username = "wrong"
}
user["id"] = 2
- coEvery { userService.getFromUsername("wrong") } returns user2
+ coEvery { userService.find("wrong") } returns user2
- coEvery { userService.getFromUsername("notExisting") } returns null
+ coEvery { userService.find("notExisting") } returns null
- coEvery { userService.userExists(3) } returns false
- coEvery { userService.getUserInfo(3) } returns null
+ coEvery { userService.exists(3) } returns false
+ coEvery { userService.find(3) } returns null
}
@@ -79,7 +77,7 @@ class AuthControllerKtTest {
}
}
- coVerify { userService.getFromUsername("existing") }
+ coVerify { userService.find("existing") }
res.status() `should be equal to` HttpStatusCode.OK
val jsonObject = JSONObject(res.content)
@@ -107,7 +105,7 @@ class AuthControllerKtTest {
}
}
- coVerify { userService.getFromUsername("wrong") }
+ coVerify { userService.find("wrong") }
res.status() `should be equal to` HttpStatusCode.Unauthorized
res.content `should strictly be equal to json` """{msg: "Unauthorized"}"""
@@ -122,7 +120,7 @@ class AuthControllerKtTest {
}
}
- coVerify { userService.getFromUsername("notExisting") }
+ coVerify { userService.find("notExisting") }
res.status() `should be equal to` HttpStatusCode.Unauthorized
res.content `should strictly be equal to json` """{msg: "Unauthorized"}"""
@@ -155,7 +153,7 @@ class AuthControllerKtTest {
val jsonObject = JSONObject(res.content)
jsonObject.keyList() `should be equal to` listOf("token", "refreshToken")
- coVerify { userService.userExists(1) }
+ coVerify { userService.exists(1) }
res.status() `should be equal to` HttpStatusCode.OK
}
@@ -170,7 +168,7 @@ class AuthControllerKtTest {
}
}
- coVerify { userService.userExists(3) }
+ coVerify { userService.exists(3) }
res.status() `should be equal to` HttpStatusCode.Unauthorized
res.content `should strictly be equal to json` """{msg: "Unauthorized"}"""
}
@@ -207,7 +205,7 @@ class AuthControllerKtTest {
val res = testEngine.get("/user/me") {
setToken(token)
}
- res.content `should strictly be equal to json` """{user:{username:"existing", email: "existing@mail.com"}}"""
+ res.content `should strictly be equal to json` """{user:{username:"existing"}}"""
res.status() `should be equal to` HttpStatusCode.OK
}
diff --git a/api/test/integration/routing/UserControllerKtTest.kt b/api/test/integration/routing/UserControllerKtTest.kt
index b2e1955..9615dff 100644
--- a/api/test/integration/routing/UserControllerKtTest.kt
+++ b/api/test/integration/routing/UserControllerKtTest.kt
@@ -15,7 +15,6 @@ import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import utils.*
-import java.time.LocalDateTime
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class UserControllerKtTest {
@@ -24,31 +23,21 @@ class UserControllerKtTest {
init {
// new user
- coEvery { userService.userExists("new", "new@test.com") } returns false
- coEvery { userService.createUser("new", "new@test.com", any()) } returns User {
- this.createdAt = LocalDateTime.now()
+ coEvery { userService.exists("new") } returns false
+ coEvery { userService.create("new", any()) } returns User {
this.username = "new"
- this.email = "new@test.com"
}
// existing user
- coEvery { userService.userExists("existing", "existing@test.com") } returns true
- coEvery { userService.createUser("existing", "existing@test.com", any()) } returns null
- coEvery { userService.getUserId("existing@test.com") } returns 1
- coEvery { userService.deleteUser(1) } returns true andThen false
+ coEvery { userService.exists("existing") } returns true
+ coEvery { userService.create("existing", any()) } returns null
+ coEvery { userService.delete(1) } returns true andThen false
// modified user
- coEvery { userService.userExists("modified", "modified@test.com") } returns true
- coEvery {
- userService.userExists(
- and(not("modified"), not("existing")),
- and(not("modified@test.com"), not("existing@test.com"))
- )
- } returns false
- coEvery { userService.userExists(1) } returns true
- coEvery { userService.createUser("modified", "modified@test.com", any()) } returns null
- coEvery { userService.getUserId("modified@test.com") } returns 1
- coEvery { userService.updateUser(1, "ThisIsMyNewName", "ThisIsMyNewName@mail.com", any()) } returns Unit
+ coEvery { userService.exists("modified") } returns true
+ coEvery { userService.exists(and(not("modified"), not("existing"))) } returns false
+ coEvery { userService.exists(1) } returns true
+ coEvery { userService.create("modified", any()) } returns null
}
@@ -71,11 +60,10 @@ class UserControllerKtTest {
json {
it["username"] = "new"
it["password"] = "test123abc"
- it["email"] = "new@test.com"
}
}
res.status() `should be equal to` HttpStatusCode.Created
- res.content `should be equal to json` """{msg:"Created"}"""
+ res.content `should strictly be equal to json` """{username:"new"}"""
}
@Test
@@ -83,7 +71,6 @@ class UserControllerKtTest {
val res = testEngine.post("/user") {
json {
it["username"] = "existing"
- it["email"] = "existing@test.com"
it["password"] = "test123abc"
}
}
@@ -116,28 +103,4 @@ class UserControllerKtTest {
}
}
- @Nested
- inner class ModifyUser {
-
- @Test
- fun `modify a user`() {
- val authJwt by kodein.instance("auth")
- val token = authJwt.sign(1)
-
- val res = testEngine.put("/user") {
- setToken(token)
- json {
- it["username"] = "ThisIsMyNewName"
- it["email"] = "ThisIsMyNewName@mail.com"
- it["password"] = "ThisIsMyCurrentPassword"
- }
- }
-
- res.status() `should be equal to` HttpStatusCode.OK
- res.content `should be equal to json` """{msg:"OK"}"""
-
- }
- }
-
-
}
diff --git a/api/test/integration/services/UserServiceTest.kt b/api/test/integration/services/UserServiceTest.kt
index c758707..0a4ac1a 100644
--- a/api/test/integration/services/UserServiceTest.kt
+++ b/api/test/integration/services/UserServiceTest.kt
@@ -39,17 +39,12 @@ class UserServiceTest {
fun `test create user`() {
runBlocking {
val username = "hubert"
- val email = "a@a"
val password = "password"
- userService.createUser(username, email, password)
- val id = userService.getUserId(email)
- id `should not be` null
-
- userService.getUserInfo(id!!)!!.let {
- it.username `should be equal to` username
- it.email `should be equal to` email
- }
+ userService.create(username, password)
+ val user = userService.find(username)
+ user `should not be` null
+ user?.username `should be equal to` username
}
}
@@ -57,7 +52,7 @@ class UserServiceTest {
@Order(2)
fun `test create same user`() {
runBlocking {
- userService.createUser(username = "hubert", hashedPassword = "password", email = "a@a") `should be` null
+ userService.create(username = "hubert", hashedPassword = "password") `should be` null
}
}
@@ -65,12 +60,11 @@ class UserServiceTest {
@Order(3)
fun `test delete user`() {
runBlocking {
- val email = "a@a"
- val id = userService.getUserId(email)!!
- userService.deleteUser(id)
+ val id = userService.find("hubert")!!.id
+ userService.delete(id)
- userService.getUserId(email) `should be` null
- userService.getUserInfo(id) `should be` null
+ userService.find("hubert") `should be` null
+ userService.find(id) `should be` null
}
}
diff --git a/api/test/unit/validation/RegisterValidationTest.kt b/api/test/unit/validation/RegisterValidationTest.kt
index b2ea374..0fac7da 100644
--- a/api/test/unit/validation/RegisterValidationTest.kt
+++ b/api/test/unit/validation/RegisterValidationTest.kt
@@ -14,41 +14,17 @@ class RegisterValidationTest {
val violations = registerValidator.validate(User {
username = "hubert"
password = "definitelyNotMyPassword"
- email = "test@mail.com"
})
violations.isValid `should be equal to` true
}
- @Test
- fun `invalid email test`() {
- val violations = registerValidator.validate(User {
- username = "hubert"
- password = "definitelyNotMyPassword"
- email = "teom"
- })
-
- violations.isValid `should be equal to` false
- violations.firstInvalid `should be equal to` "email"
- }
-
- @Test
- fun `missing email test`() {
- val violations = registerValidator.validate(User {
- username = "hubert"
- password = "definitelyNotMyPassword"
- })
-
- violations.isValid `should be equal to` false
- violations.firstInvalid `should be equal to` "email"
- }
@Test
fun `username too long test`() {
val violations = registerValidator.validate(User {
username = "6X9iboWmEOWjVjkO328ReTJ1gGPTTmB/ZGgBLhB6EzAJoWkJht8"
password = "definitelyNotMyPassword"
- email = "test@mail.com"
})
violations.isValid `should be equal to` false