From a442f70af9fcea7ad7841fa8f59b5de8ee2f4c86 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:42:07 +0000 Subject: [PATCH 1/4] Initial plan From 9e2c503abdc00adc4532650b6d34a41bbee0bb3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:46:14 +0000 Subject: [PATCH 2/4] Convert dependencies and core application from Dropwizard to Ktor Co-authored-by: evanchooly <195021+evanchooly@users.noreply.github.com> --- pom.xml | 63 +++++-- .../kotlin/javabot/web/JavabotApplication.kt | 159 ++++++++++-------- .../javabot/web/JavabotApplication.kt.bak | 1 + .../javabot/web/JavabotConfiguration.kt | 3 +- .../javabot/web/resources/BotResource.kt | 146 +++++++--------- .../web/resources/KtorServletRequest.kt | 135 +++++++++++++++ src/main/kotlin/javabot/web/views/MainView.kt | 20 ++- 7 files changed, 349 insertions(+), 178 deletions(-) create mode 100644 src/main/kotlin/javabot/web/JavabotApplication.kt.bak create mode 100644 src/main/kotlin/javabot/web/resources/KtorServletRequest.kt diff --git a/pom.xml b/pom.xml index e65fe9e9..165d5652 100644 --- a/pom.xml +++ b/pom.xml @@ -333,34 +333,61 @@ 1.9.0 - + - io.dropwizard - dropwizard-core - ${dropwizard.version} + io.ktor + ktor-server-core-jvm + ${ktor.version} - io.dropwizard - dropwizard-auth - ${dropwizard.version} + io.ktor + ktor-server-netty-jvm + ${ktor.version} - - io.dropwizard - dropwizard-assets - ${dropwizard.version} + io.ktor + ktor-server-sessions-jvm + ${ktor.version} + + + + io.ktor + ktor-server-auth-jvm + ${ktor.version} + + + + io.ktor + ktor-server-freemarker-jvm + ${ktor.version} + + + + io.ktor + ktor-server-status-pages-jvm + ${ktor.version} + + + + io.ktor + ktor-server-call-logging-jvm + ${ktor.version} + + + + io.ktor + ktor-server-content-negotiation-jvm + ${ktor.version} - - - io.dropwizard - dropwizard-views-freemarker - ${dropwizard.version} + io.ktor + ktor-serialization-jackson-jvm + ${ktor.version} - + @@ -500,7 +527,7 @@ 1.1.1 4.4.4 - 2.1.5 + 3.0.3 5.1.0 2.21.0 0.8.5 diff --git a/src/main/kotlin/javabot/web/JavabotApplication.kt b/src/main/kotlin/javabot/web/JavabotApplication.kt index 6cfee532..84140bc9 100644 --- a/src/main/kotlin/javabot/web/JavabotApplication.kt +++ b/src/main/kotlin/javabot/web/JavabotApplication.kt @@ -4,14 +4,18 @@ import com.google.inject.Guice import com.google.inject.Inject import com.google.inject.Injector import com.google.inject.Singleton -import io.dropwizard.Application -import io.dropwizard.assets.AssetsBundle -import io.dropwizard.setup.Bootstrap -import io.dropwizard.setup.Environment -import io.dropwizard.views.ViewBundle +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import io.ktor.server.plugins.statuspages.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.server.sessions.* +import io.ktor.server.freemarker.* +import freemarker.cache.ClassTemplateLoader import java.io.File import java.nio.file.Files -import java.util.EnumSet import javabot.Javabot import javabot.JavabotConfig import javabot.JavabotModule @@ -19,97 +23,106 @@ import javabot.dao.ApiDao import javabot.web.resources.AdminResource import javabot.web.resources.BotResource import javabot.web.resources.PublicOAuthResource -import javax.servlet.DispatcherType -import javax.servlet.Filter -import javax.servlet.FilterChain -import javax.servlet.FilterConfig -import javax.servlet.ServletRequest -import javax.servlet.ServletResponse -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse -import org.eclipse.jetty.server.session.SessionHandler import org.slf4j.LoggerFactory @Singleton -class JavabotApplication @Inject constructor(var injector: Injector) : - Application() { +class JavabotApplication @Inject constructor(var injector: Injector) { var running = false - + lateinit var server: NettyApplicationEngine + companion object { private val LOG = LoggerFactory.getLogger(JavabotApplication::class.java) @Throws(Exception::class) @JvmStatic fun main(args: Array) { - Guice.createInjector(JavabotModule()) + val application = Guice.createInjector(JavabotModule()) .getInstance(JavabotApplication::class.java) - .run(*arrayOf("server", "javabot.yml")) + application.run() } } - override fun initialize(bootstrap: Bootstrap) { - bootstrap.addBundle(ViewBundle()) - bootstrap.addBundle(AssetsBundle("/assets", "/assets", null, "assets")) - bootstrap.addBundle( - AssetsBundle("/META-INF/resources/webjars", "/webjars", null, "webjars") - ) - } - - override fun run(configuration: JavabotConfiguration, environment: Environment) { - environment.applicationContext.isSessionsEnabled = true - environment.applicationContext.sessionHandler = SessionHandler() - + fun run() { + val configuration = JavabotConfiguration() + val bot = injector.getInstance(Javabot::class.java) bot.setUpThreads() - val oauth = injector.getInstance(PublicOAuthResource::class.java) - oauth.configuration = configuration - environment.jersey().register(oauth) - - environment.jersey().register(injector.getInstance(BotResource::class.java)) - environment.jersey().register(injector.getInstance(AdminResource::class.java)) - environment.jersey().register(RuntimeExceptionMapper(configuration)) - - environment - .servlets() - .addFilter("javadoc", injector.getInstance(JavadocFilter::class.java)) - .addMappingForUrlPatterns( - EnumSet.allOf(DispatcherType::class.java), - false, - "/javadoc/*", - ) - - environment.healthChecks().register("javabot", JavabotHealthCheck()) - - running = false + server = embeddedServer(Netty, port = 8080, host = "0.0.0.0") { + configureServer(configuration) + } + + running = true + server.start(wait = true) } - class JavadocFilter @Inject constructor(var apiDao: ApiDao, var config: JavabotConfig) : - Filter { - override fun destroy() {} + private fun Application.configureServer(configuration: JavabotConfiguration) { + // Install FreeMarker for templating + install(FreeMarker) { + templateLoader = ClassTemplateLoader(this::class.java.classLoader, "/") + } - override fun doFilter( - request: ServletRequest, - response: ServletResponse, - chain: FilterChain, - ) { - request as HttpServletRequest - var filePath = request.requestURI.split("/").drop(2).joinToString("/") - if (!filePath.startsWith("/")) { - filePath = "/" + filePath + // Install sessions + install(Sessions) { + cookie(JavabotConfiguration.SESSION_TOKEN_NAME) { + cookie.path = "/" + cookie.maxAgeInSeconds = 86400 * 30 } - val path = File("javadoc$filePath").toPath() + } - if (Files.exists(path)) { - response.outputStream.use { stream -> - Files.copy(path, stream) - stream.flush() - } - } else { - (response as HttpServletResponse).sendError(404) + // Install status pages for error handling + install(StatusPages) { + exception { call, cause -> + LOG.error("Request failed", cause) + call.respond(HttpStatusCode.InternalServerError, "Internal Server Error: ${cause.message}") + } + + status(HttpStatusCode.NotFound) { call, status -> + call.respondText("404: Page Not Found", status = status) } } - override fun init(filterConfig: FilterConfig?) {} + // Configure routing + routing { + // Static assets + staticResources("/assets", "assets") + staticResources("/webjars", "META-INF/resources/webjars") + + // Javadoc filter + get("/javadoc/{...}") { + val apiDao = injector.getInstance(ApiDao::class.java) + val config = injector.getInstance(JavabotConfig::class.java) + + val pathSegments = call.request.path().split("/").drop(2) + val filePath = "/" + pathSegments.joinToString("/") + val path = File("javadoc$filePath").toPath() + + if (Files.exists(path)) { + call.respondFile(path.toFile()) + } else { + call.respond(HttpStatusCode.NotFound) + } + } + + // Health check + get("/health") { + call.respondText("OK", contentType = ContentType.Text.Plain) + } + + // Register OAuth routes + val oauth = injector.getInstance(PublicOAuthResource::class.java) + oauth.configuration = configuration + oauth.configureRoutes(this) + + // Register Bot routes + val botResource = injector.getInstance(BotResource::class.java) + botResource.configureRoutes(this) + + // Register Admin routes + val adminResource = injector.getInstance(AdminResource::class.java) + adminResource.configureRoutes(this) + } } } + +data class UserSession(val sessionToken: String) diff --git a/src/main/kotlin/javabot/web/JavabotApplication.kt.bak b/src/main/kotlin/javabot/web/JavabotApplication.kt.bak new file mode 100644 index 00000000..b75182d8 --- /dev/null +++ b/src/main/kotlin/javabot/web/JavabotApplication.kt.bak @@ -0,0 +1 @@ +BACKUP - will delete later \ No newline at end of file diff --git a/src/main/kotlin/javabot/web/JavabotConfiguration.kt b/src/main/kotlin/javabot/web/JavabotConfiguration.kt index 2b1e3f87..1007f3e1 100644 --- a/src/main/kotlin/javabot/web/JavabotConfiguration.kt +++ b/src/main/kotlin/javabot/web/JavabotConfiguration.kt @@ -2,12 +2,11 @@ package javabot.web import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import io.dropwizard.Configuration import java.util.HashMap import java.util.Properties import javabot.web.model.OAuthConfig -class JavabotConfiguration : Configuration() { +class JavabotConfiguration { companion object { val SESSION_TOKEN_NAME: String = "JavabotSession" diff --git a/src/main/kotlin/javabot/web/resources/BotResource.kt b/src/main/kotlin/javabot/web/resources/BotResource.kt index 81f94ffb..d2e9b979 100644 --- a/src/main/kotlin/javabot/web/resources/BotResource.kt +++ b/src/main/kotlin/javabot/web/resources/BotResource.kt @@ -1,6 +1,10 @@ package javabot.web.resources -import io.dropwizard.views.View +import io.ktor.server.application.* +import io.ktor.server.freemarker.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.http.* import java.io.UnsupportedEncodingException import java.net.URLDecoder import java.time.LocalDate @@ -9,99 +13,75 @@ import java.time.format.DateTimeFormatter import javabot.model.Factoid import javabot.web.views.ViewFactory import javax.inject.Inject -import javax.servlet.http.HttpServletRequest -import javax.ws.rs.Consumes -import javax.ws.rs.GET -import javax.ws.rs.Path -import javax.ws.rs.PathParam -import javax.ws.rs.Produces -import javax.ws.rs.QueryParam -import javax.ws.rs.core.Context -import javax.ws.rs.core.MediaType import org.slf4j.LoggerFactory -@Path("/") -@Consumes(MediaType.APPLICATION_JSON) -@Produces(MediaType.APPLICATION_JSON) class BotResource @Inject constructor(var viewFactory: ViewFactory) { - @GET - @Produces("text/html;charset=ISO-8859-1") - fun index(@Context request: HttpServletRequest): View { - if (request.getParameter("test.exception") != null) { - throw RuntimeException("Testing 500 pages") - } - return viewFactory.createIndexView(request) - } + fun configureRoutes(routing: Routing) { + routing { + get("/") { + if (call.request.queryParameters["test.exception"] != null) { + throw RuntimeException("Testing 500 pages") + } + val view = viewFactory.createIndexView(KtorServletRequest(call)) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @GET - @Path("/index") - @Produces("text/html;charset=ISO-8859-1") - fun indexHtml(@Context request: HttpServletRequest): View { - return index(request) - } + get("/index") { + val view = viewFactory.createIndexView(KtorServletRequest(call)) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @GET - @Path("/factoids") - @Produces("text/html;charset=ISO-8859-1") - fun factoids( - @Context request: HttpServletRequest, - @QueryParam("page") page: Int?, - @QueryParam("name") name: String?, - @QueryParam("value") value: String?, - @QueryParam("userName") userName: String?, - ): View { - return viewFactory.createFactoidsView(request, page ?: 1, Factoid.of(name, value, userName)) - } + get("/factoids") { + val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 + val name = call.request.queryParameters["name"] + val value = call.request.queryParameters["value"] + val userName = call.request.queryParameters["userName"] + + val view = viewFactory.createFactoidsView( + KtorServletRequest(call), + page, + Factoid.of(name, value, userName) + ) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @GET - @Path("/karma") - @Produces("text/html;charset=ISO-8859-1") - fun karma( - @Context request: HttpServletRequest, - @QueryParam("page") page: Int?, - @Suppress("UNUSED_PARAMETER") @QueryParam("name") name: String?, - @Suppress("UNUSED_PARAMETER") @QueryParam("value") value: Int?, - @Suppress("UNUSED_PARAMETER") @QueryParam("userName") userName: String?, - ): View { - return viewFactory.createKarmaView(request, page ?: 1) - } + get("/karma") { + val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 + val view = viewFactory.createKarmaView(KtorServletRequest(call), page) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @GET - @Path("/changes") - @Produces("text/html;charset=ISO-8859-1") - fun changes( - @Context request: HttpServletRequest, - @QueryParam("page") page: Int?, - @QueryParam("message") message: String?, - ): View { - return viewFactory.createChangesView(request, page ?: 1, message) - } + get("/changes") { + val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 + val message = call.request.queryParameters["message"] + val view = viewFactory.createChangesView(KtorServletRequest(call), page, message) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } + + get("/logs/{channel}/{date}") { + val channel = call.parameters["channel"] + val dateString = call.parameters["date"] + + val date: LocalDateTime = + try { + if ("today" == dateString) LocalDate.now().atStartOfDay() + else LocalDate.parse(dateString, FORMAT).atStartOfDay() + } catch (e: Exception) { + LocalDate.now().atStartOfDay() + } + val channelName: String + try { + channelName = URLDecoder.decode(channel, "UTF-8") + } catch (e: UnsupportedEncodingException) { + LOG.error(e.message, e) + throw RuntimeException(e.message, e) + } - @GET - @Path("/logs/{channel}/{date}") - @Produces("text/html;charset=ISO-8859-1") - fun logs( - @Context request: HttpServletRequest, - @PathParam("channel") channel: String?, - @PathParam("date") dateString: String?, - ): View { - val date: LocalDateTime = - try { - if ("today" == dateString) LocalDate.now().atStartOfDay() - else LocalDate.parse(dateString, FORMAT).atStartOfDay() - } catch (e: Exception) { - LocalDate.now().atStartOfDay() + val view = viewFactory.createLogsView(KtorServletRequest(call), channelName, date) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) } - val channelName: String - try { - channelName = URLDecoder.decode(channel, "UTF-8") - } catch (e: UnsupportedEncodingException) { - LOG.error(e.message, e) - throw RuntimeException(e.message, e) } - - return viewFactory.createLogsView(request, channelName, date) } companion object { diff --git a/src/main/kotlin/javabot/web/resources/KtorServletRequest.kt b/src/main/kotlin/javabot/web/resources/KtorServletRequest.kt new file mode 100644 index 00000000..ba7a51b7 --- /dev/null +++ b/src/main/kotlin/javabot/web/resources/KtorServletRequest.kt @@ -0,0 +1,135 @@ +package javabot.web.resources + +import io.ktor.server.application.* +import io.ktor.server.request.* +import java.io.BufferedReader +import java.security.Principal +import java.util.* +import javax.servlet.* +import javax.servlet.http.* + +/** + * Adapter class to make Ktor ApplicationCall work like HttpServletRequest + * Only implements the methods used by the views + */ +class KtorServletRequest(private val call: ApplicationCall) : HttpServletRequest { + + override fun getCookies(): Array? { + val cookies = call.request.cookies.rawCookies.map { (name, value) -> + Cookie(name, value) + }.toTypedArray() + return if (cookies.isEmpty()) null else cookies + } + + override fun getParameter(name: String?): String? { + return call.request.queryParameters[name] + } + + override fun getParameterMap(): MutableMap> { + return call.request.queryParameters.entries() + .associate { it.key to it.value.toTypedArray() } + .toMutableMap() + } + + override fun getSession(): HttpSession { + return DummySession() + } + + override fun getSession(create: Boolean): HttpSession? { + return if (create) DummySession() else null + } + + // Minimal session implementation + private class DummySession : HttpSession { + private val attributes = mutableMapOf() + + override fun getAttribute(name: String?): Any? = attributes[name] + override fun setAttribute(name: String?, value: Any?) { attributes[name] = value } + override fun removeAttribute(name: String?) { attributes.remove(name) } + override fun getAttributeNames(): Enumeration = Collections.enumeration(attributes.keys) + override fun getCreationTime(): Long = System.currentTimeMillis() + override fun getId(): String = UUID.randomUUID().toString() + override fun getLastAccessedTime(): Long = System.currentTimeMillis() + override fun getMaxInactiveInterval(): Int = 3600 + override fun getServletContext(): ServletContext? = null + override fun invalidate() {} + override fun isNew(): Boolean = false + override fun setMaxInactiveInterval(interval: Int) {} + @Deprecated("Deprecated") + override fun getSessionContext(): HttpSessionContext? = null + @Deprecated("Deprecated") + override fun getValue(name: String?): Any? = getAttribute(name) + @Deprecated("Deprecated") + override fun getValueNames(): Array = attributes.keys.toTypedArray() + @Deprecated("Deprecated") + override fun putValue(name: String?, value: Any?) = setAttribute(name, value) + @Deprecated("Deprecated") + override fun removeValue(name: String?) = removeAttribute(name) + } + + // Required methods - not all implemented + override fun getAuthType(): String? = null + override fun getContextPath(): String = "" + override fun getHeader(name: String?): String? = call.request.headers[name] + override fun getHeaderNames(): Enumeration = Collections.enumeration(call.request.headers.names()) + override fun getHeaders(name: String?): Enumeration = Collections.enumeration(call.request.headers.getAll(name) ?: emptyList()) + override fun getMethod(): String = call.request.httpMethod.value + override fun getPathInfo(): String? = call.request.path() + override fun getPathTranslated(): String? = null + override fun getQueryString(): String? = call.request.queryString() + override fun getRemoteUser(): String? = null + override fun getRequestedSessionId(): String? = null + override fun getRequestURI(): String = call.request.uri + override fun getRequestURL(): StringBuffer = StringBuffer(call.request.origin.uri) + override fun getServletPath(): String = "" + override fun getUserPrincipal(): Principal? = null + override fun isRequestedSessionIdFromCookie(): Boolean = false + override fun isRequestedSessionIdFromURL(): Boolean = false + @Deprecated("Deprecated") + override fun isRequestedSessionIdFromUrl(): Boolean = false + override fun isRequestedSessionIdValid(): Boolean = false + override fun isUserInRole(role: String?): Boolean = false + override fun authenticate(response: HttpServletResponse?): Boolean = false + override fun changeSessionId(): String = "" + override fun getIntHeader(name: String?): Int = -1 + override fun getDateHeader(name: String?): Long = -1 + override fun login(username: String?, password: String?) {} + override fun logout() {} + override fun getParts(): MutableCollection = mutableListOf() + override fun getPart(name: String?): Part? = null + override fun upgrade(handlerClass: Class?): T = TODO() + override fun getAttribute(name: String?): Any? = null + override fun getAttributeNames(): Enumeration = Collections.emptyEnumeration() + override fun getCharacterEncoding(): String = "UTF-8" + override fun getContentLength(): Int = -1 + override fun getContentLengthLong(): Long = -1 + override fun getContentType(): String? = call.request.contentType().toString() + override fun getInputStream(): ServletInputStream = TODO() + override fun getLocalAddr(): String = "" + override fun getLocalName(): String = "" + override fun getLocalPort(): Int = 0 + override fun getLocale(): Locale = Locale.getDefault() + override fun getLocales(): Enumeration = Collections.enumeration(listOf(Locale.getDefault())) + override fun getParameterNames(): Enumeration = Collections.enumeration(call.request.queryParameters.names()) + override fun getParameterValues(name: String?): Array? = call.request.queryParameters.getAll(name)?.toTypedArray() + override fun getProtocol(): String = "HTTP/1.1" + override fun getReader(): BufferedReader = TODO() + override fun getRealPath(path: String?): String? = null + override fun getRemoteAddr(): String = call.request.local.remoteHost + override fun getRemoteHost(): String = call.request.local.remoteHost + override fun getRemotePort(): Int = call.request.local.remotePort + override fun getRequestDispatcher(path: String?): RequestDispatcher? = null + override fun getScheme(): String = call.request.origin.scheme + override fun getServerName(): String = call.request.local.serverHost + override fun getServerPort(): Int = call.request.local.serverPort + override fun getServletContext(): ServletContext? = null + override fun isAsyncStarted(): Boolean = false + override fun isAsyncSupported(): Boolean = false + override fun isSecure(): Boolean = call.request.origin.scheme == "https" + override fun removeAttribute(name: String?) {} + override fun setAttribute(name: String?, o: Any?) {} + override fun setCharacterEncoding(env: String?) {} + override fun startAsync(): AsyncContext = TODO() + override fun startAsync(servletRequest: ServletRequest?, servletResponse: ServletResponse?): AsyncContext = TODO() + override fun getDispatcherType(): DispatcherType = DispatcherType.REQUEST +} diff --git a/src/main/kotlin/javabot/web/views/MainView.kt b/src/main/kotlin/javabot/web/views/MainView.kt index 29211ca5..997464c9 100644 --- a/src/main/kotlin/javabot/web/views/MainView.kt +++ b/src/main/kotlin/javabot/web/views/MainView.kt @@ -1,7 +1,6 @@ package javabot.web.views import com.antwerkz.sofia.Sofia -import io.dropwizard.views.View import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.ArrayList @@ -22,7 +21,7 @@ abstract class MainView( var factoidDao: FactoidDao, var apiDao: ApiDao, val request: HttpServletRequest, -) : View("/main.ftl", com.google.common.base.Charsets.ISO_8859_1) { +) { companion object { val DATE_TIME_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd hh:mm") @@ -89,4 +88,21 @@ abstract class MainView( open fun format(date: LocalDateTime?): String { return if (date != null) DATE_TIME_FORMATTER.format(date) else "" } + + /** + * Convert this view to a model map for FreeMarker + */ + open fun toModel(): Map { + return mapOf( + "sofia" to sofia(), + "factoidCount" to getFactoidCount(), + "loggedIn" to loggedIn(), + "isAdmin" to isAdmin(), + "currentChannel" to getCurrentChannel(), + "channels" to getChannels(), + "apis" to getAPIs(), + "errors" to getErrors(), + "hasErrors" to hasErrors() + ) + } } From c7333f9045d0ed759a740851013b9487f8a35d39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:49:49 +0000 Subject: [PATCH 3/4] Convert resources and views to Ktor routing and templating Co-authored-by: evanchooly <195021+evanchooly@users.noreply.github.com> --- .../javabot/web/JavabotApplication.kt.bak | 1 - .../kotlin/javabot/web/JavabotHealthCheck.kt | 10 - .../javabot/web/RuntimeExceptionMapper.kt | 69 --- .../javabot/web/resources/AdminResource.kt | 520 +++++++++++------- .../web/resources/PublicErrorResource.kt | 7 +- .../web/resources/PublicOAuthResource.kt | 176 +++--- .../javabot/web/views/AdminIndexView.kt | 8 + .../kotlin/javabot/web/views/ChangesView.kt | 7 + .../javabot/web/views/ChannelEditView.kt | 6 + .../javabot/web/views/ConfigurationView.kt | 8 + .../kotlin/javabot/web/views/ErrorView.kt | 12 +- .../kotlin/javabot/web/views/FactoidsView.kt | 6 + .../kotlin/javabot/web/views/IndexView.kt | 4 + .../javabot/web/views/JavadocAdminView.kt | 6 + src/main/kotlin/javabot/web/views/LogsView.kt | 11 + .../kotlin/javabot/web/views/PagedView.kt | 15 + 16 files changed, 455 insertions(+), 411 deletions(-) delete mode 100644 src/main/kotlin/javabot/web/JavabotApplication.kt.bak delete mode 100644 src/main/kotlin/javabot/web/JavabotHealthCheck.kt delete mode 100644 src/main/kotlin/javabot/web/RuntimeExceptionMapper.kt diff --git a/src/main/kotlin/javabot/web/JavabotApplication.kt.bak b/src/main/kotlin/javabot/web/JavabotApplication.kt.bak deleted file mode 100644 index b75182d8..00000000 --- a/src/main/kotlin/javabot/web/JavabotApplication.kt.bak +++ /dev/null @@ -1 +0,0 @@ -BACKUP - will delete later \ No newline at end of file diff --git a/src/main/kotlin/javabot/web/JavabotHealthCheck.kt b/src/main/kotlin/javabot/web/JavabotHealthCheck.kt deleted file mode 100644 index 37309ca8..00000000 --- a/src/main/kotlin/javabot/web/JavabotHealthCheck.kt +++ /dev/null @@ -1,10 +0,0 @@ -package javabot.web - -import com.codahale.metrics.health.HealthCheck - -class JavabotHealthCheck : HealthCheck() { - @Throws(Exception::class) - override fun check(): Result { - return Result.healthy() - } -} diff --git a/src/main/kotlin/javabot/web/RuntimeExceptionMapper.kt b/src/main/kotlin/javabot/web/RuntimeExceptionMapper.kt deleted file mode 100644 index e4e8eb6f..00000000 --- a/src/main/kotlin/javabot/web/RuntimeExceptionMapper.kt +++ /dev/null @@ -1,69 +0,0 @@ -package javabot.web - -import java.net.URI -import java.net.URISyntaxException -import javabot.web.resources.PublicErrorResource -import javax.ws.rs.WebApplicationException -import javax.ws.rs.core.Context -import javax.ws.rs.core.Response -import javax.ws.rs.core.Response.Status.FORBIDDEN -import javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR -import javax.ws.rs.core.Response.Status.NOT_FOUND -import javax.ws.rs.core.Response.Status.TEMPORARY_REDIRECT -import javax.ws.rs.core.Response.Status.UNAUTHORIZED -import javax.ws.rs.ext.ExceptionMapper -import javax.ws.rs.ext.Provider -import org.apache.http.protocol.HttpContext -import org.slf4j.LoggerFactory - -@Provider -class RuntimeExceptionMapper(val configuration: JavabotConfiguration) : - ExceptionMapper { - - companion object { - private val LOG = LoggerFactory.getLogger(RuntimeExceptionMapper::class.java) - } - - @Context private var httpContext: HttpContext? = null - - override fun toResponse(runtime: RuntimeException): Response { - - if (runtime is WebApplicationException) { - return handleWebApplicationException(runtime) - } else { - LOG.error(runtime.message, runtime) - return Response.status(INTERNAL_SERVER_ERROR) - .entity(PublicErrorResource.view500()) - .build() - } - } - - private fun handleWebApplicationException(exception: RuntimeException): Response { - val webAppException = exception as WebApplicationException - - // No logging - val status = webAppException.response.status - if (status == UNAUTHORIZED.statusCode) { - try { - return Response.status(TEMPORARY_REDIRECT).location(URI("/auth/login")).build() - } catch (e: URISyntaxException) { - return Response.status(INTERNAL_SERVER_ERROR) - .entity(PublicErrorResource.view500()) - .build() - } - } else if (status == FORBIDDEN.statusCode) { - return Response.status(INTERNAL_SERVER_ERROR) - .entity(PublicErrorResource.view403()) - .build() - } else if (status == NOT_FOUND.statusCode) { - return Response.status(INTERNAL_SERVER_ERROR) - .entity(PublicErrorResource.view404()) - .build() - } else { - LOG.error(exception.message, exception) - return Response.status(INTERNAL_SERVER_ERROR) - .entity(PublicErrorResource.view500()) - .build() - } - } -} diff --git a/src/main/kotlin/javabot/web/resources/AdminResource.kt b/src/main/kotlin/javabot/web/resources/AdminResource.kt index a1c6da25..a4ac34cd 100644 --- a/src/main/kotlin/javabot/web/resources/AdminResource.kt +++ b/src/main/kotlin/javabot/web/resources/AdminResource.kt @@ -1,6 +1,11 @@ package javabot.web.resources -import io.dropwizard.views.View +import io.ktor.server.application.* +import io.ktor.server.freemarker.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.server.request.* +import io.ktor.http.* import javabot.Javabot import javabot.JavabotConfig import javabot.dao.AdminDao @@ -11,24 +16,12 @@ import javabot.model.Admin import javabot.model.ApiEvent import javabot.model.Channel import javabot.model.javadoc.JavadocApi -import javabot.web.auth.Restricted import javabot.web.model.Authority import javabot.web.model.User import javabot.web.views.ViewFactory import javax.inject.Inject -import javax.servlet.http.HttpServletRequest -import javax.ws.rs.Consumes -import javax.ws.rs.FormParam -import javax.ws.rs.GET -import javax.ws.rs.POST -import javax.ws.rs.Path -import javax.ws.rs.PathParam -import javax.ws.rs.WebApplicationException -import javax.ws.rs.core.Context -import javax.ws.rs.core.MediaType import org.bson.types.ObjectId -@Path("/admin") class AdminResource @Inject constructor( @@ -41,223 +34,322 @@ constructor( var config: JavabotConfig, ) { - @GET - fun index( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - ): View { - val current = adminDao.getAdminByEmailAddress(user.email) - return if (current == null) PublicErrorResource.view403() - else viewFactory.createAdminIndexView(request, current, Admin()) - } + fun configureRoutes(routing: Routing) { + routing { + route("/admin") { + get { + val user = getAuthenticatedUser(call) ?: run { + val view = PublicErrorResource.view403() + call.respond(FreeMarkerContent(view.template, view.toModel())) + return@get + } + + val current = adminDao.getAdminByEmailAddress(user.email) + if (current == null) { + val view = PublicErrorResource.view403() + call.respond(FreeMarkerContent(view.template, view.toModel())) + } else { + val view = viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } + } - @GET - @Path("/config") - fun config( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - ): View { - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - return viewFactory.createConfigurationView(request) - } + get("/config") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val view = viewFactory.createConfigurationView(KtorServletRequest(call)) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @GET - @Path("/javadoc") - fun javadoc( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - ): View { - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - return viewFactory.createJavadocAdminView(request) - } + get("/javadoc") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val view = viewFactory.createJavadocAdminView(KtorServletRequest(call)) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @GET - @Path("/newChannel") - fun newChannel( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - ): View { - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - return viewFactory.createChannelEditView(request, Channel()) - } + get("/newChannel") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val view = viewFactory.createChannelEditView(KtorServletRequest(call), Channel()) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @GET - @Path("/editChannel/{channel}") - fun editChannel( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @PathParam("channel") channel: String, - ): View { + get("/editChannel/{channel}") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val channelName = call.parameters["channel"] ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val channel = channelDao.get(channelName) ?: run { + call.respond(HttpStatusCode.NotFound) + return@get + } + val view = viewFactory.createChannelEditView(KtorServletRequest(call), channel) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - // TODO redirect to / if channel is null - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - return viewFactory.createChannelEditView(request, channelDao.get(channel)!!) - } + post("/saveChannel") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + + val params = call.receiveParameters() + val id = params["id"] + val name = params["name"] ?: "" + val key = params["key"] ?: "" + val logged = params["logged"]?.toBoolean() ?: false + + val channel = + if (id == null) Channel(name, key, logged) else Channel(ObjectId(id), name, key, logged) + channelDao.save(channel) + + val current = adminDao.getAdminByEmailAddress(user.email)!! + val view = viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @POST - @Path("/saveChannel") - fun saveChannel( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @FormParam("id") id: String?, - @FormParam("name") name: String, - @FormParam("key") key: String, - @FormParam("logged") logged: Boolean, - ): View { - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - val channel = - if (id == null) Channel(name, key, logged) else Channel(ObjectId(id), name, key, logged) - channelDao.save(channel) - return index(request, user) - } + post("/saveConfig") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + + val params = call.receiveParameters() + val config = configDao.get() + config.server = params["server"] ?: config.server + config.url = params["url"] ?: config.url + config.port = params["port"]?.toIntOrNull() ?: config.port + config.historyLength = params["historyLength"]?.toIntOrNull() ?: config.historyLength + config.trigger = params["trigger"] ?: config.trigger + config.nick = params["nick"] ?: config.nick + config.password = params["password"] ?: config.password + config.throttleThreshold = params["throttleThreshold"]?.toIntOrNull() ?: config.throttleThreshold + config.minimumNickServAge = params["minimumNickServAge"]?.toIntOrNull() ?: config.minimumNickServAge + configDao.save(config) + + val view = viewFactory.createConfigurationView(KtorServletRequest(call)) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @POST - @Path("/saveConfig") - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - fun saveConfig( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @FormParam("server") server: String, - @FormParam("url") url: String, - @FormParam("port") port: Int, - @FormParam("historyLength") historyLength: Int, - @FormParam("trigger") trigger: String, - @FormParam("nick") nick: String, - @FormParam("password") password: String, - @FormParam("throttleThreshold") throttleThreshold: Int, - @FormParam("minimumNickServAge") minimumNickServAge: Int, - ): View { - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - val config = configDao.get() - config.server = server - config.url = url - config.port = port - config.historyLength = historyLength - config.trigger = trigger - config.nick = nick - config.password = password - config.throttleThreshold = throttleThreshold - config.minimumNickServAge = minimumNickServAge - configDao.save(config) - return viewFactory.createConfigurationView(request) - } + get("/enableOperation/{name}") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val name = call.parameters["name"] ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + javabot.enableOperation(name) + val view = viewFactory.createConfigurationView(KtorServletRequest(call)) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @GET - @Path("/enableOperation/{name}") - fun enableOperation( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @PathParam("name") name: String, - ): View { - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - javabot.enableOperation(name) - return viewFactory.createConfigurationView(request) - } + get("/disableOperation/{name}") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val name = call.parameters["name"] ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + javabot.disableOperation(name) + val view = viewFactory.createConfigurationView(KtorServletRequest(call)) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @GET - @Path("/disableOperation/{name}") - fun disableOperation( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @PathParam("name") name: String, - ): View { - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - javabot.disableOperation(name) - return viewFactory.createConfigurationView(request) - } + get("/edit/{id}") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val id = call.parameters["id"] ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + val current = adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } - @GET - @Path("/edit/{id}") - fun editAdmin( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @PathParam("id") id: String, - ): View { - val current = - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) + val view = viewFactory.createAdminIndexView(KtorServletRequest(call), current, adminDao.find(ObjectId(id))) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - return viewFactory.createAdminIndexView(request, current, adminDao.find(ObjectId(id))) - } + get("/delete/{id}") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val id = call.parameters["id"] ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val admin = adminDao.find(ObjectId(id)) + if (admin != null && (!admin.botOwner)) { + adminDao.delete(admin) + } + + val current = adminDao.getAdminByEmailAddress(user.email)!! + val view = viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @GET - @Path("/delete/{id}") - fun deleteAdmin( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @PathParam("id") id: String, - ): View { - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - val admin = adminDao.find(ObjectId(id)) - if (admin != null && (!admin.botOwner)) { - adminDao.delete(admin) - } - return index(request, user) - } + post("/add") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + + val params = call.receiveParameters() + val ircName = params["ircName"] ?: "" + val hostName = params["hostName"] ?: "" + val emailAddress = params["emailAddress"] ?: "" + + var admin: Admin? = adminDao.getAdminByEmailAddress(emailAddress) + if (admin == null) { + admin = Admin(ircName, emailAddress, hostName, true) + } else { + admin.ircName = ircName + admin.hostName = hostName + admin.emailAddress = emailAddress + } + adminDao.save(admin) + + val current = adminDao.getAdminByEmailAddress(user.email)!! + val view = viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - @POST - @Path("/add") - fun addAdmin( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @FormParam("ircName") ircName: String, - @FormParam("hostName") hostName: String, - @FormParam("emailAddress") emailAddress: String, - ): View { - var admin: Admin? = adminDao.getAdminByEmailAddress(emailAddress) - if (admin == null) { - admin = Admin(ircName, emailAddress, hostName, true) - } else { - admin.ircName = ircName - admin.hostName = hostName - admin.emailAddress = emailAddress - } - adminDao.save(admin) - return index(request, user) - } + post("/addApi") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + + val params = call.receiveParameters() + val name = params["name"] + val groupId = params["groupId"] + val artifactId = params["artifactId"] + val version = params["version"] + + version?.let { + val apiName = name ?: artifactId ?: run { + call.respond(HttpStatusCode.BadRequest) + return@post + } + val api = JavadocApi(config, apiName, groupId ?: "", artifactId ?: "", version) + apiDao.save(api) + apiDao.save(ApiEvent.add(user.email, api)) + } - @POST - @Path("/addApi") - fun addApi( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @FormParam("name") name: String?, - @FormParam("groupId") groupId: String?, - @FormParam("artifactId") artifactId: String?, - @FormParam("version") version: String?, - ): View { + val view = viewFactory.createJavadocAdminView(KtorServletRequest(call)) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - version?.let { - val apiName = name ?: artifactId ?: throw WebApplicationException(400) - val api = JavadocApi(config, apiName, groupId ?: "", artifactId ?: "", version) - apiDao.save(api) - apiDao.save(ApiEvent.add(user.email, api)) - } + get("/deleteApi/{id}") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val id = call.parameters["id"] ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + apiDao.delete(ObjectId(id)) + + val view = viewFactory.createJavadocAdminView(KtorServletRequest(call)) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } - return javadoc(request, user) - } - - @GET - @Path("/deleteApi/{id}") - fun deleteApi( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @PathParam("id") id: String, - ): View { - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - apiDao.delete(ObjectId(id)) - return javadoc(request, user) + get("/reloadApi/{id}") { + val user = getAuthenticatedUser(call) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val id = call.parameters["id"] ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + apiDao.find(ObjectId(id))?.let { apiDao.save(ApiEvent.reload(user.email, it)) } + + val view = viewFactory.createJavadocAdminView(KtorServletRequest(call)) + call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + } + } + } } - - @GET - @Path("/reloadApi/{id}") - fun reloadApi( - @Context request: HttpServletRequest, - @Restricted(Authority.ROLE_ADMIN) user: User, - @PathParam("id") id: String, - ): View { - adminDao.getAdminByEmailAddress(user.email) ?: throw WebApplicationException(403) - apiDao.find(ObjectId(id))?.let { apiDao.save(ApiEvent.reload(user.email, it)) } - return javadoc(request, user) + + private fun getAuthenticatedUser(call: ApplicationCall): User? { + // TODO: Implement proper authentication + return null } } diff --git a/src/main/kotlin/javabot/web/resources/PublicErrorResource.kt b/src/main/kotlin/javabot/web/resources/PublicErrorResource.kt index 85c0678e..13523f2b 100644 --- a/src/main/kotlin/javabot/web/resources/PublicErrorResource.kt +++ b/src/main/kotlin/javabot/web/resources/PublicErrorResource.kt @@ -1,6 +1,5 @@ package javabot.web.resources -import io.dropwizard.views.View import java.util.Random import javabot.web.views.ErrorView @@ -10,15 +9,15 @@ class PublicErrorResource { private val IMAGE_404 = arrayOf("404_1.gif", "404_2.gif", "404_3.gif", "404_4.gif") private val IMAGE_500 = arrayOf("500.gif") - fun view403(): View { + fun view403(): ErrorView { return ErrorView("/error/403.ftl", getRandomImage(IMAGE_403)) } - fun view404(): View { + fun view404(): ErrorView { return ErrorView("/error/404.ftl", getRandomImage(IMAGE_404)) } - fun view500(): View { + fun view500(): ErrorView { return ErrorView("/error/500.ftl", getRandomImage(IMAGE_500)) } diff --git a/src/main/kotlin/javabot/web/resources/PublicOAuthResource.kt b/src/main/kotlin/javabot/web/resources/PublicOAuthResource.kt index 717dfff6..70ae2331 100644 --- a/src/main/kotlin/javabot/web/resources/PublicOAuthResource.kt +++ b/src/main/kotlin/javabot/web/resources/PublicOAuthResource.kt @@ -1,112 +1,100 @@ package javabot.web.resources import com.antwerkz.sofia.Sofia -import com.codahale.metrics.annotation.Timed -import com.google.common.base.Optional +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.ktor.server.sessions.* +import io.ktor.http.* import java.net.URI -import java.net.URISyntaxException import java.util.UUID import javabot.dao.AdminDao import javabot.model.Admin import javabot.web.JavabotConfiguration +import javabot.web.UserSession import javabot.web.model.Authority.ROLE_ADMIN import javabot.web.model.Authority.ROLE_PUBLIC import javabot.web.model.InMemoryUserCache.INSTANCE import javabot.web.model.User import javax.inject.Inject -import javax.servlet.http.HttpServletRequest -import javax.ws.rs.GET -import javax.ws.rs.Path -import javax.ws.rs.Produces -import javax.ws.rs.WebApplicationException -import javax.ws.rs.core.Context -import javax.ws.rs.core.MediaType -import javax.ws.rs.core.NewCookie -import javax.ws.rs.core.Response -import javax.ws.rs.core.Response.Status.BAD_REQUEST -import javax.ws.rs.core.Response.Status.UNAUTHORIZED import org.brickred.socialauth.SocialAuthConfig import org.brickred.socialauth.SocialAuthManager import org.brickred.socialauth.util.SocialAuthUtil import org.slf4j.LoggerFactory -@Path("/auth") -@Produces(MediaType.TEXT_HTML) class PublicOAuthResource @Inject constructor(var adminDao: AdminDao) { var configuration: JavabotConfiguration? = null - @GET - @Path("/login") - @Throws(URISyntaxException::class) - fun requestOAuth(@Context request: HttpServletRequest): Response { - val oauthCfg = configuration!!.OAuthCfg - if (oauthCfg != null) { - try { - val manager = getSocialAuthManager() - - request.session.setAttribute(AUTH_MANAGER, manager) - - val uri = - URI( - manager?.getAuthenticationUrl("googleplus", configuration!!.OAuthSuccessUrl) - ) - return Response.temporaryRedirect(uri).build() - } catch (e: Exception) { - log.error(e.message, e) + fun configureRoutes(routing: Routing) { + routing { + get("/auth/login") { + val oauthCfg = configuration!!.OAuthCfg + if (oauthCfg != null) { + try { + val manager = getSocialAuthManager() + + // Store manager in session + call.sessions.set(UserSession(AUTH_MANAGER)) + + val uri = URI( + manager?.getAuthenticationUrl("googleplus", configuration!!.OAuthSuccessUrl) + ) + call.respondRedirect(uri.toString()) + return@get + } catch (e: Exception) { + log.error(e.message, e) + } + } + call.respond(HttpStatusCode.BadRequest) } - } - throw WebApplicationException(BAD_REQUEST) - } - - /** - * Handles the OAuth server response to the earlier AuthRequest - * - * @return The OAuth identifier for this user if verification was successful - */ - @GET - @Timed - @Path("/verify") - fun verifyOAuthServerResponse(@Context request: HttpServletRequest): Response { - val manager = request.session.getAttribute(AUTH_MANAGER) as SocialAuthManager - try { - val params = SocialAuthUtil.getRequestParametersMap(request) - val provider = manager.connect(params) - - val p = provider.userProfile - - Sofia.loggingInUser(p) - - var tempUser = User(UUID.randomUUID(), p.email, p.validatedId, provider.accessGrant) - tempUser.authorities.add(ROLE_PUBLIC) + get("/auth/verify") { + // TODO: Retrieve manager from session + val manager = getSocialAuthManager() ?: run { + call.respond(HttpStatusCode.Unauthorized) + return@get + } - val user = INSTANCE.getByOpenIDIdentifier(tempUser.openIDIdentifier) - if (user == null) { - val admin = adminDao.getAdminByEmailAddress(tempUser.email) - if (admin != null) { - tempUser.authorities.add(ROLE_ADMIN) - } else { - if (adminDao.count() == 0L) { - adminDao.save(Admin(tempUser.email)) - tempUser.authorities.add(ROLE_ADMIN) + try { + val params = mutableMapOf() + call.request.queryParameters.entries().forEach { entry -> + params[entry.key] = entry.value.firstOrNull() ?: "" } + val provider = manager.connect(params) + + val p = provider.userProfile + + Sofia.loggingInUser(p) + + var tempUser = User(UUID.randomUUID(), p.email, p.validatedId, provider.accessGrant) + tempUser.authorities.add(ROLE_PUBLIC) + + val user = INSTANCE.getByOpenIDIdentifier(tempUser.openIDIdentifier) + if (user == null) { + val admin = adminDao.getAdminByEmailAddress(tempUser.email) + if (admin != null) { + tempUser.authorities.add(ROLE_ADMIN) + } else { + if (adminDao.count() == 0L) { + adminDao.save(Admin(tempUser.email)) + tempUser.authorities.add(ROLE_ADMIN) + } + } + INSTANCE.put(tempUser) + } else { + tempUser = user + } + + call.sessions.set(UserSession(tempUser.sessionToken.toString())) + call.respondRedirect("/") + } catch (e: Exception) { + e.printStackTrace() + log.error(e.message, e) + call.respond(HttpStatusCode.Unauthorized) } - INSTANCE.put(tempUser) - } else { - tempUser = user } - - return Response.temporaryRedirect(URI("/")) - .cookie(replaceSessionTokenCookie(Optional.of(tempUser))) - .build() - } catch (e: Exception) { - e.printStackTrace() - log.error(e.message, e) } - - // Must have failed to be here - throw WebApplicationException(UNAUTHORIZED) } /** @return Get an initialized SocialAuthManager */ @@ -124,34 +112,6 @@ class PublicOAuthResource @Inject constructor(var adminDao: AdminDao) { return null } - protected fun replaceSessionTokenCookie(user: Optional): NewCookie { - if (user.isPresent) { - val value = user.get().sessionToken.toString() - log.debug("Replacing session token with {}", value) - return NewCookie( - JavabotConfiguration.SESSION_TOKEN_NAME, - value, - "/", - null, - null, - 86400 * 30, - false, - ) - } else { - // Remove the session token cookie - log.debug("Removing session token") - return NewCookie( - JavabotConfiguration.SESSION_TOKEN_NAME, - null, - null, - null, - null, - 0, - false, - ) - } - } - companion object { private val log = LoggerFactory.getLogger(PublicOAuthResource::class.java) diff --git a/src/main/kotlin/javabot/web/views/AdminIndexView.kt b/src/main/kotlin/javabot/web/views/AdminIndexView.kt index 51f64476..8782cf4a 100644 --- a/src/main/kotlin/javabot/web/views/AdminIndexView.kt +++ b/src/main/kotlin/javabot/web/views/AdminIndexView.kt @@ -28,4 +28,12 @@ constructor( override fun getChildView(): String { return "admin/index.ftl" } + + override fun toModel(): Map { + return super.toModel() + mapOf( + "current" to current, + "editing" to editing, + "admins" to getAdmins() + ) + } } diff --git a/src/main/kotlin/javabot/web/views/ChangesView.kt b/src/main/kotlin/javabot/web/views/ChangesView.kt index b8e0d7a5..b57ab371 100644 --- a/src/main/kotlin/javabot/web/views/ChangesView.kt +++ b/src/main/kotlin/javabot/web/views/ChangesView.kt @@ -46,4 +46,11 @@ constructor( date, ) } + + override fun toModel(): Map { + return super.toModel() + mapOf( + "message" to message, + "date" to date + ) + } } diff --git a/src/main/kotlin/javabot/web/views/ChannelEditView.kt b/src/main/kotlin/javabot/web/views/ChannelEditView.kt index 3139d147..ec02d274 100644 --- a/src/main/kotlin/javabot/web/views/ChannelEditView.kt +++ b/src/main/kotlin/javabot/web/views/ChannelEditView.kt @@ -23,4 +23,10 @@ constructor( override fun getChildView(): String { return "admin/editChannel.ftl" } + + override fun toModel(): Map { + return super.toModel() + mapOf( + "channel" to channel + ) + } } diff --git a/src/main/kotlin/javabot/web/views/ConfigurationView.kt b/src/main/kotlin/javabot/web/views/ConfigurationView.kt index da91e42e..cc543892 100644 --- a/src/main/kotlin/javabot/web/views/ConfigurationView.kt +++ b/src/main/kotlin/javabot/web/views/ConfigurationView.kt @@ -46,4 +46,12 @@ constructor( override fun getChildView(): String { return "admin/configuration.ftl" } + + override fun toModel(): Map { + return super.toModel() + mapOf( + "configuration" to configuration, + "operations" to operations(), + "currentOps" to getCurrentOps() + ) + } } diff --git a/src/main/kotlin/javabot/web/views/ErrorView.kt b/src/main/kotlin/javabot/web/views/ErrorView.kt index c26d4173..597ead35 100644 --- a/src/main/kotlin/javabot/web/views/ErrorView.kt +++ b/src/main/kotlin/javabot/web/views/ErrorView.kt @@ -1,7 +1,9 @@ package javabot.web.views -import com.google.common.base.Charsets -import io.dropwizard.views.View - -class ErrorView(template: String, val image: String) : - View(template, com.google.common.base.Charsets.ISO_8859_1) +class ErrorView(val template: String, val image: String) { + fun toModel(): Map { + return mapOf( + "image" to image + ) + } +} diff --git a/src/main/kotlin/javabot/web/views/FactoidsView.kt b/src/main/kotlin/javabot/web/views/FactoidsView.kt index dba3e31a..17f68f70 100644 --- a/src/main/kotlin/javabot/web/views/FactoidsView.kt +++ b/src/main/kotlin/javabot/web/views/FactoidsView.kt @@ -70,6 +70,12 @@ constructor( override fun getPagedView(): String { return "/factoids.ftl" } + + override fun toModel(): Map { + return super.toModel() + mapOf( + "filter" to getFilter() + ) + } companion object { private val LOG = LoggerFactory.getLogger(FactoidsView::class.java) diff --git a/src/main/kotlin/javabot/web/views/IndexView.kt b/src/main/kotlin/javabot/web/views/IndexView.kt index 399aa069..fbc4c948 100644 --- a/src/main/kotlin/javabot/web/views/IndexView.kt +++ b/src/main/kotlin/javabot/web/views/IndexView.kt @@ -21,4 +21,8 @@ constructor( override fun getChildView(): String { return "/index.ftl" } + + override fun toModel(): Map { + return super.toModel() + } } diff --git a/src/main/kotlin/javabot/web/views/JavadocAdminView.kt b/src/main/kotlin/javabot/web/views/JavadocAdminView.kt index da548013..6a915ddd 100644 --- a/src/main/kotlin/javabot/web/views/JavadocAdminView.kt +++ b/src/main/kotlin/javabot/web/views/JavadocAdminView.kt @@ -26,4 +26,10 @@ constructor( fun apis(): List { return apiDao.findAll() } + + override fun toModel(): Map { + return super.toModel() + mapOf( + "apis" to apis() + ) + } } diff --git a/src/main/kotlin/javabot/web/views/LogsView.kt b/src/main/kotlin/javabot/web/views/LogsView.kt index 826be0a4..a9bcd51c 100644 --- a/src/main/kotlin/javabot/web/views/LogsView.kt +++ b/src/main/kotlin/javabot/web/views/LogsView.kt @@ -52,4 +52,15 @@ constructor( override fun getChildView(): String { return "logs.ftl" } + + override fun toModel(): Map { + return super.toModel() + mapOf( + "channel" to channel, + "date" to date, + "today" to today, + "yesterday" to yesterday, + "tomorrow" to tomorrow, + "logs" to logs() + ) + } } diff --git a/src/main/kotlin/javabot/web/views/PagedView.kt b/src/main/kotlin/javabot/web/views/PagedView.kt index b78713d2..c4bd2c42 100644 --- a/src/main/kotlin/javabot/web/views/PagedView.kt +++ b/src/main/kotlin/javabot/web/views/PagedView.kt @@ -80,6 +80,21 @@ abstract class PagedView( } abstract fun getPageItems(): List + + override fun toModel(): Map { + return super.toModel() + mapOf( + "page" to getPage(), + "pageCount" to getPageCount(), + "itemsPerPage" to getItemsPerPage(), + "itemCount" to itemCount, + "nextPage" to getNextPage(), + "previousPage" to getPreviousPage(), + "startRange" to getStartRange(), + "endRange" to getEndRange(), + "pageItems" to getPageItems(), + "pagedView" to getPagedView() + ) + } companion object { val ITEMS_PER_PAGE: Int = 50 From 30349697010d9eb74c443ed32adfda31cb94bb1a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 15:54:56 +0000 Subject: [PATCH 4/4] Fix compilation errors in Ktor migration - WIP Co-authored-by: evanchooly <195021+evanchooly@users.noreply.github.com> --- pom.xml | 13 + .../kotlin/javabot/web/JavabotApplication.kt | 53 +- .../javabot/web/resources/AdminResource.kt | 485 ++++++++++-------- .../javabot/web/resources/BotResource.kt | 31 +- .../web/resources/KtorServletRequest.kt | 175 +++++-- .../web/resources/PublicOAuthResource.kt | 30 +- .../javabot/web/views/AdminIndexView.kt | 9 +- .../kotlin/javabot/web/views/ChangesView.kt | 7 +- .../javabot/web/views/ChannelEditView.kt | 6 +- .../javabot/web/views/ConfigurationView.kt | 13 +- .../kotlin/javabot/web/views/ErrorView.kt | 4 +- .../kotlin/javabot/web/views/FactoidsView.kt | 6 +- .../kotlin/javabot/web/views/IndexView.kt | 2 +- .../javabot/web/views/JavadocAdminView.kt | 6 +- src/main/kotlin/javabot/web/views/LogsView.kt | 19 +- src/main/kotlin/javabot/web/views/MainView.kt | 9 +- .../kotlin/javabot/web/views/PagedView.kt | 27 +- src/main/resources/main.ftl | 20 +- 18 files changed, 551 insertions(+), 364 deletions(-) diff --git a/pom.xml b/pom.xml index 165d5652..7469f93e 100644 --- a/pom.xml +++ b/pom.xml @@ -387,6 +387,19 @@ ktor-serialization-jackson-jvm ${ktor.version} + + + io.ktor + ktor-server-partial-content-jvm + ${ktor.version} + + + + + javax.servlet + javax.servlet-api + 4.0.1 + diff --git a/src/main/kotlin/javabot/web/JavabotApplication.kt b/src/main/kotlin/javabot/web/JavabotApplication.kt index 84140bc9..25b585f5 100644 --- a/src/main/kotlin/javabot/web/JavabotApplication.kt +++ b/src/main/kotlin/javabot/web/JavabotApplication.kt @@ -4,16 +4,17 @@ import com.google.inject.Guice import com.google.inject.Inject import com.google.inject.Injector import com.google.inject.Singleton +import freemarker.cache.ClassTemplateLoader import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.engine.* +import io.ktor.server.freemarker.* +import io.ktor.server.http.content.* import io.ktor.server.netty.* import io.ktor.server.plugins.statuspages.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.sessions.* -import io.ktor.server.freemarker.* -import freemarker.cache.ClassTemplateLoader import java.io.File import java.nio.file.Files import javabot.Javabot @@ -28,30 +29,29 @@ import org.slf4j.LoggerFactory @Singleton class JavabotApplication @Inject constructor(var injector: Injector) { var running = false - lateinit var server: NettyApplicationEngine - + lateinit var server: EmbeddedServer<*, *> + companion object { private val LOG = LoggerFactory.getLogger(JavabotApplication::class.java) @Throws(Exception::class) @JvmStatic fun main(args: Array) { - val application = Guice.createInjector(JavabotModule()) - .getInstance(JavabotApplication::class.java) + val application = + Guice.createInjector(JavabotModule()).getInstance(JavabotApplication::class.java) application.run() } } fun run() { val configuration = JavabotConfiguration() - + val bot = injector.getInstance(Javabot::class.java) bot.setUpThreads() - server = embeddedServer(Netty, port = 8080, host = "0.0.0.0") { - configureServer(configuration) - } - + server = + embeddedServer(Netty, port = 8080, host = "0.0.0.0") { configureServer(configuration) } + running = true server.start(wait = true) } @@ -74,9 +74,12 @@ class JavabotApplication @Inject constructor(var injector: Injector) { install(StatusPages) { exception { call, cause -> LOG.error("Request failed", cause) - call.respond(HttpStatusCode.InternalServerError, "Internal Server Error: ${cause.message}") + call.respond( + HttpStatusCode.InternalServerError, + "Internal Server Error: ${cause.message}", + ) } - + status(HttpStatusCode.NotFound) { call, status -> call.respondText("404: Page Not Found", status = status) } @@ -84,17 +87,21 @@ class JavabotApplication @Inject constructor(var injector: Injector) { // Configure routing routing { - // Static assets - staticResources("/assets", "assets") - staticResources("/webjars", "META-INF/resources/webjars") - + // Static assets using static() which serves files from resources + static("/assets") { + resources("assets") + } + static("/webjars") { + resources("META-INF/resources/webjars") + } + // Javadoc filter get("/javadoc/{...}") { val apiDao = injector.getInstance(ApiDao::class.java) val config = injector.getInstance(JavabotConfig::class.java) - - val pathSegments = call.request.path().split("/").drop(2) - val filePath = "/" + pathSegments.joinToString("/") + + val pathAfterJavadoc = call.request.uri.substringAfter("/javadoc/") + val filePath = "/$pathAfterJavadoc" val path = File("javadoc$filePath").toPath() if (Files.exists(path)) { @@ -103,11 +110,9 @@ class JavabotApplication @Inject constructor(var injector: Injector) { call.respond(HttpStatusCode.NotFound) } } - + // Health check - get("/health") { - call.respondText("OK", contentType = ContentType.Text.Plain) - } + get("/health") { call.respondText("OK", contentType = ContentType.Text.Plain) } // Register OAuth routes val oauth = injector.getInstance(PublicOAuthResource::class.java) diff --git a/src/main/kotlin/javabot/web/resources/AdminResource.kt b/src/main/kotlin/javabot/web/resources/AdminResource.kt index a4ac34cd..e5e0d543 100644 --- a/src/main/kotlin/javabot/web/resources/AdminResource.kt +++ b/src/main/kotlin/javabot/web/resources/AdminResource.kt @@ -1,11 +1,11 @@ package javabot.web.resources +import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.freemarker.* +import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -import io.ktor.server.request.* -import io.ktor.http.* import javabot.Javabot import javabot.JavabotConfig import javabot.dao.AdminDao @@ -16,7 +16,6 @@ import javabot.model.Admin import javabot.model.ApiEvent import javabot.model.Channel import javabot.model.javadoc.JavadocApi -import javabot.web.model.Authority import javabot.web.model.User import javabot.web.views.ViewFactory import javax.inject.Inject @@ -35,230 +34,294 @@ constructor( ) { fun configureRoutes(routing: Routing) { - routing { + with(routing) { route("/admin") { get { - val user = getAuthenticatedUser(call) ?: run { - val view = PublicErrorResource.view403() - call.respond(FreeMarkerContent(view.template, view.toModel())) - return@get - } - + val user = + getAuthenticatedUser(call) + ?: run { + val view = PublicErrorResource.view403() + call.respond(FreeMarkerContent(view.template, view.toModel())) + return@get + } + val current = adminDao.getAdminByEmailAddress(user.email) if (current == null) { val view = PublicErrorResource.view403() call.respond(FreeMarkerContent(view.template, view.toModel())) } else { - val view = viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + val view = + viewFactory.createAdminIndexView( + KtorServletRequest(call), + current, + Admin(), + ) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } } get("/config") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } val view = viewFactory.createConfigurationView(KtorServletRequest(call)) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/javadoc") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } val view = viewFactory.createJavadocAdminView(KtorServletRequest(call)) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/newChannel") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - val view = viewFactory.createChannelEditView(KtorServletRequest(call), Channel()) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val view = + viewFactory.createChannelEditView(KtorServletRequest(call), Channel()) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/editChannel/{channel}") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - val channelName = call.parameters["channel"] ?: run { - call.respond(HttpStatusCode.BadRequest) - return@get - } - - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - val channel = channelDao.get(channelName) ?: run { - call.respond(HttpStatusCode.NotFound) - return@get - } + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val channelName = + call.parameters["channel"] + ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val channel = + channelDao.get(channelName) + ?: run { + call.respond(HttpStatusCode.NotFound) + return@get + } val view = viewFactory.createChannelEditView(KtorServletRequest(call), channel) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } post("/saveChannel") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@post - } - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@post - } - + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + val params = call.receiveParameters() val id = params["id"] val name = params["name"] ?: "" val key = params["key"] ?: "" val logged = params["logged"]?.toBoolean() ?: false - + val channel = - if (id == null) Channel(name, key, logged) else Channel(ObjectId(id), name, key, logged) + if (id == null) Channel(name, key, logged) + else Channel(ObjectId(id), name, key, logged) channelDao.save(channel) - + val current = adminDao.getAdminByEmailAddress(user.email)!! - val view = viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + val view = + viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } post("/saveConfig") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@post - } - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@post - } - + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + val params = call.receiveParameters() val config = configDao.get() config.server = params["server"] ?: config.server config.url = params["url"] ?: config.url config.port = params["port"]?.toIntOrNull() ?: config.port - config.historyLength = params["historyLength"]?.toIntOrNull() ?: config.historyLength + config.historyLength = + params["historyLength"]?.toIntOrNull() ?: config.historyLength config.trigger = params["trigger"] ?: config.trigger config.nick = params["nick"] ?: config.nick config.password = params["password"] ?: config.password - config.throttleThreshold = params["throttleThreshold"]?.toIntOrNull() ?: config.throttleThreshold - config.minimumNickServAge = params["minimumNickServAge"]?.toIntOrNull() ?: config.minimumNickServAge + config.throttleThreshold = + params["throttleThreshold"]?.toIntOrNull() ?: config.throttleThreshold + config.minimumNickServAge = + params["minimumNickServAge"]?.toIntOrNull() ?: config.minimumNickServAge configDao.save(config) - + val view = viewFactory.createConfigurationView(KtorServletRequest(call)) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/enableOperation/{name}") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - val name = call.parameters["name"] ?: run { - call.respond(HttpStatusCode.BadRequest) - return@get - } - - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val name = + call.parameters["name"] + ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } javabot.enableOperation(name) val view = viewFactory.createConfigurationView(KtorServletRequest(call)) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/disableOperation/{name}") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - val name = call.parameters["name"] ?: run { - call.respond(HttpStatusCode.BadRequest) - return@get - } - - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val name = + call.parameters["name"] + ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } javabot.disableOperation(name) val view = viewFactory.createConfigurationView(KtorServletRequest(call)) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/edit/{id}") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - val id = call.parameters["id"] ?: run { - call.respond(HttpStatusCode.BadRequest) - return@get - } - - val current = adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val id = + call.parameters["id"] + ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + val current = + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } - val view = viewFactory.createAdminIndexView(KtorServletRequest(call), current, adminDao.find(ObjectId(id))) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + val view = + viewFactory.createAdminIndexView( + KtorServletRequest(call), + current, + adminDao.find(ObjectId(id)), + ) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/delete/{id}") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - val id = call.parameters["id"] ?: run { - call.respond(HttpStatusCode.BadRequest) - return@get - } - - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val id = + call.parameters["id"] + ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } val admin = adminDao.find(ObjectId(id)) if (admin != null && (!admin.botOwner)) { adminDao.delete(admin) } - + val current = adminDao.getAdminByEmailAddress(user.email)!! - val view = viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + val view = + viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } post("/add") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@post - } - + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + val params = call.receiveParameters() val ircName = params["ircName"] ?: "" val hostName = params["hostName"] ?: "" val emailAddress = params["emailAddress"] ?: "" - + var admin: Admin? = adminDao.getAdminByEmailAddress(emailAddress) if (admin == null) { admin = Admin(ircName, emailAddress, hostName, true) @@ -268,86 +331,104 @@ constructor( admin.emailAddress = emailAddress } adminDao.save(admin) - + val current = adminDao.getAdminByEmailAddress(user.email)!! - val view = viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + val view = + viewFactory.createAdminIndexView(KtorServletRequest(call), current, Admin()) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } post("/addApi") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@post - } - - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@post - } - + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@post + } + val params = call.receiveParameters() val name = params["name"] val groupId = params["groupId"] val artifactId = params["artifactId"] val version = params["version"] - + version?.let { - val apiName = name ?: artifactId ?: run { - call.respond(HttpStatusCode.BadRequest) - return@post - } - val api = JavadocApi(config, apiName, groupId ?: "", artifactId ?: "", version) + val apiName = + name + ?: artifactId + ?: run { + call.respond(HttpStatusCode.BadRequest) + return@post + } + val api = + JavadocApi(config, apiName, groupId ?: "", artifactId ?: "", version) apiDao.save(api) apiDao.save(ApiEvent.add(user.email, api)) } val view = viewFactory.createJavadocAdminView(KtorServletRequest(call)) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/deleteApi/{id}") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - val id = call.parameters["id"] ?: run { - call.respond(HttpStatusCode.BadRequest) - return@get - } - - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val id = + call.parameters["id"] + ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } apiDao.delete(ObjectId(id)) - + val view = viewFactory.createJavadocAdminView(KtorServletRequest(call)) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/reloadApi/{id}") { - val user = getAuthenticatedUser(call) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } - val id = call.parameters["id"] ?: run { - call.respond(HttpStatusCode.BadRequest) - return@get - } - - adminDao.getAdminByEmailAddress(user.email) ?: run { - call.respond(HttpStatusCode.Forbidden) - return@get - } + val user = + getAuthenticatedUser(call) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } + val id = + call.parameters["id"] + ?: run { + call.respond(HttpStatusCode.BadRequest) + return@get + } + + adminDao.getAdminByEmailAddress(user.email) + ?: run { + call.respond(HttpStatusCode.Forbidden) + return@get + } apiDao.find(ObjectId(id))?.let { apiDao.save(ApiEvent.reload(user.email, it)) } - + val view = viewFactory.createJavadocAdminView(KtorServletRequest(call)) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } } } } - + private fun getAuthenticatedUser(call: ApplicationCall): User? { // TODO: Implement proper authentication return null diff --git a/src/main/kotlin/javabot/web/resources/BotResource.kt b/src/main/kotlin/javabot/web/resources/BotResource.kt index d2e9b979..3b1997ac 100644 --- a/src/main/kotlin/javabot/web/resources/BotResource.kt +++ b/src/main/kotlin/javabot/web/resources/BotResource.kt @@ -1,10 +1,10 @@ package javabot.web.resources +import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.freemarker.* import io.ktor.server.response.* import io.ktor.server.routing.* -import io.ktor.http.* import java.io.UnsupportedEncodingException import java.net.URLDecoder import java.time.LocalDate @@ -18,18 +18,18 @@ import org.slf4j.LoggerFactory class BotResource @Inject constructor(var viewFactory: ViewFactory) { fun configureRoutes(routing: Routing) { - routing { + with(routing) { get("/") { if (call.request.queryParameters["test.exception"] != null) { throw RuntimeException("Testing 500 pages") } val view = viewFactory.createIndexView(KtorServletRequest(call)) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/index") { val view = viewFactory.createIndexView(KtorServletRequest(call)) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/factoids") { @@ -37,32 +37,33 @@ class BotResource @Inject constructor(var viewFactory: ViewFactory) { val name = call.request.queryParameters["name"] val value = call.request.queryParameters["value"] val userName = call.request.queryParameters["userName"] - - val view = viewFactory.createFactoidsView( - KtorServletRequest(call), - page, - Factoid.of(name, value, userName) - ) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + + val view = + viewFactory.createFactoidsView( + KtorServletRequest(call), + page, + Factoid.of(name, value, userName), + ) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/karma") { val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 val view = viewFactory.createKarmaView(KtorServletRequest(call), page) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/changes") { val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 val message = call.request.queryParameters["message"] val view = viewFactory.createChangesView(KtorServletRequest(call), page, message) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } get("/logs/{channel}/{date}") { val channel = call.parameters["channel"] val dateString = call.parameters["date"] - + val date: LocalDateTime = try { if ("today" == dateString) LocalDate.now().atStartOfDay() @@ -79,7 +80,7 @@ class BotResource @Inject constructor(var viewFactory: ViewFactory) { } val view = viewFactory.createLogsView(KtorServletRequest(call), channelName, date) - call.respond(FreeMarkerContent(view.getChildView(), view.toModel())) + call.respond(FreeMarkerContent("main.ftl", view.toModel())) } } } diff --git a/src/main/kotlin/javabot/web/resources/KtorServletRequest.kt b/src/main/kotlin/javabot/web/resources/KtorServletRequest.kt index ba7a51b7..62972bd6 100644 --- a/src/main/kotlin/javabot/web/resources/KtorServletRequest.kt +++ b/src/main/kotlin/javabot/web/resources/KtorServletRequest.kt @@ -9,15 +9,16 @@ import javax.servlet.* import javax.servlet.http.* /** - * Adapter class to make Ktor ApplicationCall work like HttpServletRequest - * Only implements the methods used by the views + * Adapter class to make Ktor ApplicationCall work like HttpServletRequest Only implements the + * methods used by the views */ class KtorServletRequest(private val call: ApplicationCall) : HttpServletRequest { - + override fun getCookies(): Array? { - val cookies = call.request.cookies.rawCookies.map { (name, value) -> - Cookie(name, value) - }.toTypedArray() + val cookies = + call.request.cookies.rawCookies + .map { (name, value) -> Cookie(name, value) } + .toTypedArray() return if (cookies.isEmpty()) null else cookies } @@ -26,7 +27,8 @@ class KtorServletRequest(private val call: ApplicationCall) : HttpServletRequest } override fun getParameterMap(): MutableMap> { - return call.request.queryParameters.entries() + return call.request.queryParameters + .entries() .associate { it.key to it.value.toTypedArray() } .toMutableMap() } @@ -42,94 +44,181 @@ class KtorServletRequest(private val call: ApplicationCall) : HttpServletRequest // Minimal session implementation private class DummySession : HttpSession { private val attributes = mutableMapOf() - + override fun getAttribute(name: String?): Any? = attributes[name] - override fun setAttribute(name: String?, value: Any?) { attributes[name] = value } - override fun removeAttribute(name: String?) { attributes.remove(name) } - override fun getAttributeNames(): Enumeration = Collections.enumeration(attributes.keys) + + override fun setAttribute(name: String?, value: Any?) { + attributes[name] = value + } + + override fun removeAttribute(name: String?) { + attributes.remove(name) + } + + override fun getAttributeNames(): Enumeration = + Collections.enumeration(attributes.keys) + override fun getCreationTime(): Long = System.currentTimeMillis() + override fun getId(): String = UUID.randomUUID().toString() + override fun getLastAccessedTime(): Long = System.currentTimeMillis() + override fun getMaxInactiveInterval(): Int = 3600 - override fun getServletContext(): ServletContext? = null + + fun getServletContext(): ServletContext? = null + override fun invalidate() {} + override fun isNew(): Boolean = false + override fun setMaxInactiveInterval(interval: Int) {} - @Deprecated("Deprecated") - override fun getSessionContext(): HttpSessionContext? = null - @Deprecated("Deprecated") - override fun getValue(name: String?): Any? = getAttribute(name) + + @Deprecated("Deprecated") override fun getSessionContext(): HttpSessionContext? = null + + @Deprecated("Deprecated") override fun getValue(name: String?): Any? = getAttribute(name) + @Deprecated("Deprecated") override fun getValueNames(): Array = attributes.keys.toTypedArray() + @Deprecated("Deprecated") override fun putValue(name: String?, value: Any?) = setAttribute(name, value) - @Deprecated("Deprecated") - override fun removeValue(name: String?) = removeAttribute(name) + + @Deprecated("Deprecated") override fun removeValue(name: String?) = removeAttribute(name) } // Required methods - not all implemented override fun getAuthType(): String? = null + override fun getContextPath(): String = "" - override fun getHeader(name: String?): String? = call.request.headers[name] - override fun getHeaderNames(): Enumeration = Collections.enumeration(call.request.headers.names()) - override fun getHeaders(name: String?): Enumeration = Collections.enumeration(call.request.headers.getAll(name) ?: emptyList()) + + override fun getHeader(name: String?): String? = call.request.headers[name ?: ""] + + override fun getHeaderNames(): Enumeration = + Collections.enumeration(call.request.headers.names()) + + override fun getHeaders(name: String?): Enumeration = + Collections.enumeration(call.request.headers.getAll(name ?: "") ?: emptyList()) + override fun getMethod(): String = call.request.httpMethod.value + override fun getPathInfo(): String? = call.request.path() + override fun getPathTranslated(): String? = null + override fun getQueryString(): String? = call.request.queryString() + override fun getRemoteUser(): String? = null + override fun getRequestedSessionId(): String? = null + override fun getRequestURI(): String = call.request.uri + override fun getRequestURL(): StringBuffer = StringBuffer(call.request.origin.uri) + override fun getServletPath(): String = "" + override fun getUserPrincipal(): Principal? = null + override fun isRequestedSessionIdFromCookie(): Boolean = false + override fun isRequestedSessionIdFromURL(): Boolean = false - @Deprecated("Deprecated") - override fun isRequestedSessionIdFromUrl(): Boolean = false + + @Deprecated("Deprecated") override fun isRequestedSessionIdFromUrl(): Boolean = false + override fun isRequestedSessionIdValid(): Boolean = false + override fun isUserInRole(role: String?): Boolean = false - override fun authenticate(response: HttpServletResponse?): Boolean = false - override fun changeSessionId(): String = "" + + fun authenticate(response: HttpServletResponse?): Boolean = false + + fun changeSessionId(): String = "" + override fun getIntHeader(name: String?): Int = -1 + override fun getDateHeader(name: String?): Long = -1 - override fun login(username: String?, password: String?) {} - override fun logout() {} - override fun getParts(): MutableCollection = mutableListOf() - override fun getPart(name: String?): Part? = null + + fun login(username: String?, password: String?) {} + + fun logout() {} + + fun getParts(): MutableCollection = mutableListOf() + + fun getPart(name: String?): Part? = null + override fun upgrade(handlerClass: Class?): T = TODO() + override fun getAttribute(name: String?): Any? = null + override fun getAttributeNames(): Enumeration = Collections.emptyEnumeration() + override fun getCharacterEncoding(): String = "UTF-8" + override fun getContentLength(): Int = -1 - override fun getContentLengthLong(): Long = -1 + + fun getContentLengthLong(): Long = -1 + override fun getContentType(): String? = call.request.contentType().toString() + override fun getInputStream(): ServletInputStream = TODO() - override fun getLocalAddr(): String = "" - override fun getLocalName(): String = "" - override fun getLocalPort(): Int = 0 + + fun getLocalAddr(): String = "" + + fun getLocalName(): String = "" + + fun getLocalPort(): Int = 0 + override fun getLocale(): Locale = Locale.getDefault() - override fun getLocales(): Enumeration = Collections.enumeration(listOf(Locale.getDefault())) - override fun getParameterNames(): Enumeration = Collections.enumeration(call.request.queryParameters.names()) - override fun getParameterValues(name: String?): Array? = call.request.queryParameters.getAll(name)?.toTypedArray() + + override fun getLocales(): Enumeration = + Collections.enumeration(listOf(Locale.getDefault())) + + override fun getParameterNames(): Enumeration = + Collections.enumeration(call.request.queryParameters.names()) + + override fun getParameterValues(name: String?): Array? = + call.request.queryParameters.getAll(name)?.toTypedArray() + override fun getProtocol(): String = "HTTP/1.1" + override fun getReader(): BufferedReader = TODO() + override fun getRealPath(path: String?): String? = null + override fun getRemoteAddr(): String = call.request.local.remoteHost + override fun getRemoteHost(): String = call.request.local.remoteHost - override fun getRemotePort(): Int = call.request.local.remotePort + + fun getRemotePort(): Int = call.request.local.remotePort + override fun getRequestDispatcher(path: String?): RequestDispatcher? = null + override fun getScheme(): String = call.request.origin.scheme + override fun getServerName(): String = call.request.local.serverHost + override fun getServerPort(): Int = call.request.local.serverPort - override fun getServletContext(): ServletContext? = null - override fun isAsyncStarted(): Boolean = false - override fun isAsyncSupported(): Boolean = false + + fun getServletContext(): ServletContext? = null + + fun isAsyncStarted(): Boolean = false + + fun isAsyncSupported(): Boolean = false + override fun isSecure(): Boolean = call.request.origin.scheme == "https" + override fun removeAttribute(name: String?) {} + override fun setAttribute(name: String?, o: Any?) {} + override fun setCharacterEncoding(env: String?) {} - override fun startAsync(): AsyncContext = TODO() - override fun startAsync(servletRequest: ServletRequest?, servletResponse: ServletResponse?): AsyncContext = TODO() - override fun getDispatcherType(): DispatcherType = DispatcherType.REQUEST + + fun startAsync(): AsyncContext = TODO() + + fun startAsync( + servletRequest: ServletRequest?, + servletResponse: ServletResponse?, + ): AsyncContext = TODO() + + fun getDispatcherType(): DispatcherType = DispatcherType.REQUEST } diff --git a/src/main/kotlin/javabot/web/resources/PublicOAuthResource.kt b/src/main/kotlin/javabot/web/resources/PublicOAuthResource.kt index 70ae2331..d03a8b7b 100644 --- a/src/main/kotlin/javabot/web/resources/PublicOAuthResource.kt +++ b/src/main/kotlin/javabot/web/resources/PublicOAuthResource.kt @@ -1,11 +1,11 @@ package javabot.web.resources import com.antwerkz.sofia.Sofia +import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.sessions.* -import io.ktor.http.* import java.net.URI import java.util.UUID import javabot.dao.AdminDao @@ -19,7 +19,6 @@ import javabot.web.model.User import javax.inject.Inject import org.brickred.socialauth.SocialAuthConfig import org.brickred.socialauth.SocialAuthManager -import org.brickred.socialauth.util.SocialAuthUtil import org.slf4j.LoggerFactory class PublicOAuthResource @Inject constructor(var adminDao: AdminDao) { @@ -27,19 +26,23 @@ class PublicOAuthResource @Inject constructor(var adminDao: AdminDao) { var configuration: JavabotConfiguration? = null fun configureRoutes(routing: Routing) { - routing { + with(routing) { get("/auth/login") { val oauthCfg = configuration!!.OAuthCfg if (oauthCfg != null) { try { val manager = getSocialAuthManager() - + // Store manager in session call.sessions.set(UserSession(AUTH_MANAGER)) - val uri = URI( - manager?.getAuthenticationUrl("googleplus", configuration!!.OAuthSuccessUrl) - ) + val uri = + URI( + manager?.getAuthenticationUrl( + "googleplus", + configuration!!.OAuthSuccessUrl, + ) + ) call.respondRedirect(uri.toString()) return@get } catch (e: Exception) { @@ -51,10 +54,12 @@ class PublicOAuthResource @Inject constructor(var adminDao: AdminDao) { get("/auth/verify") { // TODO: Retrieve manager from session - val manager = getSocialAuthManager() ?: run { - call.respond(HttpStatusCode.Unauthorized) - return@get - } + val manager = + getSocialAuthManager() + ?: run { + call.respond(HttpStatusCode.Unauthorized) + return@get + } try { val params = mutableMapOf() @@ -67,7 +72,8 @@ class PublicOAuthResource @Inject constructor(var adminDao: AdminDao) { Sofia.loggingInUser(p) - var tempUser = User(UUID.randomUUID(), p.email, p.validatedId, provider.accessGrant) + var tempUser = + User(UUID.randomUUID(), p.email, p.validatedId, provider.accessGrant) tempUser.authorities.add(ROLE_PUBLIC) val user = INSTANCE.getByOpenIDIdentifier(tempUser.openIDIdentifier) diff --git a/src/main/kotlin/javabot/web/views/AdminIndexView.kt b/src/main/kotlin/javabot/web/views/AdminIndexView.kt index 8782cf4a..7ec57ec1 100644 --- a/src/main/kotlin/javabot/web/views/AdminIndexView.kt +++ b/src/main/kotlin/javabot/web/views/AdminIndexView.kt @@ -28,12 +28,9 @@ constructor( override fun getChildView(): String { return "admin/index.ftl" } - + override fun toModel(): Map { - return super.toModel() + mapOf( - "current" to current, - "editing" to editing, - "admins" to getAdmins() - ) + return super.toModel() + + mapOf("current" to current, "editing" to editing, "admins" to getAdmins()) } } diff --git a/src/main/kotlin/javabot/web/views/ChangesView.kt b/src/main/kotlin/javabot/web/views/ChangesView.kt index b57ab371..927e3ef9 100644 --- a/src/main/kotlin/javabot/web/views/ChangesView.kt +++ b/src/main/kotlin/javabot/web/views/ChangesView.kt @@ -46,11 +46,8 @@ constructor( date, ) } - + override fun toModel(): Map { - return super.toModel() + mapOf( - "message" to message, - "date" to date - ) + return super.toModel() + mapOf("message" to message, "date" to date) } } diff --git a/src/main/kotlin/javabot/web/views/ChannelEditView.kt b/src/main/kotlin/javabot/web/views/ChannelEditView.kt index ec02d274..b272f5d9 100644 --- a/src/main/kotlin/javabot/web/views/ChannelEditView.kt +++ b/src/main/kotlin/javabot/web/views/ChannelEditView.kt @@ -23,10 +23,8 @@ constructor( override fun getChildView(): String { return "admin/editChannel.ftl" } - + override fun toModel(): Map { - return super.toModel() + mapOf( - "channel" to channel - ) + return super.toModel() + mapOf("channel" to channel) } } diff --git a/src/main/kotlin/javabot/web/views/ConfigurationView.kt b/src/main/kotlin/javabot/web/views/ConfigurationView.kt index cc543892..8f88f3cd 100644 --- a/src/main/kotlin/javabot/web/views/ConfigurationView.kt +++ b/src/main/kotlin/javabot/web/views/ConfigurationView.kt @@ -46,12 +46,13 @@ constructor( override fun getChildView(): String { return "admin/configuration.ftl" } - + override fun toModel(): Map { - return super.toModel() + mapOf( - "configuration" to configuration, - "operations" to operations(), - "currentOps" to getCurrentOps() - ) + return super.toModel() + + mapOf( + "configuration" to configuration, + "operations" to operations(), + "currentOps" to getCurrentOps(), + ) } } diff --git a/src/main/kotlin/javabot/web/views/ErrorView.kt b/src/main/kotlin/javabot/web/views/ErrorView.kt index 597ead35..40b3804e 100644 --- a/src/main/kotlin/javabot/web/views/ErrorView.kt +++ b/src/main/kotlin/javabot/web/views/ErrorView.kt @@ -2,8 +2,6 @@ package javabot.web.views class ErrorView(val template: String, val image: String) { fun toModel(): Map { - return mapOf( - "image" to image - ) + return mapOf("image" to image) } } diff --git a/src/main/kotlin/javabot/web/views/FactoidsView.kt b/src/main/kotlin/javabot/web/views/FactoidsView.kt index 17f68f70..82e379fb 100644 --- a/src/main/kotlin/javabot/web/views/FactoidsView.kt +++ b/src/main/kotlin/javabot/web/views/FactoidsView.kt @@ -70,11 +70,9 @@ constructor( override fun getPagedView(): String { return "/factoids.ftl" } - + override fun toModel(): Map { - return super.toModel() + mapOf( - "filter" to getFilter() - ) + return super.toModel() + mapOf("filter" to getFilter()) } companion object { diff --git a/src/main/kotlin/javabot/web/views/IndexView.kt b/src/main/kotlin/javabot/web/views/IndexView.kt index fbc4c948..58c40bdb 100644 --- a/src/main/kotlin/javabot/web/views/IndexView.kt +++ b/src/main/kotlin/javabot/web/views/IndexView.kt @@ -21,7 +21,7 @@ constructor( override fun getChildView(): String { return "/index.ftl" } - + override fun toModel(): Map { return super.toModel() } diff --git a/src/main/kotlin/javabot/web/views/JavadocAdminView.kt b/src/main/kotlin/javabot/web/views/JavadocAdminView.kt index 6a915ddd..576055b0 100644 --- a/src/main/kotlin/javabot/web/views/JavadocAdminView.kt +++ b/src/main/kotlin/javabot/web/views/JavadocAdminView.kt @@ -26,10 +26,8 @@ constructor( fun apis(): List { return apiDao.findAll() } - + override fun toModel(): Map { - return super.toModel() + mapOf( - "apis" to apis() - ) + return super.toModel() + mapOf("apis" to apis()) } } diff --git a/src/main/kotlin/javabot/web/views/LogsView.kt b/src/main/kotlin/javabot/web/views/LogsView.kt index a9bcd51c..a25fd43c 100644 --- a/src/main/kotlin/javabot/web/views/LogsView.kt +++ b/src/main/kotlin/javabot/web/views/LogsView.kt @@ -52,15 +52,16 @@ constructor( override fun getChildView(): String { return "logs.ftl" } - + override fun toModel(): Map { - return super.toModel() + mapOf( - "channel" to channel, - "date" to date, - "today" to today, - "yesterday" to yesterday, - "tomorrow" to tomorrow, - "logs" to logs() - ) + return super.toModel() + + mapOf( + "channel" to channel, + "date" to date, + "today" to today, + "yesterday" to yesterday, + "tomorrow" to tomorrow, + "logs" to logs(), + ) } } diff --git a/src/main/kotlin/javabot/web/views/MainView.kt b/src/main/kotlin/javabot/web/views/MainView.kt index 997464c9..b74414ba 100644 --- a/src/main/kotlin/javabot/web/views/MainView.kt +++ b/src/main/kotlin/javabot/web/views/MainView.kt @@ -88,10 +88,8 @@ abstract class MainView( open fun format(date: LocalDateTime?): String { return if (date != null) DATE_TIME_FORMATTER.format(date) else "" } - - /** - * Convert this view to a model map for FreeMarker - */ + + /** Convert this view to a model map for FreeMarker */ open fun toModel(): Map { return mapOf( "sofia" to sofia(), @@ -102,7 +100,8 @@ abstract class MainView( "channels" to getChannels(), "apis" to getAPIs(), "errors" to getErrors(), - "hasErrors" to hasErrors() + "hasErrors" to hasErrors(), + "childView" to getChildView(), ) } } diff --git a/src/main/kotlin/javabot/web/views/PagedView.kt b/src/main/kotlin/javabot/web/views/PagedView.kt index c4bd2c42..b0137439 100644 --- a/src/main/kotlin/javabot/web/views/PagedView.kt +++ b/src/main/kotlin/javabot/web/views/PagedView.kt @@ -80,20 +80,21 @@ abstract class PagedView( } abstract fun getPageItems(): List - + override fun toModel(): Map { - return super.toModel() + mapOf( - "page" to getPage(), - "pageCount" to getPageCount(), - "itemsPerPage" to getItemsPerPage(), - "itemCount" to itemCount, - "nextPage" to getNextPage(), - "previousPage" to getPreviousPage(), - "startRange" to getStartRange(), - "endRange" to getEndRange(), - "pageItems" to getPageItems(), - "pagedView" to getPagedView() - ) + return super.toModel() + + mapOf( + "page" to getPage(), + "pageCount" to getPageCount(), + "itemsPerPage" to getItemsPerPage(), + "itemCount" to itemCount, + "nextPage" to getNextPage(), + "previousPage" to getPreviousPage(), + "startRange" to getStartRange(), + "endRange" to getEndRange(), + "pageItems" to getPageItems(), + "pagedView" to getPagedView(), + ) } companion object { diff --git a/src/main/resources/main.ftl b/src/main/resources/main.ftl index 3ce8a352..5000af4c 100644 --- a/src/main/resources/main.ftl +++ b/src/main/resources/main.ftl @@ -30,12 +30,12 @@ - <#if !loggedIn()> + <#if !loggedIn> - <#if isAdmin()> + <#if isAdmin>

Admin

    @@ -50,7 +50,7 @@ - <#if isAdmin()> + <#if isAdmin> @@ -61,11 +61,11 @@
    Channels +
    - <#list getChannels() as channel> + <#list channels as channel>
    - + class='current' >${channel.name} @@ -79,7 +79,7 @@
    - <#list getAPIs() as api> + <#list apis as api>
    ${api.name} @@ -107,7 +107,11 @@
    - <#include getChildView() > + <#if pagedView??> + <#include pagedView > + <#elseif childView??> + <#include childView > +