Merge branch 'feature/notes-controller'

This commit is contained in:
Hubert Van De Walle 2020-04-19 22:59:22 +02:00
commit 2a142504a5
13 changed files with 119 additions and 90 deletions

14
api/http/test.http Normal file
View File

@ -0,0 +1,14 @@
# Register a new user
POST http://localhost:8081/login
Content-Type: application/json
{
"username": "hubert",
"password": "test"
}
> {% client.global.set("token", response.body.token); %}
### Get notes
GET http://localhost:8081/notes
Authorization: Bearer {{token}}

View File

@ -0,0 +1,5 @@
ALTER TABLE `Notes`
DROP COLUMN `last_viewed`;
ALTER TABLE `Notes`
ADD COLUMN `updated_at` datetime;

View File

@ -9,6 +9,7 @@ import be.vandewalleh.features.features
import be.vandewalleh.migrations.Migration
import io.ktor.application.Application
import io.ktor.application.log
import io.ktor.auth.authenticate
import io.ktor.routing.routing
import me.liuwj.ktorm.database.Database
import org.kodein.di.Kodein

View File

@ -0,0 +1,31 @@
package be.vandewalleh.controllers
import io.ktor.application.ApplicationCall
import io.ktor.auth.UserIdPrincipal
import io.ktor.auth.authenticate
import io.ktor.auth.principal
import io.ktor.routing.Route
import io.ktor.routing.Routing
import io.ktor.routing.route
import org.kodein.di.Kodein
abstract class AuthCrudController(
private val path: String,
override val kodein: Kodein
) :
KodeinController(kodein) {
abstract val route: Route.() -> Unit
fun ApplicationCall.userEmail(): String =
this.principal<UserIdPrincipal>()!!.name
override fun Routing.registerRoutes() {
authenticate {
route(path) {
route()
}
}
}
}

View File

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

View File

@ -1,27 +1,14 @@
package be.vandewalleh.controllers
import io.ktor.application.Application
import io.ktor.locations.locations
import io.ktor.routing.Routing
import org.kodein.di.Kodein
import org.kodein.di.KodeinAware
import org.kodein.di.generic.instance
abstract class KodeinController(override val kodein: Kodein) : KodeinAware {
/**
* Injected dependency with the current [Application].
*/
val application: Application by instance()
/**
* Shortcut to get the url of a [TypedRoute].
*/
val TypedRoute.href get() = application.locations.href(this)
/**
* Method that subtypes must override to register the handled [Routing] routes.
*/
abstract fun Routing.registerRoutes()
}
interface TypedRoute

View File

@ -1,73 +0,0 @@
package be.vandewalleh.controllers
import be.vandewalleh.entities.Note
import be.vandewalleh.entities.Tag
import be.vandewalleh.errors.ApiError
import be.vandewalleh.tables.Notes
import be.vandewalleh.tables.Tags
import be.vandewalleh.tables.Users
import io.ktor.application.call
import io.ktor.auth.UserIdPrincipal
import io.ktor.auth.authenticate
import io.ktor.auth.principal
import io.ktor.http.HttpStatusCode
import io.ktor.locations.Location
import io.ktor.locations.post
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.entity.add
import me.liuwj.ktorm.entity.find
import me.liuwj.ktorm.entity.sequenceOf
import org.kodein.di.Kodein
import org.kodein.di.generic.instance
class NoteController(kodein: Kodein) : KodeinController(kodein) {
private val db by instance<Database>()
override fun Routing.registerRoutes() {
authenticate {
post<Routes.Notes> {
data class Response(val message: String)
val (email) = call.principal<UserIdPrincipal>()!!
val body = call.receive<PostNotesBody>()
val user = db.sequenceOf(Users)
.find { Users.email eq email }
?: return@post call.respond(HttpStatusCode.Forbidden, ApiError.DeletedUserError)
val transaction = db.useTransaction {
val note = Note {
this.title = body.title
this.user = user
}
db.sequenceOf(Notes).add(note)
body.tags.forEach { tagName ->
val tag = Tag {
this.name = tagName
this.note = note
}
db.sequenceOf(Tags).add(tag)
}
1
}
return@post call.respond(Response("created"))
}
}
}
object Routes {
@Location("/notes")
class Notes
}
}
data class PostNotesBody(val title: String, val content: String, val tags: List<String>)

View File

@ -0,0 +1,59 @@
package be.vandewalleh.controllers
import be.vandewalleh.tables.Notes
import be.vandewalleh.tables.Tags
import be.vandewalleh.tables.Users
import io.ktor.application.call
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Route
import io.ktor.routing.get
import me.liuwj.ktorm.database.Database
import me.liuwj.ktorm.dsl.*
import org.kodein.di.Kodein
import org.kodein.di.generic.instance
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class NotesController(kodein: Kodein) : AuthCrudController("/notes", kodein) {
private val db by kodein.instance<Database>()
private class ResponseItem(val title: String, val tags: List<String>, val updatedAt: String)
override val route: Route.() -> Unit = {
get {
val email = call.userEmail()
val list = db.from(Notes)
.leftJoin(Users, on = Users.id eq Notes.userId)
.select(Notes.id, Notes.title, Notes.updatedAt)
.where { Users.email eq email }
.orderBy(Notes.updatedAt.desc())
.map { row ->
Notes.createEntity(row)
}
.toList()
val response = mutableListOf<ResponseItem>()
list.forEach { note ->
val tags = db.from(Tags)
.select(Tags.name)
.where { Tags.noteId eq note.id }
.map { it[Tags.name]!! }
.toList()
val updatedAt = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(note.updatedAt)
val item = ResponseItem(
title = note.title,
tags = tags,
updatedAt = updatedAt
)
response += item
}
call.respond(response)
}
}
}

View File

@ -26,6 +26,8 @@ class UserController(kodein: Kodein) : KodeinController(kodein) {
private val db by instance<Database>()
override fun Routing.registerRoutes() {
post<Routes.Login> {
data class Response(val token: String)

View File

@ -9,5 +9,5 @@ interface Note : Entity<Note> {
val id: Int
var title: String
var user: User
var lastViewed: LocalDateTime?
var updatedAt: LocalDateTime
}

View File

@ -12,4 +12,5 @@ object Chapters : Table<Chapter>("Chapters") {
val number by int("number").bindTo { it.number }
val content by text("content").bindTo { it.content }
val noteId by int("note_id").references(Notes) { it.note }
val note get() = noteId.referenceTable as Notes
}

View File

@ -6,6 +6,7 @@ import me.liuwj.ktorm.schema.*
object Notes : Table<Note>("Notes") {
val id by int("id").primaryKey().bindTo { it.id }
val title by varchar("title").bindTo { it.title }
val user by int("user_id").references(Users) { it.user }
val lastViewed by datetime("last_viewed").bindTo { it.lastViewed }
val userId by int("user_id").references(Users) { it.user }
val updatedAt by datetime("updated_at").bindTo { it.updatedAt }
val user get() = userId.referenceTable as Users
}

View File

@ -9,4 +9,5 @@ object Tags : Table<Tag>("Tags") {
val id by int("id").primaryKey().bindTo { it.id }
val name by varchar("name").bindTo { it.name }
val noteId by int("note_id").references(Notes) { it.note }
val note get() = noteId.referenceTable as Notes
}