package be.simplenotes.app.routes import be.simplenotes.app.api.ApiNoteController import be.simplenotes.app.api.ApiUserController import be.simplenotes.app.controllers.* import be.simplenotes.app.filters.* import be.simplenotes.domain.security.JwtPayload import org.http4k.core.* import org.http4k.core.Method.* import org.http4k.filter.ResponseFilters.GZip import org.http4k.filter.ServerFilters.InitialiseRequestContext import org.http4k.routing.* import org.http4k.routing.ResourceLoader.Companion.Classpath class Router( private val baseController: BaseController, private val userController: UserController, private val noteController: NoteController, private val settingsController: SettingsController, private val apiUserController: ApiUserController, private val apiNoteController: ApiNoteController, private val healthCheckController: HealthCheckController, private val requiredAuth: Filter, private val optionalAuth: Filter, private val apiAuth: Filter, private val errorFilter: ErrorFilter, private val transactionFilter: TransactionFilter, private val contexts: RequestContexts, ) { operator fun invoke(): RoutingHttpHandler { val basicRoutes = routes( "/health" bind GET to healthCheckController::healthCheck, ImmutableFilter.then(static(Classpath("/static"), "woff2" to ContentType("font/woff2"))) ) val publicRoutes = routes( "/" bind GET public baseController::index, "/register" bind GET public userController::register, "/register" bind POST `public transactional` userController::register, "/login" bind GET public userController::login, "/login" bind POST public userController::login, "/logout" bind POST to userController::logout, "/notes/public/{uuid}" bind GET public noteController::public, ) val protectedRoutes = routes( "/settings" bind GET protected settingsController::settings, "/settings" bind POST transactional settingsController::settings, "/export" bind POST protected settingsController::export, "/notes" bind GET protected noteController::list, "/notes" bind POST protected noteController::search, "/notes/new" bind GET protected noteController::new, "/notes/new" bind POST transactional noteController::new, "/notes/trash" bind GET protected noteController::trash, "/notes/{uuid}" bind GET protected noteController::note, "/notes/{uuid}" bind POST transactional noteController::note, "/notes/{uuid}/edit" bind GET protected noteController::edit, "/notes/{uuid}/edit" bind POST transactional noteController::edit, "/notes/deleted/{uuid}" bind POST transactional noteController::deleted, ) val apiRoutes = routes( "/api/login" bind POST to apiUserController::login, ) val protectedApiRoutes = routes( "/api/notes" bind GET protected apiNoteController::notes, "/api/notes" bind POST transactional apiNoteController::createNote, "/api/notes/search" bind POST transactional apiNoteController::search, "/api/notes/{uuid}" bind GET protected apiNoteController::note, "/api/notes/{uuid}" bind PUT transactional apiNoteController::update, ) val routes = routes( basicRoutes, optionalAuth.then(publicRoutes), requiredAuth.then(protectedRoutes), apiAuth.then(protectedApiRoutes), apiRoutes, ) val globalFilters = errorFilter .then(InitialiseRequestContext(contexts)) .then(SecurityFilter) .then(GZip()) return globalFilters.then(routes) } private inline infix fun PathMethod.public(crossinline handler: PublicHandler) = this to { handler(it, it.jwtPayload(contexts)) } private inline infix fun PathMethod.protected(crossinline handler: ProtectedHandler) = this to { handler(it, it.jwtPayload(contexts)!!) } private inline infix fun PathMethod.transactional(crossinline handler: ProtectedHandler) = this to transactionFilter.then { handler(it, it.jwtPayload(contexts)!!) } private inline infix fun PathMethod.`public transactional`(crossinline handler: PublicHandler) = this to transactionFilter.then { handler(it, it.jwtPayload(contexts)) } } private typealias PublicHandler = (Request, JwtPayload?) -> Response private typealias ProtectedHandler = (Request, JwtPayload) -> Response