40
loading...
This website collects cookies to deliver better user experience
konteudo
, o artifact sendo konteudo.konteudo
, e a engine como Netty.contentNegociation
, kotlinx.serialization
, GSON
e Routing
:resources
, src
e test
.resources
é uma pasta que se refere á algumas configurações do Ktor. Não iremos utiliza-lá por enquanto.
src
é a pasta onde estará os nossos arquivos de código, essa será a pasta que mais iremos utilizar
test
é a pasta que iremos armazenar os testes da nossa aplicação, neste artigo, não iremos usar esta pasta.
src/main/kotlin/konteudo/Application.kt
, este arquivo é onde a nossa aplicação está. Vamos destrinchar algumas partes deste arquivo.import io.ktor.server.engine.*
import io.ktor.server.netty.*
import konteudo.plugins.*
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
configureSerialization()
configureRouting()
}.start(wait = true)
}
main
. Que está apenas iniciando o servidor Netty, que é o servidor que escolhemos para rodar a nossa aplicação. Com isso não vamos precisar nos preocupar com configuração de Apache, Nginx, etc. Além disso, essa função inicia outras 2, sendo configureSerialization
e configureRouting
. Que cada uma está em um arquivo separado na pasta plugins
src/main/kotlin/konteudo/plugins/Serialization.kt
há a função configureSerialization
:fun Application.configureSerialization() {
install(ContentNegotiation) {
json()
gson {
}
}
routing {
get("/json/kotlinx-serialization") {
call.respond(mapOf("hello" to "world"))
}
get("/json/gson") {
call.respond(mapOf("hello" to "world"))
}
}
}
src/main/kotlin/konteudo/plugins/Routing.kt
há a função configureRouting
, que é:fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello World!")
}
}
}
get()
, post()
, delete()
, update()
, etc. que representam os métodos HTTP.call.respondText()
, informando o texto./
. A rota /
apenas é um Hello World. Mas vamos relembrar que há outras duas rotas para JSON, sendo /json/kotlinx-serialization
, e /json/gson
. As duas são iguais, e estão apenas para mostrar que a nossa aplicação consegue receber e enviar JSON.Application.kt
e depois em Run, e também abrir um terminal, e executar ./gradlew :run
.build.gradle.kts
, e no topo, veja que há 3 variáveis:val logback_version: String by project
val ktor_version: String by project
val kotlin_version: String by project
gradle.properties
.logback_version=1.2.1
ktor_version=1.5.4
kotlin.code.style=official
kotlin_version=1.4.32
exposedVersion=0.31.1
gradle.properties
, assim, teremos a versão do exposed fixa.build.gradle.kts
, e adicione na secção das variáveis de ambiente, a variável exposedVersion
val logback_version: String by project
val ktor_version: String by project
val kotlin_version: String by project
+ val exposedVersion: String by project
dependencies
, adicione três dependências á mais, que são referentes ao Exposed.dependencies {
implementation("io.ktor:ktor-serialization:$ktor_version")
implementation("io.ktor:ktor-server-core:$ktor_version")
implementation("io.ktor:ktor-gson:$ktor_version")
implementation("io.ktor:ktor-server-netty:$ktor_version")
implementation("ch.qos.logback:logback-classic:$logback_version")
testImplementation("io.ktor:ktor-server-tests:$ktor_version")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
+ implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
+ implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
+ implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
}
src/main/kotlin/konteudo
, esse pacote é uma pasta que irá armazenar os models da aplicação, e inicialmente, apenas o model User. Aperte o botão direito na pasta src/main/kotlin/konteudo
, New e depois em Package.User.kt
, para fazer isso, clique com o botão direito, New, e depois em Kotlin Class/File.@Seriazable
, que irá transformar objetos Kotlin em JSON, e JSON em objetos Kotlin. Para importa-lá, adicione esse import:import kotlinx.serialization.Serializable
@Serializable
data class User (var id: String? = null, val name: String)
@Serializable
deixa explícito que a classe abaixo poderá ser transformada em JSON e JSON poderá ser transformado nessa classedata class
é usado para classes que apenas irão guardar dados, como é o caso dessa.id
está sendo criado com var
pois é um campo mutável, podendo assim mudar seu valor, e no seu final, há um String? = null
, isso é uma maneira de fazer que esse campo também seja opicional para o kotlinx.serialization
, com seu valor padrão sendo null
caso não seja preenchido.src/main/kotlin/kotlin/models/User.kt
:import org.jetbrains.exposed.sql.*
object Users: Table(){
val id: Column<String> = char("id", 36)
val name: Column<String> = varchar("name", 50)
override val primaryKey = PrimaryKey(id, name = "PK_Users_Id")
fun toUser(row: ResultRow): User = User(
id = row[Users.id],
name = row[Users.name],
)
}
Table()
)id
e name
. As duas são do tipo Column<String>
que representa uma coluna em um banco de dados, uma sendo char
de 36 caracteres (UUID), e a outra sendo um varchar
com tamanho máximo de 50.toUser()
que iremos usar para transformar uma linha da tabela, em um usuário. Entenderemos melhor porque essa função é definida mais á frente.package konteudo.models
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.sql.*
object Users: Table(){
val id: Column<String> = char("id", 36)
val name: Column<String> = varchar("name", 50)
override val primaryKey = PrimaryKey(id, name = "PK_Users_Id")
fun toUser(row: ResultRow): User = User(
id = row[Users.id],
name = row[Users.name],
)
}
@Serializable
data class User (var id: String? = null, val name: String)
implementation("com.h2database:h2:1.4.199")
src/main/kotlin/konteudo/
chamado initDB.kt
, esse arquivo terá uma função que irá conectar ao banco de dados. e realizar uma migration, que irá transformar a classe User
em uma tabela.import konteudo.models.Users
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
initDB()
:fun initDB(){
}
Database.connect
para nos conectarmos ao banco H2.fun initDB(){
Database.connect("jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;", "org.h2.Driver")
}
transaction{ }
que representa uma transação ao banco, e dentro deste, vamos rodar a migration usando a função SchemaUtils.create()
passando a tabela Users
como argumento:fun initDB(){
Database.connect("jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;", "org.h2.Driver")
transaction {
SchemaUtils.create(Users)
}
}
src/main/kotlin/konteudo/Application.kt
e vamos adicionar a função initDB()
para fazer a conexão na inicialização da aplicação:fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
+ initDB()
configureSerialization()
configureRouting()
}.start(wait = true)
}
routes/
em src/main/kotlin/konteudo
, e um arquivo dentro de routes/
chamado UserRoutes.kt
.import konteudo.models.User
import konteudo.models.Users
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.UUID;
UserRouting()
:fun Route.userRouting(){
}
/user
fun Route.userRouting(){
route("/user"){
}
}
route("/user")
poderemos inserir as funções get()
, post()
, delete()
, put()
, etc. para começar, vamos iniciar um com um GET.fun Route.userRouting(){
route("/user"){
get {
}
}
}
/user
. Esse GET irá servir para pegar todos os usuários, e enviar como uma lista como resposta da requisição.User
usando a função toUser()
. Com isso em mente, o nosso código ficará assim:fun Route.userRouting(){
route("/user"){
get {
val users = transaction {
Users.selectAll().map { Users.toUser(it) }
}
}
}
}
transaction
para receber a transação ao banco de dados.users
, usando o método Users.selectAll()
e transformamos cada linha em uma instância da classe User
.users
.fun Route.userRouting(){
route("/user"){
get {
val users = transaction {
Users.selectAll().map { Users.toUser(it) }
}
return@get call.respond(users)
}
}
}
return@get
que deixa explícito que é o retorno de uma requisição GET.call.respond()
, que irá responder a requisição, passando a lista users
como objeto.@Serializable
á classe User
e porque temos o GSON para fazer a resposta e envio de JSON./user/{id do usuário}
.fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
}
}
}
fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
val id = call.parameters["id"] ?: return@get call.respondText(
"User not found", status = HttpStatusCode.NotFound
)
}
}
}
id
que recebe o valor do parâmetro id
da requisição, usando call.parameters["id"]
, e usando o operador ternário simplificado ?:
, caso esse ID não seja informado na requisição será enviado um erro 404return@get
para deixar claro que será o retorno da função, e como um 404 pode ser apenas uma resposta em texto, foi usado um call.respondText
, que necessita de uma mensagem, e um status, que representa o status HTTP da resposta, que no caso é 404.fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
val id = call.parameters["id"] ?: return@get call.respondText(
"User not found", status = HttpStatusCode.NotFound
)
val user: List<User> = transaction { Users.select { Users.id eq id }.map { Users.toUser(it) }}
}
}
}
user
, essa variável vai receber uma lista de instâncias da classe user
, essa lista poderá armazenar apenas um usuário, pois um ID pertence á só um usuário.transaction
, e para selecionar linhas de uma tabela usando um critério específico, podemos usar o método Users.select {}
informando o critério, que no caso é Users.id eq id
, onde necessariamente o ID da linha deve ser igual á variável id
. Após isso, todas as linhas (uma no caso) são tranformadas em instâncias da classe User
com a função toUser
.fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
val id = call.parameters["id"] ?: return@get call.respondText(
"User not found", status = HttpStatusCode.NotFound
)
val user: List<User> = transaction { Users.select { Users.id eq id }.map { Users.toUser(it) }}
if (user.isNotEmpty()) {
return@get call.respond(user.first())
}
return@get call.respondText("User not found", status = HttpStatusCode.NotFound)
}
}
}
user
, caso essa lista seja vazia (nenhum usuário com certo ID específico) será enviado como resposta um 404, com a mensagem User not found
(usuário não encontrado).call.respond
para responder com o valor de uma variável, passando user.first()
que pega o primeiro usuário.fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
...
}
post{
}
}
}
@Seriazable
que adicionamos da classe User
, vamos transformar o corpo da requisição, em uma instância da classe User
, dessa maneira:fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
...
}
post{
val user = call.receive<User>()
}
}
}
UUID
para gerar esse ID único.fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
...
}
post{
val user = call.receive<User>()
user.id = UUID.randomUUID().toString()
}
}
}
UUID.randomUUID()
para gerar um UUID aleatório, e convertemos esse UUID para String usando o .toString()
ao final.user.id
, para assim, esse usuário ter um ID definido.User
pronta para ser inserida no banco de dados.fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
...
}
post{
val user = call.receive<User>()
user.id = UUID.randomUUID().toString()
transaction {
Users.insert {
it[id] = user.id!!
it[name] = user.name
}
}
call.respondText("Created", status = HttpStatusCode.Created)
}
}
}
transaction
para inserir mais uma linha á tabela users
, usando o método Users.insert {}
e definindo o valor de cada coluna, como por exemplo it[id] = user.id
.it[id] = article.id!!
foram usados dois sinais de exclamação !!
é para definir o valor de it[id]
garantindo que o valor de article.id
não seja nulo. Isso é para garantir que esse valor tenha mudado, pois esse valor inicia-se como nulo.fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
...
}
post{
...
}
delete("id"){
}
}
}
get("{id}")
:fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
...
}
post{
...
}
delete("id"){
val id = call.parameters["id"] ?: return@delete call.respondText(
"Insert user ID to delete a user",
status = HttpStatusCode.BadRequest
)
}
}
}
get("{id}")
. mas a diferença é que usamos um return@delete
para responder á uma requisição DELETE e a mensagem de erro também é diferente, junto com o seu status, sendo 400 Bad Request
.
fun Route.userRouting(){
route("/user"){
get {
...
}
get("{id}"){
...
}
post{
...
}
delete("id"){
val id = call.parameters["id"] ?: return@delete call.respondText(
"Insert user ID to delete a user",
status = HttpStatusCode.BadRequest
)
val delete: Int = transaction {
Users.deleteWhere { Users.id eq id }
}
if (delete == 1){
return@delete call.respondText("Deleted", status = HttpStatusCode.OK)
}
return@delete call.respondText("User not found", status = HttpStatusCode.NotFound)
}
}
}
transaction
para guardar o método para deletar items do banco, que no caso é Users.deleteWhere{ }
, com o critério do ID do item ser igual á variável ID.delete
, onde caso essa variável receba 1
como valor, significa que algo foi deletado, e caso receba 0
nada foi deletado.delete
receba 1
, vamos responder com um status 200 Deleted
, pois o usuário foi deletado, e caso seja 0 será respondido com um 404 User not found
pois nenhum elemento foi deletado, logo esse ID específico não existe no banco de dados.fun Application.registerArticleRoutes(){
routing {
articleRouting()
}
}
Application.registerArticleRoutes
poderemos usar no arquivo src/main/kotlin/konteudo/plugins/Routing.kt
para registrar essas rotas.fun Route.articleRouting(){
route("/article"){
get {
val articles = transaction {
Articles.selectAll().map { Articles.toArticle(it) }
}
return@get call.respond(articles)
}
get("{id}"){
val id = call.parameters["id"] ?: return@get call.respondText(
"Article not found", status = HttpStatusCode.NotFound
)
val article: List<Article> = transaction { Articles.select { Articles.id eq id }.map { Articles.toArticle(it) }}
if (article.isNotEmpty()) {
return@get call.respond(article.first())
}
return@get call.respondText("Article not found", status = HttpStatusCode.NotFound)
}
post{
val article = call.receive<Article>()
article.id = UUID.randomUUID().toString()
transaction {
Articles.insert {
it[id] = article.id!!
it[name] = article.name
}
}
call.respondText("Created", status = HttpStatusCode.Created)
}
delete("id"){
val id = call.parameters["id"] ?: return@delete call.respondText(
"Insert article ID to delete a article",
status = HttpStatusCode.BadRequest
)
val delete: Int = transaction {
Articles.deleteWhere { Articles.id eq id }
}
if (delete == 1){
return@delete call.respondText("Deleted", status = HttpStatusCode.OK)
}
return@delete call.respondText("Article not found", status = HttpStatusCode.NotFound)
}
}
}
fun Application.registerArticleRoutes(){
routing {
articleRouting()
}
}
src/main/kotlin/konteudo/plugins/Routing.kt
, e importe a função registerArticleRoutes
que criamos.import io.ktor.routing.*
import io.ktor.http.*
import io.ktor.application.*
import io.ktor.response.*
import io.ktor.request.*
+ import konteudo.routes.registerArticleRoutes
registerArticleRoutes()
proximo ao final do arquivo, na função configureRouting()
:fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello World!")
}
}
+ registerArticleRoutes()
}
routing
que apenas guarda uma rota GET / que responde apenas um Hello World, pois essa rota não será útil no exercício final.Article
, que irá representar um artigo na nossa aplicação. Primeiro, vamos criar um arquivo em src/main/kotlin/konteudo/models/
chamado Article.kt
.import io.ktor.application.*
import io.ktor.http.*
import io.ktor.response.*
import kotlinx.serialization.Serializable
import konteudo.models.Users
import org.jetbrains.exposed.sql.*
@Serializable
data class Article (var id: String? = null, val title: String, val body: String, val author: String)
@Serializable
deixa explícito que a classe abaixo poderá ser transformada em JSON e JSON poderá ser transformado nessa classedata class
é usado para classes que apenas irão guardar dados, como é o caso dessa.id
está sendo criado com var
pois é um campo mutável, podendo assim mudar seu valor, e no seu final, há um String? = null
, isso é uma maneira de fazer que esse campo também seja opicional para o kotlinx.serialization
, com seu valor padrão sendo null
caso não seja preenchido.articles
:object Articles: Table(){
val id: Column<String> = char("id", 36)
val title: Column<String> = varchar("title", 50)
val body: Column<String> = text("body")
val author: Column<String> = char("author_id", 38) references Users.id
override val primaryKey = PrimaryKey(id, name = "PK_Articles_Id")
fun toArticle(row: ResultRow): Article = Article(
id = row[Articles.id],
title = row[Articles.title],
body = row[Articles.body],
author = row[Articles.author],
)
}
package com.konteudo.models
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.response.*
import kotlinx.serialization.Serializable
import konteudo.models.Users
import org.jetbrains.exposed.sql.*
object Articles: Table(){
val id: Column<String> = char("id", 36)
val title: Column<String> = varchar("title", 50)
val body: Column<String> = text("body")
val author: Column<String> = char("author_id", 38) references Users.id
override val primaryKey = PrimaryKey(id, name = "PK_Articles_Id")
fun toArticle(row: ResultRow): Article = Article(
id = row[Articles.id],
title = row[Articles.title],
body = row[Articles.body],
author = row[Articles.author],
)
}
@Serializable
data class Article (var id: String? = null, val title: String, val body: String, val author: String)
Articles
que haviamos criado, dessa vez temos um atributo author
que faz uma referência ao atributo Users.id
, Logo, sendo uma chave estrangeira. Isso foi feito pois esse atributo guarda o ID do artigo que criou aquele artigo, logo, é um dado que já existe no banco.src/main/kotlin/konteudo/initDB.kt
e vamos adicionar o objeto Articles
ao banco de dados, para assim, quando o banco de dados iniciar, tanto Users
quanto Articles
sejam criados.Articles
do pacote de models
.import konteudo.models.Users
+ import konteudo.models.Articles
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
transaction
do arquivo, e vamos adicionar Articles
ao banco:fun initDB(){
Database.connect("jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;", "org.h2.Driver")
transaction {
SchemaUtils.create(Users)
+ SchemaUtils.create(Articles)
}
}
users
e articles
serão criadas.src/main/kotlin/konteudo/initDB.kt
ficará assim:package konteudo
import konteudo.models.Users
import konteudo.models.Articles
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
fun initDB(){
Database.connect("jdbc:h2:mem:regular;DB_CLOSE_DELAY=-1;", "org.h2.Driver")
transaction {
SchemaUtils.create(Users)
SchemaUtils.create(Articles)
}
}
src/main/kotlin/konteudo
. Primeiro, tente criar as 4 rotas, GET /articles
, GET /articles/id
, POST /articles
, DELETE /articles
sem consultar esse artigo, pois serão os mesmos processos a serem feitos, mas caso tenha dificuldades, volte neste artigo para aprender a como criar essas rotas.import konteudo.models.Article
import konteudo.models.Articles
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.UUID;
ArticleRouting()
:fun Route.articleRouting(){
}
/article
fun Route.articleRouting(){
route("/article"){
}
}
route("/article")
poderemos inserir as funções get()
, post()
, delete()
, put()
, etc. para começar, vamos iniciar um com um GET.fun Route.articleRouting(){
route("/article"){
get {
}
}
}
/article
. Esse GET irá servir para pegar todos os artigos, e enviar como uma lista como resposta da requisição.Article
usando a função toArticle()
. Com isso em mente, o nosso código ficará assim:fun Route.articleRouting(){
route("/article"){
get {
val articles = transaction {
Articles.selectAll().map { Articles.toArticle(it) }
}
}
}
}
transaction
para receber a transação ao banco de dados.articles
, usando o método Articles.selectAll()
e transformamos cada linha em uma instância da classe Article
.articles
.fun Route.articleRouting(){
route("/article"){
get {
val articles = transaction {
Articles.selectAll().map { Articles.toArticle(it) }
}
return@get call.respond(articles)
}
}
}
return@get
que deixa explícito que é o retorno de uma requisição GET.call.respond()
, que irá responder a requisição, passando a lista articles
como objeto.@Serializable
á classe Article
e porque temos o GSON para fazer a resposta e envio de JSON./article/{id do artigo}
.fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
}
}
}
fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
val id = call.parameters["id"] ?: return@get call.respondText(
"Article not found", status = HttpStatusCode.NotFound
)
}
}
}
id
que recebe o valor do parâmetro id
da requisição, usando call.parameters["id"]
, e usando o operador ternário simplificado ?:
, caso esse ID não seja informado na requisição será enviado um erro 404return@get
para deixar claro que será o retorno da função, e como um 404 pode ser apenas uma resposta em texto, foi usado um call.respondText
, que necessita de uma mensagem, e um status, que representa o status HTTP da resposta, que no caso é 404.fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
val id = call.parameters["id"] ?: return@get call.respondText(
"Article not found", status = HttpStatusCode.NotFound
)
val article: List<Article> = transaction { Articles.select { Articles.id eq id }.map { Articles.toArticle(it) }}
}
}
}
article
, essa variável vai receber uma lista de instâncias da classe article
, essa lista poderá armazenar apenas um artigo, pois um ID pertence á só um artigo.transaction
, e para selecionar linhas de uma tabela usando um critério específico, podemos usar o método Articles.select {}
informando o critério, que no caso é Articles.id eq id
, onde necessariamente o ID da linha deve ser igual á variável id
. Após isso, todas as linhas (uma no caso) são tranformadas em instâncias da classe Article
com a função toArticle
.fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
val id = call.parameters["id"] ?: return@get call.respondText(
"Article not found", status = HttpStatusCode.NotFound
)
val article: List<Article> = transaction { Articles.select { Articles.id eq id }.map { Articles.toArticle(it) }}
if (article.isNotEmpty()) {
return@get call.respond(article.first())
}
return@get call.respondText("Article not found", status = HttpStatusCode.NotFound)
}
}
}
article
, caso essa lista seja vazia (nenhum artigo com certo ID específico) será enviado como resposta um 404, com a mensagem Article not found
(artigo não encontrado).call.respond
para responder com o valor de uma variável, passando article.first()
que pega o primeiro artigo.fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
...
}
post{
}
}
}
@Seriazable
que adicionamos da classe Article
, vamos transformar o corpo da requisição, em uma instância da classe Article
, dessa maneira:fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
...
}
post{
val article = call.receive<Article>()
}
}
}
UUID
para gerar esse ID único.fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
...
}
post{
val article = call.receive<Article>()
article.id = UUID.randomUUID().toString()
}
}
}
UUID.randomUUID()
para gerar um UUID aleatório, e convertemos esse UUID para String usando o .toString()
ao final.article.id
, para assim, esse artigo ter um ID definido.Article
pronta para ser inserida no banco de dados.fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
...
}
post{
val article = call.receive<Article>()
article.id = UUID.randomUUID().toString()
transaction {
Articles.insert {
it[id] = article.id!!
it[title] = article.title
it[body] = article.body
it[author] = article.author
}
}
call.respondText("Created", status = HttpStatusCode.Created)
}
}
}
transaction
para inserir mais uma linha á tabela articles
, usando o método Articles.insert {}
e definindo o valor de cada coluna, como por exemplo it[id] = article.id
.it[id] = article.id!!
foram usados dois sinais de exclamação !!
é para definir o valor de it[id]
garantindo que o valor de article.id
não seja nulo. Isso é para garantir que esse valor tenha mudado, pois esse valor inicia-se como nulo.fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
...
}
post{
...
}
delete("id"){
}
}
}
get("{id}")
:fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
...
}
post{
...
}
delete("id"){
val id = call.parameters["id"] ?: return@delete call.respondText(
"Insert article ID to delete a article",
status = HttpStatusCode.BadRequest
)
}
}
}
get("{id}")
. mas a diferença é que usamos um return@delete
para responder á uma requisição DELETE, uma mensagem que será enviada caso o ID não seja informado e um status 400 Bad Request
, deixando claro que a request não pode ser efetuada com sucesso.
fun Route.articleRouting(){
route("/article"){
get {
...
}
get("{id}"){
...
}
post{
...
}
delete("id"){
val id = call.parameters["id"] ?: return@delete call.respondText(
"Insert article ID to delete a article",
status = HttpStatusCode.BadRequest
)
val delete: Int = transaction {
Articles.deleteWhere { Articles.id eq id }
}
if (delete == 1){
return@delete call.respondText("Deleted", status = HttpStatusCode.OK)
}
return@delete call.respondText("Article not found", status = HttpStatusCode.NotFound)
}
}
}
transaction
para guardar o método para deletar items do banco, que no caso é Articles.deleteWhere{ }
, com o critério do ID do item ser igual á variável ID.delete
, onde caso essa variável receba 1
como valor, significa que algo foi deletado, e caso receba 0
nada foi deletado.delete
receba 1
, vamos responder com um status 200 Deleted
, pois o artigo foi deletado, e caso seja 0 será respondido com um 404 Article not found
pois nenhum elemento foi deletado, logo esse ID específico não existe no banco de dados.import konteudo.models.Article
import konteudo.models.Articles
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.UUID;
fun Route.articleRouting(){
route("/article"){
get {
val articles = transaction {
Articles.selectAll().map { Articles.toArticle(it) }
}
return@get call.respond(articles)
}
get("{id}"){
val id = call.parameters["id"] ?: return@get call.respondText(
"Article not found", status = HttpStatusCode.NotFound
)
val article: List<Article> = transaction { Articles.select { Articles.id eq id }.map { Articles.toArticle(it) }}
if (article.isNotEmpty()) {
return@get call.respond(article.first())
}
return@get call.respondText("Article not found", status = HttpStatusCode.NotFound)
}
post{
val article = call.receive<Article>()
article.id = UUID.randomUUID().toString()
transaction {
Articles.insert {
it[id] = article.id
it[title] = article.title
it[body] = article.body
it[author] = article.author
}
}
call.respondText("Created", status = HttpStatusCode.Created)
}
delete("id"){
val id = call.parameters["id"] ?: return@delete call.respondText(
"Insert article ID to delete a article",
status = HttpStatusCode.BadRequest
)
val delete: Int = transaction {
Articles.deleteWhere { Articles.id eq id }
}
if (delete == 1){
return@delete call.respondText("Deleted", status = HttpStatusCode.OK)
}
return@delete call.respondText("Article not found", status = HttpStatusCode.NotFound)
}
}
}
fun Application.registerArticleRoutes(){
routing {
articleRouting()
}
}
src/main/kotlin/konteudo/plugins/Routing.kt
, e importe a função registerArticleRoutes
que criamos.import io.ktor.routing.*
import io.ktor.http.*
import io.ktor.application.*
import io.ktor.response.*
import io.ktor.request.*
import konteudo.routes.registerUserRoutes
+ import konteudo.routes.registerArticleRoutes
registerArticleRoutes()
proximo ao final do arquivo, na função configureRouting()
:fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello World!")
}
}
registerUserRoutes()
+ registerArticleRoutes()
}