Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ group 'io.realworld'
version '1.0-SNAPSHOT'

ext {
javalin_version = "2.6.0"
jackson_version = "2.9.7"
slf4j_version = "1.7.25"
javalin_version = "3.8.0"
jackson_version = "2.10.1"
slf4j_version = "1.7.28"
koin_version = "1.0.2"
junit_version = "4.12"
unirest_version = "1.4.9"
Expand All @@ -13,7 +13,8 @@ ext {
h2_version = "1.4.197"
exposed_version = "0.11.1"
slugify_version = "2.1.+"
swagger_version = "3.17.6"
swagger_version = "3.24.3"
swagger_core_version = "2.0.9"
}

buildscript {
Expand Down Expand Up @@ -51,6 +52,7 @@ dependencies {
compile "org.jetbrains.exposed:exposed:$exposed_version"
compile "com.github.slugify:slugify:$slugify_version"
compile "org.webjars:swagger-ui:$swagger_version"
compile "io.swagger.core.v3:swagger-core:$swagger_core_version"

testCompile "junit:junit:$junit_version"
testCompile "com.mashape.unirest:unirest-java:$unirest_version"
Expand Down
55 changes: 34 additions & 21 deletions src/main/kotlin/io/realworld/app/config/AppConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ package io.realworld.app.config
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.javalin.Javalin
import io.javalin.JavalinEvent
import io.javalin.json.JavalinJackson
import io.javalin.core.plugin.Plugin
import io.javalin.core.security.SecurityUtil.roles
import io.javalin.plugin.json.JavalinJackson
import io.javalin.plugin.openapi.OpenApiOptions
import io.javalin.plugin.openapi.OpenApiPlugin
import io.javalin.plugin.openapi.ui.SwaggerOptions
import io.realworld.app.config.ModulesConfig.allModules
import io.realworld.app.web.ErrorExceptionMapping
import io.realworld.app.web.Router
import io.swagger.v3.oas.models.info.Info
import org.eclipse.jetty.server.Server
import org.koin.core.KoinProperties
import org.koin.standalone.KoinComponent
import org.koin.standalone.StandAloneContext
Expand All @@ -21,32 +27,39 @@ class AppConfig : KoinComponent {

fun setup(): Javalin {
StandAloneContext.startKoin(
allModules,
KoinProperties(true, true)
allModules,
KoinProperties(true, true)
)
return Javalin.create()
.also { app ->
this.configureMapper()
app.enableCorsForAllOrigins()
.contextPath(getProperty("context"))
.event(JavalinEvent.SERVER_STOPPING) {
StandAloneContext.stopKoin()
}
authConfig.configure(app)
router.register(app)
ErrorExceptionMapping.register(app)
app.port(getProperty("server_port"))
app.enableWebJars()
this.configureMapper()
val app = Javalin.create { config ->
config.apply {
enableWebjars()
enableCorsForAllOrigins()
contextPath = getProperty("context")
addStaticFiles("/swagger")
addSinglePageRoot("","/swagger/swagger-ui.html")
server {
Server(getProperty("server_port") as Int)
}
}
}.events {
it.serverStopping {
StandAloneContext.stopKoin()
}
}
authConfig.configure(app)
router.register(app)
ErrorExceptionMapping.register(app)
return app
}

private fun configureMapper() {
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
JavalinJackson.configure(
jacksonObjectMapper()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setDateFormat(dateFormat)
.configure(SerializationFeature.WRITE_DATES_WITH_ZONE_ID, true)
jacksonObjectMapper()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setDateFormat(dateFormat)
.configure(SerializationFeature.WRITE_DATES_WITH_ZONE_ID, true)
)
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/io/realworld/app/config/AuthConfig.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package io.realworld.app.config

import com.auth0.jwt.interfaces.DecodedJWT
import io.javalin.Context
import io.javalin.ForbiddenResponse
import io.javalin.Javalin
import io.javalin.security.Role
import io.javalin.core.security.Role
import io.javalin.http.Context
import io.javalin.http.ForbiddenResponse
import io.realworld.app.utils.JwtProvider

internal enum class Roles : Role {
Expand All @@ -15,7 +15,7 @@ private const val headerTokenName = "Authorization"

class AuthConfig(private val jwtProvider: JwtProvider) {
fun configure(app: Javalin) {
app.accessManager { handler, ctx, permittedRoles ->
app.config.accessManager { handler, ctx, permittedRoles ->
val jwtToken = getJwtTokenHeader(ctx)
val userRole = getUserRole(jwtToken) ?: Roles.ANYONE
permittedRoles.takeIf { !it.contains(userRole) }?.apply { throw ForbiddenResponse() }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.realworld.app.domain.repository

import io.javalin.NotFoundResponse
import io.javalin.http.NotFoundResponse
import io.realworld.app.domain.Article
import io.realworld.app.domain.User
import org.jetbrains.exposed.sql.Column
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.realworld.app.domain.repository

import io.javalin.BadRequestResponse
import io.javalin.NotFoundResponse
import io.javalin.http.BadRequestResponse
import io.javalin.http.NotFoundResponse
import io.realworld.app.domain.Comment
import io.realworld.app.domain.User
import org.jetbrains.exposed.dao.LongIdTable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.realworld.app.domain.repository

import io.javalin.NotFoundResponse
import io.javalin.http.NotFoundResponse
import io.realworld.app.domain.User
import org.jetbrains.exposed.dao.LongIdTable
import org.jetbrains.exposed.sql.Column
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.realworld.app.domain.service

import com.github.slugify.Slugify
import io.javalin.BadRequestResponse
import io.javalin.InternalServerErrorResponse
import io.javalin.NotFoundResponse
import io.javalin.http.BadRequestResponse
import io.javalin.http.InternalServerErrorResponse
import io.javalin.http.NotFoundResponse
import io.realworld.app.domain.Article
import io.realworld.app.domain.repository.ArticleRepository
import io.realworld.app.domain.repository.UserRepository
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.realworld.app.domain.service

import io.javalin.BadRequestResponse
import io.javalin.http.BadRequestResponse
import io.realworld.app.domain.Comment
import io.realworld.app.domain.repository.CommentRepository

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.realworld.app.domain.service

import io.javalin.BadRequestResponse
import io.javalin.HttpResponseException
import io.javalin.NotFoundResponse
import io.javalin.UnauthorizedResponse
import io.javalin.http.BadRequestResponse
import io.javalin.http.HttpResponseException
import io.javalin.http.NotFoundResponse
import io.javalin.http.UnauthorizedResponse
import io.realworld.app.config.Roles
import io.realworld.app.domain.Profile
import io.realworld.app.domain.User
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/io/realworld/app/utils/JwtProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.realworld.app.utils

import com.auth0.jwt.JWT
import com.auth0.jwt.interfaces.DecodedJWT
import io.javalin.security.Role
import io.javalin.core.security.Role
import io.realworld.app.domain.User
import java.util.*

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package io.realworld.app.web

import com.auth0.jwt.exceptions.JWTVerificationException
import io.javalin.BadRequestResponse
import io.javalin.ForbiddenResponse
import io.javalin.HttpResponseException
import io.javalin.Javalin
import io.javalin.NotFoundResponse
import io.javalin.UnauthorizedResponse
import io.javalin.http.*
import org.eclipse.jetty.http.HttpStatus
import org.jetbrains.exposed.exceptions.ExposedSQLException
import org.slf4j.LoggerFactory
Expand Down
4 changes: 1 addition & 3 deletions src/main/kotlin/io/realworld/app/web/Router.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import io.javalin.apibuilder.ApiBuilder.get
import io.javalin.apibuilder.ApiBuilder.path
import io.javalin.apibuilder.ApiBuilder.post
import io.javalin.apibuilder.ApiBuilder.put
import io.javalin.core.util.SwaggerRenderer
import io.javalin.security.SecurityUtil.roles
import io.javalin.core.security.SecurityUtil.roles
import io.realworld.app.config.Roles
import io.realworld.app.web.controllers.ArticleController
import io.realworld.app.web.controllers.CommentController
Expand Down Expand Up @@ -64,7 +63,6 @@ class Router(
path("tags") {
get(tagController::get, rolesOptionalAuthenticated)
}
get("", SwaggerRenderer("swagger/api.yaml"), rolesOptionalAuthenticated)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.realworld.app.web.controllers

import io.javalin.Context
import io.javalin.http.Context
import io.realworld.app.domain.ArticleDTO
import io.realworld.app.domain.ArticlesDTO
import io.realworld.app.domain.service.ArticleService
Expand All @@ -27,54 +27,54 @@ class ArticleController(private val articleService: ArticleService) {
}

fun get(ctx: Context) {
ctx.validatedPathParam("slug")
ctx.pathParam<String>("slug")
.check({ it.isNotBlank() })
.getOrThrow().also { slug ->
.get().also { slug ->
articleService.findBySlug(slug).apply {
ctx.json(ArticleDTO(this))
}
}
}

fun create(ctx: Context) {
ctx.validatedBody<ArticleDTO>()
ctx.bodyValidator<ArticleDTO>()
.check({ !it.article?.title.isNullOrBlank() })
.check({ !it.article?.description.isNullOrBlank() })
.check({ !it.article?.body.isNullOrBlank() })
.getOrThrow().article?.also { article ->
.get().article?.also { article ->
articleService.create(ctx.attribute("email"), article).apply {
ctx.json(ArticleDTO(this))
}
}
}

fun update(ctx: Context) {
val slug = ctx.validatedPathParam("slug").getOrThrow()
ctx.validatedBody<ArticleDTO>()
val slug = ctx.pathParam<String>("slug").get()
ctx.bodyValidator<ArticleDTO>()
.check({ !it.article?.body.isNullOrBlank() })
.getOrThrow().article?.also { article ->
.get().article?.also { article ->
articleService.update(slug, article).apply {
ctx.json(ArticleDTO(this))
}
}
}

fun delete(ctx: Context) {
ctx.validatedPathParam("slug").getOrThrow().also { slug ->
ctx.pathParam<String>("slug").get().also { slug ->
articleService.delete(slug)
}
}

fun favorite(ctx: Context) {
ctx.validatedPathParam("slug").getOrThrow().also { slug ->
ctx.pathParam<String>("slug").get().also { slug ->
articleService.favorite(ctx.attribute("email"), slug).apply {
ctx.json(ArticleDTO(this))
}
}
}

fun unfavorite(ctx: Context) {
ctx.validatedPathParam("slug").getOrThrow().also { slug ->
ctx.pathParam<String>("slug").get().also { slug ->
articleService.unfavorite(ctx.attribute("email"), slug).apply {
ctx.json(ArticleDTO(this))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
package io.realworld.app.web.controllers

import io.javalin.Context
import io.javalin.http.Context
import io.realworld.app.domain.CommentDTO
import io.realworld.app.domain.CommentsDTO
import io.realworld.app.domain.service.CommentService

class CommentController(private val commentService: CommentService) {
fun add(ctx: Context) {
val slug = ctx.validatedPathParam("slug").getOrThrow()
ctx.validatedBody<CommentDTO>()
val slug = ctx.pathParam<String>("slug").get()
ctx.bodyValidator<CommentDTO>()
.check({ !it.comment?.body.isNullOrBlank() })
.getOrThrow().apply {
.get().apply {
commentService.add(slug, ctx.attribute("email")!!, this.comment!!).also {
ctx.json(CommentDTO(it))
}
}
}

fun findBySlug(ctx: Context) {
ctx.validatedPathParam("slug").getOrThrow().apply {
ctx.pathParam<String>("slug").get().apply {
commentService.findBySlug(this).also { comments ->
ctx.json(CommentsDTO(comments))
}
}
}

fun delete(ctx: Context) {
val slug = ctx.validatedPathParam("slug").getOrThrow()
val id = ctx.validatedPathParam("id").asLong().getOrThrow()
val slug = ctx.pathParam<String>("slug").get()
val id = ctx.pathParam<Long>("id").get()
commentService.delete(id, slug)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
package io.realworld.app.web.controllers

import io.javalin.Context
import io.javalin.http.Context
import io.realworld.app.domain.ProfileDTO
import io.realworld.app.domain.service.UserService

class ProfileController(private val userService: UserService) {
fun get(ctx: Context) {
ctx.validatedPathParam("username").getOrThrow().also { usernameFollowing ->
ctx.pathParam<String>("username").get().also { usernameFollowing ->
userService.getProfileByUsername(ctx.attribute("email")!!, usernameFollowing).also { profile ->
ctx.json(ProfileDTO(profile))
}
}
}

fun follow(ctx: Context) {
ctx.validatedPathParam("username").getOrThrow().also { usernameToFollow ->
ctx.pathParam<String>("username").get().also { usernameToFollow ->
userService.follow(ctx.attribute("email")!!, usernameToFollow).also { profile ->
ctx.json(ProfileDTO(profile))
}
}
}

fun unfollow(ctx: Context) {
ctx.validatedPathParam("username").getOrThrow().also { usernameToUnfollow ->
ctx.pathParam<String>("username").get().also { usernameToUnfollow ->
userService.unfollow(ctx.attribute("email")!!, usernameToUnfollow).also { profile ->
ctx.json(ProfileDTO(profile))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.realworld.app.web.controllers

import io.javalin.Context
import io.javalin.http.Context
import io.realworld.app.domain.service.TagService

class TagController(private val tagService: TagService) {
Expand Down
Loading