package fr.dcproject.application import com.fasterxml.jackson.core.util.DefaultIndenter import com.fasterxml.jackson.core.util.DefaultPrettyPrinter import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.datatype.joda.JodaModule import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException import fr.dcproject.application.Env.PROD import fr.dcproject.component.article.articleKoinModule import fr.dcproject.component.article.routes.installArticleRoutes import fr.dcproject.component.auth.ForbiddenException import fr.dcproject.component.auth.authKoinModule import fr.dcproject.component.auth.jwt.jwtInstallation import fr.dcproject.component.auth.routes.installAuthRoutes import fr.dcproject.component.auth.user import fr.dcproject.component.citizen.citizenKoinModule import fr.dcproject.component.citizen.routes.installCitizenRoutes import fr.dcproject.component.comment.article.routes.installCommentArticleRoutes import fr.dcproject.component.comment.commentKoinModule import fr.dcproject.component.comment.constitution.routes.installCommentConstitutionRoutes import fr.dcproject.component.comment.generic.routes.installCommentRoutes import fr.dcproject.component.constitution.constitutionKoinModule import fr.dcproject.component.constitution.routes.installConstitutionRoutes import fr.dcproject.component.follow.followKoinModule import fr.dcproject.component.follow.routes.article.installFollowArticleRoutes import fr.dcproject.component.follow.routes.constitution.installFollowConstitutionRoutes import fr.dcproject.component.opinion.opinionKoinModule import fr.dcproject.component.opinion.routes.installOpinionRoutes import fr.dcproject.component.views.viewKoinModule import fr.dcproject.component.vote.routes.installVoteRoutes import fr.dcproject.component.vote.voteKoinModule import fr.dcproject.component.workgroup.routes.installWorkgroupRoutes import fr.dcproject.component.workgroup.workgroupKoinModule import fr.dcproject.notification.EventNotification import fr.dcproject.routes.definition import fr.dcproject.routes.notificationArticle import fr.dcproject.security.AccessDeniedException import fr.postgresjson.migration.Migrations import io.ktor.application.Application import io.ktor.application.call import io.ktor.application.install import io.ktor.auth.Authentication import io.ktor.auth.authenticate import io.ktor.client.HttpClient import io.ktor.client.engine.jetty.Jetty import io.ktor.features.AutoHeadResponse import io.ktor.features.CORS import io.ktor.features.CallLogging import io.ktor.features.ContentNegotiation import io.ktor.features.DataConversion import io.ktor.features.NotFoundException import io.ktor.features.StatusPages import io.ktor.features.maxAge import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.http.cio.websocket.pingPeriod import io.ktor.http.cio.websocket.timeout import io.ktor.jackson.jackson import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.Locations import io.ktor.response.respond import io.ktor.routing.Routing import io.ktor.server.jetty.EngineMain import io.ktor.util.KtorExperimentalAPI import io.ktor.websocket.WebSockets import kotlinx.coroutines.ExperimentalCoroutinesApi import org.eclipse.jetty.util.log.Slf4jLog import org.koin.ktor.ext.Koin import org.koin.ktor.ext.get import org.slf4j.event.Level import java.time.Duration import java.util.concurrent.CompletionException fun main(args: Array): Unit = EngineMain.main(args) enum class Env { PROD, TEST, CUCUMBER } @ExperimentalCoroutinesApi @KtorExperimentalAPI @KtorExperimentalLocationsAPI @Suppress("unused") // Referenced in application.conf fun Application.module(env: Env = PROD) { install(Koin) { Slf4jLog() modules( listOf( KoinModule, articleKoinModule, authKoinModule, citizenKoinModule, commentKoinModule, constitutionKoinModule, followKoinModule, opinionKoinModule, viewKoinModule, voteKoinModule, workgroupKoinModule, ) ) } install(CallLogging) { level = Level.INFO } install(DataConversion, converters) install(Locations) HttpClient(Jetty) { engine { } } install(WebSockets) { pingPeriod = Duration.ofSeconds(60) // Disabled (null) by default timeout = Duration.ofSeconds(15) maxFrameSize = Long.MAX_VALUE // Disabled (max value). The connection will be closed if surpassed this length. masking = false } EventNotification(get(), get(), get(), get(), get(), Configuration.exchangeNotificationName).config() install(Authentication, jwtInstallation(get())) install(AutoHeadResponse) install(ContentNegotiation) { jackson { propertyNamingStrategy = PropertyNamingStrategies.SNAKE_CASE registerModule(JodaModule()) disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) configure(SerializationFeature.INDENT_OUTPUT, true) setDefaultPrettyPrinter( DefaultPrettyPrinter().apply { indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance) indentObjectsWith(DefaultIndenter(" ", "\n")) } ) } } install(Routing.Feature) { // trace { application.log.trace(it.buildText()) } installArticleRoutes() installAuthRoutes() installCitizenRoutes() installCommentArticleRoutes() installCommentRoutes() installFollowArticleRoutes() installFollowConstitutionRoutes() installWorkgroupRoutes() installOpinionRoutes() installVoteRoutes() installConstitutionRoutes() installCommentConstitutionRoutes() authenticate(optional = true) { definition() } authenticate("url") { notificationArticle(get()) } } install(StatusPages) { // TODO move to postgresJson lib exception { e -> val parent = e.cause?.cause if (parent is GenericDatabaseException) { call.respond(HttpStatusCode.BadRequest, parent.errorMessage.message!!) } else { throw e } } exception { e -> call.respond(HttpStatusCode.NotFound, e.message!!) } exception { if (call.user == null) call.respond(HttpStatusCode.Unauthorized) else call.respond(HttpStatusCode.Forbidden) } exception { call.respond(HttpStatusCode.Forbidden) } } install(CORS) { method(HttpMethod.Options) method(HttpMethod.Put) method(HttpMethod.Delete) header(HttpHeaders.Authorization) anyHost() // host("localhost:4200", schemes = listOf("http", "https")) allowCredentials = true allowSameOrigin = true maxAge = Duration.ofDays(1) } if (env == PROD) { get().run() } }