org.http4s.dsl.Http4sDsl Scala Examples

The following examples show how to use org.http4s.dsl.Http4sDsl. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example.
Example 1
Source File: CategoryRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes

import cats._
import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.server.Router
import shop.algebras.Categories
import shop.http.json._

final class CategoryRoutes[F[_]: Defer: Monad](
    categories: Categories[F]
) extends Http4sDsl[F] {

  private[routes] val prefixPath = "/categories"

  private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root =>
      Ok(categories.findAll)
  }

  val routes: HttpRoutes[F] = Router(
    prefixPath -> httpRoutes
  )

} 
Example 2
Source File: UserRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes

import cats._
import cats.implicits._
import org.http4s._
import org.http4s.circe.JsonDecoder
import org.http4s.dsl.Http4sDsl
import org.http4s.server.Router
import shop.algebras.Auth
import shop.domain.auth._
import shop.effects._
import shop.http.decoder._
import shop.http.json._

final class UserRoutes[F[_]: Defer: JsonDecoder: MonadThrow](
    auth: Auth[F]
) extends Http4sDsl[F] {

  private[routes] val prefixPath = "/auth"

  private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] {

    case req @ POST -> Root / "users" =>
      req
        .decodeR[CreateUser] { user =>
          auth
            .newUser(user.username.toDomain, user.password.toDomain)
            .flatMap(Created(_))
            .recoverWith {
              case UserNameInUse(u) => Conflict(u.value)
            }
        }

  }

  val routes: HttpRoutes[F] = Router(
    prefixPath -> httpRoutes
  )

} 
Example 3
Source File: CartRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes.secured

import cats._
import cats.implicits._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl
import org.http4s.server._
import shop.algebras.ShoppingCart
import shop.domain.cart._
import shop.domain.item._
import shop.http.auth.users.CommonUser
import shop.http.json._

final class CartRoutes[F[_]: Defer: JsonDecoder: Monad](
    shoppingCart: ShoppingCart[F]
) extends Http4sDsl[F] {

  private[routes] val prefixPath = "/cart"

  private val httpRoutes: AuthedRoutes[CommonUser, F] = AuthedRoutes.of {
    // Get shopping cart
    case GET -> Root as user =>
      Ok(shoppingCart.get(user.value.id))

    // Add items to the cart
    case ar @ POST -> Root as user =>
      ar.req.asJsonDecode[Cart].flatMap { cart =>
        cart.items
          .map {
            case (id, quantity) =>
              shoppingCart.add(user.value.id, id, quantity)
          }
          .toList
          .sequence *> Created()
      }

    // Modify items in the cart
    case ar @ PUT -> Root as user =>
      ar.req.asJsonDecode[Cart].flatMap { cart =>
        shoppingCart.update(user.value.id, cart) *> Ok()
      }

    // Remove item from the cart
    case DELETE -> Root / UUIDVar(uuid) as user =>
      shoppingCart.removeItem(user.value.id, ItemId(uuid)) *> NoContent()
  }

  def routes(authMiddleware: AuthMiddleware[F, CommonUser]): HttpRoutes[F] = Router(
    prefixPath -> authMiddleware(httpRoutes)
  )

} 
Example 4
Source File: CheckoutRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes.secured

import cats.Defer
import cats.implicits._
import org.http4s._
import org.http4s.circe.JsonDecoder
import org.http4s.dsl.Http4sDsl
import org.http4s.server._
import shop.domain.cart._
import shop.domain.checkout._
import shop.domain.order._
import shop.effects._
import shop.http.auth.users.CommonUser
import shop.http.decoder._
import shop.http.json._
import shop.programs.CheckoutProgram

final class CheckoutRoutes[F[_]: Defer: JsonDecoder: MonadThrow](
    program: CheckoutProgram[F]
) extends Http4sDsl[F] {

  private[routes] val prefixPath = "/checkout"

  private val httpRoutes: AuthedRoutes[CommonUser, F] = AuthedRoutes.of {

    case ar @ POST -> Root as user =>
      ar.req.decodeR[Card] { card =>
        program
          .checkout(user.value.id, card)
          .flatMap(Created(_))
          .recoverWith {
            case CartNotFound(userId) =>
              NotFound(s"Cart not found for user: ${userId.value}")
            case EmptyCartError =>
              BadRequest("Shopping cart is empty!")
            case PaymentError(cause) =>
              BadRequest(cause)
            case OrderError(cause) =>
              BadRequest(cause)
          }
      }

  }

  def routes(authMiddleware: AuthMiddleware[F, CommonUser]): HttpRoutes[F] = Router(
    prefixPath -> authMiddleware(httpRoutes)
  )

} 
Example 5
Source File: OrderRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes.secured

import cats._
import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.server._
import shop.algebras.Orders
import shop.domain.order._
import shop.http.auth.users.CommonUser
import shop.http.json._

final class OrderRoutes[F[_]: Defer: Monad](
    orders: Orders[F]
) extends Http4sDsl[F] {

  private[routes] val prefixPath = "/orders"

  private val httpRoutes: AuthedRoutes[CommonUser, F] = AuthedRoutes.of {

    case GET -> Root as user =>
      Ok(orders.findBy(user.value.id))

    case GET -> Root / UUIDVar(orderId) as user =>
      Ok(orders.get(user.value.id, OrderId(orderId)))

  }

  def routes(authMiddleware: AuthMiddleware[F, CommonUser]): HttpRoutes[F] = Router(
    prefixPath -> authMiddleware(httpRoutes)
  )

} 
Example 6
Source File: BrandRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes

import cats._
import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.server.Router
import shop.algebras.Brands
import shop.http.json._

final class BrandRoutes[F[_]: Defer: Monad](
    brands: Brands[F]
) extends Http4sDsl[F] {

  private[routes] val prefixPath = "/brands"

  private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root =>
      Ok(brands.findAll)
  }

  val routes: HttpRoutes[F] = Router(
    prefixPath -> httpRoutes
  )

} 
Example 7
Source File: HealthRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes

import cats._
import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.server.Router
import shop.algebras.HealthCheck
import shop.http.json._

final class HealthRoutes[F[_]: Defer: Monad](
    healthCheck: HealthCheck[F]
) extends Http4sDsl[F] {

  private[routes] val prefixPath = "/healthcheck"

  private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root =>
      Ok(healthCheck.status)
  }

  val routes: HttpRoutes[F] = Router(
    prefixPath -> httpRoutes
  )

} 
Example 8
Source File: AdminBrandRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes.admin

import cats._
import io.circe.refined._
import org.http4s._
import org.http4s.circe.JsonDecoder
import org.http4s.dsl.Http4sDsl
import org.http4s.server._
import shop.algebras.Brands
import shop.domain.brand._
import shop.http.auth.users.AdminUser
import shop.effects._
import shop.http.decoder._
import shop.http.json._

final class AdminBrandRoutes[F[_]: Defer: JsonDecoder: MonadThrow](
    brands: Brands[F]
) extends Http4sDsl[F] {

  private[admin] val prefixPath = "/brands"

  private val httpRoutes: AuthedRoutes[AdminUser, F] =
    AuthedRoutes.of {
      case ar @ POST -> Root as _ =>
        ar.req.decodeR[BrandParam] { bp =>
          Created(brands.create(bp.toDomain))
        }
    }

  def routes(authMiddleware: AuthMiddleware[F, AdminUser]): HttpRoutes[F] = Router(
    prefixPath -> authMiddleware(httpRoutes)
  )

} 
Example 9
Source File: AdminCategoryRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes.admin

import cats._
import io.circe.refined._
import org.http4s._
import org.http4s.circe.JsonDecoder
import org.http4s.dsl.Http4sDsl
import org.http4s.server._
import shop.algebras.Categories
import shop.domain.category._
import shop.effects._
import shop.http.auth.users.AdminUser
import shop.http.decoder._
import shop.http.json._

final class AdminCategoryRoutes[F[_]: Defer: JsonDecoder: MonadThrow](
    categories: Categories[F]
) extends Http4sDsl[F] {

  private[admin] val prefixPath = "/categories"

  private val httpRoutes: AuthedRoutes[AdminUser, F] =
    AuthedRoutes.of {
      case ar @ POST -> Root as _ =>
        ar.req.decodeR[CategoryParam] { c =>
          Created(categories.create(c.toDomain))
        }
    }

  def routes(authMiddleware: AuthMiddleware[F, AdminUser]): HttpRoutes[F] = Router(
    prefixPath -> authMiddleware(httpRoutes)
  )

} 
Example 10
Source File: AdminItemRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes.admin

import cats._
import org.http4s._
import org.http4s.circe.JsonDecoder
import org.http4s.dsl.Http4sDsl
import org.http4s.server._
import shop.algebras.Items
import shop.domain.item._
import shop.effects._
import shop.http.auth.users.AdminUser
import shop.http.decoder._
import shop.http.json._

final class AdminItemRoutes[F[_]: Defer: JsonDecoder: MonadThrow](
    items: Items[F]
) extends Http4sDsl[F] {

  private[admin] val prefixPath = "/items"

  private val httpRoutes: AuthedRoutes[AdminUser, F] =
    AuthedRoutes.of {
      // Create new item
      case ar @ POST -> Root as _ =>
        ar.req.decodeR[CreateItemParam] { item =>
          Created(items.create(item.toDomain))
        }

      // Update price of item
      case ar @ PUT -> Root as _ =>
        ar.req.decodeR[UpdateItemParam] { item =>
          Ok(items.update(item.toDomain))
        }
    }

  def routes(authMiddleware: AuthMiddleware[F, AdminUser]): HttpRoutes[F] = Router(
    prefixPath -> authMiddleware(httpRoutes)
  )

} 
Example 11
Source File: LogoutRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes

import cats._
import cats.implicits._
import dev.profunktor.auth.AuthHeaders
import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.server._
import shop.algebras.Auth
import shop.http.auth.users._

final class LogoutRoutes[F[_]: Defer: Monad](
    auth: Auth[F]
) extends Http4sDsl[F] {

  private[routes] val prefixPath = "/auth"

  private val httpRoutes: AuthedRoutes[CommonUser, F] = AuthedRoutes.of {

    case ar @ POST -> Root / "logout" as user =>
      AuthHeaders
        .getBearerToken(ar.req)
        .traverse_(t => auth.logout(t, user.value.name)) *> NoContent()

  }

  def routes(authMiddleware: AuthMiddleware[F, CommonUser]): HttpRoutes[F] = Router(
    prefixPath -> authMiddleware(httpRoutes)
  )

} 
Example 12
Source File: AuthExampleApp.scala    From caliban   with Apache License 2.0 5 votes vote down vote up
package caliban.http4s

import caliban.GraphQL._
import caliban.schema.GenericSchema
import caliban.{ Http4sAdapter, RootResolver }
import org.http4s.HttpRoutes
import org.http4s.dsl.Http4sDsl
import org.http4s.implicits._
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.server.{ Router, ServiceErrorHandler }
import org.http4s.util.CaseInsensitiveString
import zio._
import zio.interop.catz._
import zio.interop.catz.implicits._

import scala.concurrent.ExecutionContext

object AuthExampleApp extends CatsApp {

  // Simple service that returns the token coming from the request
  type Auth = Has[Auth.Service]
  object Auth {
    trait Service {
      def token: String
    }
  }
  type AuthTask[A] = RIO[Auth, A]

  case class MissingToken() extends Throwable

  // http4s middleware that extracts a token from the request and eliminate the Auth layer dependency
  object AuthMiddleware {
    def apply(route: HttpRoutes[AuthTask]): HttpRoutes[Task] =
      Http4sAdapter.provideLayerFromRequest(
        route,
        _.headers.get(CaseInsensitiveString("token")) match {
          case Some(value) => ZLayer.succeed(new Auth.Service { override def token: String = value.value })
          case None        => ZLayer.fail(MissingToken())
        }
      )
  }

  // http4s error handler to customize the response for our throwable
  object dsl extends Http4sDsl[Task]
  import dsl._
  val errorHandler: ServiceErrorHandler[Task] = _ => { case MissingToken() => Forbidden() }

  // our GraphQL API
  val schema: GenericSchema[Auth] = new GenericSchema[Auth] {}
  import schema._
  case class Query(token: RIO[Auth, String])
  private val resolver = RootResolver(Query(ZIO.access[Auth](_.get[Auth.Service].token)))
  private val api      = graphQL(resolver)

  override def run(args: List[String]): ZIO[ZEnv, Nothing, ExitCode] =
    (for {
      interpreter <- api.interpreter
      route       = AuthMiddleware(Http4sAdapter.makeHttpService(interpreter))
      _ <- BlazeServerBuilder[Task](ExecutionContext.global)
            .withServiceErrorHandler(errorHandler)
            .bindHttp(8088, "localhost")
            .withHttpApp(Router[Task]("/api/graphql" -> route).orNotFound)
            .resource
            .toManaged
            .useForever
    } yield ()).exitCode
} 
Example 13
Source File: AirlinesHttpEndpoint.scala    From core   with Apache License 2.0 5 votes vote down vote up
package com.smartbackpackerapp.http

import cats.Monad
import cats.syntax.flatMap._
import cats.syntax.functor._
import com.smartbackpackerapp.model.AirlineName
import com.smartbackpackerapp.service.AirlineService
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl

class AirlinesHttpEndpoint[F[_] : Monad](airlineService: AirlineService[F])
                                        (implicit handler: HttpErrorHandler[F]) extends Http4sDsl[F] {

  object AirlineNameQueryParamMatcher extends QueryParamDecoderMatcher[String]("name")

  val service: AuthedService[String, F] = AuthedService {
    case GET -> Root / ApiVersion / "airlines" :? AirlineNameQueryParamMatcher(airline) as _ =>
      for {
        policy    <- airlineService.baggagePolicy(AirlineName(airline))
        response  <- policy.fold(handler.handle, x => Ok(x.asJson))
      } yield response
  }

} 
Example 14
Source File: HealthInfoHttpEndpoint.scala    From core   with Apache License 2.0 5 votes vote down vote up
package com.smartbackpackerapp.http

import cats.Monad
import cats.syntax.flatMap._
import cats.syntax.functor._
import com.smartbackpackerapp.model.CountryCode
import com.smartbackpackerapp.service.HealthService
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl

class HealthInfoHttpEndpoint[F[_] : Monad](healthService: HealthService[F])
                                          (implicit handler: HttpErrorHandler[F]) extends Http4sDsl[F] {

  val service: AuthedService[String, F] = AuthedService {
    case GET -> Root / ApiVersion / "health" / countryCode as _ =>
      for {
        healthInfo  <- healthService.findHealthInfo(CountryCode(countryCode))
        response    <- healthInfo.fold(handler.handle, x => Ok(x.asJson))
      } yield response
  }

} 
Example 15
Source File: VisaRestrictionIndexHttpEndpoint.scala    From core   with Apache License 2.0 5 votes vote down vote up
package com.smartbackpackerapp.http

import cats.Monad
import cats.syntax.flatMap._
import cats.syntax.functor._
import com.smartbackpackerapp.model.CountryCode
import com.smartbackpackerapp.service.VisaRestrictionIndexService
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl

class VisaRestrictionIndexHttpEndpoint[F[_] : Monad](visaRestrictionIndexService: VisaRestrictionIndexService[F])
                                                    (implicit handler: HttpErrorHandler[F]) extends Http4sDsl[F] {

  val service: AuthedService[String, F] = AuthedService {
    case GET -> Root / ApiVersion / "ranking" / countryCode as _ =>
      for {
        index     <- visaRestrictionIndexService.findIndex(CountryCode(countryCode))
        response  <- index.fold(handler.handle, x => Ok(x.asJson))
      } yield response
  }

} 
Example 16
Source File: DestinationInfoHttpEndpoint.scala    From core   with Apache License 2.0 5 votes vote down vote up
package com.smartbackpackerapp.http

import cats.Monad
import cats.syntax.flatMap._
import cats.syntax.functor._
import com.smartbackpackerapp.model.{CountryCode, Currency}
import com.smartbackpackerapp.service.DestinationInfoService
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl

class DestinationInfoHttpEndpoint[F[_] : Monad](destinationInfoService: DestinationInfoService[F])
                                               (implicit handler: HttpErrorHandler[F]) extends Http4sDsl[F] {

  object BaseCurrencyQueryParamMatcher extends QueryParamDecoderMatcher[String]("baseCurrency")

  val service: AuthedService[String, F] = AuthedService {
    case GET -> Root / ApiVersion / "traveling" / from / "to" / to :? BaseCurrencyQueryParamMatcher(baseCurrency) as _ =>
      for {
        info      <- destinationInfoService.find(CountryCode(from), CountryCode(to), Currency(baseCurrency))
        response  <- info.fold(handler.handle, x => Ok(x.asJson))
      } yield response
  }

} 
Example 17
Source File: JwtTokenAuthMiddleware.scala    From core   with Apache License 2.0 5 votes vote down vote up
package com.smartbackpackerapp.http.auth

import cats.data.{EitherT, Kleisli, OptionT}
import cats.effect.Sync
import cats.syntax.applicativeError._
import cats.syntax.functor._
import com.smartbackpackerapp.http.auth.JwtTokenAuthMiddleware.AuthConfig
import org.http4s.Credentials.Token
import org.http4s.dsl.Http4sDsl
import org.http4s.{AuthScheme, AuthedService, Request}
import org.http4s.headers.Authorization
import org.http4s.server.AuthMiddleware
import tsec.jws.mac.JWTMac
import tsec.mac.imports._

object JwtTokenAuthMiddleware {
  def apply[F[_] : Sync](apiToken: Option[String]): F[AuthMiddleware[F, String]] =
    new Middleware[F](apiToken).middleware

  case class AuthConfig(jwtKey: MacSigningKey[HMACSHA256])
}

class Middleware[F[_]](apiToken: Option[String])(implicit F: Sync[F]) {

  private val ifEmpty = F.raiseError[AuthMiddleware[F, String]](new Exception("Api Token not found"))

  private def generateJwtKey(token: String): F[MacSigningKey[HMACSHA256]] = {
    F.catchNonFatal(HMACSHA256.buildKeyUnsafe(token.getBytes))
  }

  val middleware: F[AuthMiddleware[F, String]] = apiToken.fold(ifEmpty) { token =>
    generateJwtKey(token).map { jwtKey =>
      val config = AuthConfig(jwtKey)
      new JwtTokenAuthMiddleware[F](config).middleware
    }
  }
}

class JwtTokenAuthMiddleware[F[_] : Sync](config: AuthConfig) extends Http4sDsl[F] {

  private val onFailure: AuthedService[String, F] =
    Kleisli(req => OptionT.liftF(Forbidden(req.authInfo)))

  private def bearerTokenFromRequest(request: Request[F]): OptionT[F, String] =
    OptionT.fromOption[F] {
      request.headers.get(Authorization).collect {
        case Authorization(Token(AuthScheme.Bearer, token)) => token
      }
    }

  private def verifyToken(request: Request[F],
                          jwtKey: MacSigningKey[HMACSHA256]): OptionT[F, String] =
    for {
      token       <- bearerTokenFromRequest(request)
      verified    <- OptionT.liftF(JWTMac.verifyAndParse[F, HMACSHA256](token, jwtKey))
      accessToken <- OptionT.fromOption[F](verified.body.subject)
    } yield accessToken

  private def authUser(jwtKey: MacSigningKey[HMACSHA256]): Kleisli[F, Request[F], Either[String, String]] =
    Kleisli { request =>
      verifyToken(request, jwtKey).value.map { option =>
        Either.cond[String, String](option.isDefined, option.get, "Unable to authorize token")
      }.recoverWith {
        case MacVerificationError(msg) => EitherT.leftT(msg).value
      }
    }

  def middleware: AuthMiddleware[F, String] =
    AuthMiddleware(authUser(config.jwtKey), onFailure)

} 
Example 18
Source File: LoginTest.scala    From scala-pet-store   with Apache License 2.0 5 votes vote down vote up
package io.github.pauljamescleary.petstore
package infrastructure.endpoint

import cats.data.Kleisli
import cats.effect.IO
import domain.authentication.{LoginRequest, SignupRequest}
import domain.users.{Role, User}
import org.http4s.circe.{jsonEncoderOf, jsonOf}
import org.http4s.client.dsl.Http4sClientDsl
import org.http4s.{EntityDecoder, EntityEncoder, HttpApp, Request, Response}
import org.http4s.implicits._
import org.http4s.headers.Authorization
import io.circe.generic.auto._
import org.http4s.dsl.Http4sDsl

trait LoginTest extends Http4sClientDsl[IO] with Http4sDsl[IO] {
  implicit val userEnc: EntityEncoder[IO, User] = jsonEncoderOf
  implicit val userDec: EntityDecoder[IO, User] = jsonOf

  implicit val signUpRequestEnc: EntityEncoder[IO, SignupRequest] = jsonEncoderOf
  implicit val signUpRequestDec: EntityDecoder[IO, SignupRequest] = jsonOf

  implicit val loginRequestEnc: EntityEncoder[IO, LoginRequest] = jsonEncoderOf
  implicit val loginRequestDec: EntityDecoder[IO, LoginRequest] = jsonOf

  def signUpAndLogIn(
      userSignUp: SignupRequest,
      userEndpoint: HttpApp[IO],
  ): IO[(User, Option[Authorization])] =
    for {
      signUpRq <- POST(userSignUp, uri"/users")
      signUpResp <- userEndpoint.run(signUpRq)
      user <- signUpResp.as[User]
      loginBody = LoginRequest(userSignUp.userName, userSignUp.password)
      loginRq <- POST(loginBody, uri"/users/login")
      loginResp <- userEndpoint.run(loginRq)
    } yield {
      user -> loginResp.headers.get(Authorization)
    }

  def signUpAndLogInAsAdmin(
      userSignUp: SignupRequest,
      userEndpoint: Kleisli[IO, Request[IO], Response[IO]],
  ): IO[(User, Option[Authorization])] =
    signUpAndLogIn(userSignUp.copy(role = Role.Admin), userEndpoint)

  def signUpAndLogInAsCustomer(
      userSignUp: SignupRequest,
      userEndpoint: Kleisli[IO, Request[IO], Response[IO]],
  ): IO[(User, Option[Authorization])] =
    signUpAndLogIn(userSignUp.copy(role = Role.Customer), userEndpoint)
} 
Example 19
Source File: OrderEndpoints.scala    From scala-pet-store   with Apache License 2.0 5 votes vote down vote up
package io.github.pauljamescleary.petstore
package infrastructure.endpoint

import cats.effect.Sync
import cats.implicits._
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl

import domain.OrderNotFoundError
import domain.authentication.Auth
import domain.orders.{Order, OrderService}
import io.github.pauljamescleary.petstore.domain.users.User
import tsec.authentication.{AugmentedJWT, SecuredRequestHandler, asAuthed}
import tsec.jwt.algorithms.JWTMacAlgo

class OrderEndpoints[F[_]: Sync, Auth: JWTMacAlgo] extends Http4sDsl[F] {
  
  implicit val orderDecoder: EntityDecoder[F, Order] = jsonOf

  private def placeOrderEndpoint(orderService: OrderService[F]): AuthEndpoint[F, Auth] = {
    case req @ POST -> Root asAuthed user =>
      for {
        order <- req.request
          .as[Order]
          .map(_.copy(userId = user.id))
        saved <- orderService.placeOrder(order)
        resp <- Ok(saved.asJson)
      } yield resp
  }

  private def getOrderEndpoint(orderService: OrderService[F]): AuthEndpoint[F, Auth] = {
    case GET -> Root / LongVar(id) asAuthed _ =>
      orderService.get(id).value.flatMap {
        case Right(found) => Ok(found.asJson)
        case Left(OrderNotFoundError) => NotFound("The order was not found")
      }
  }

  private def deleteOrderEndpoint(orderService: OrderService[F]): AuthEndpoint[F, Auth] = {
    case DELETE -> Root / LongVar(id) asAuthed _ =>
      for {
        _ <- orderService.delete(id)
        resp <- Ok()
      } yield resp
  }

  def endpoints(
      orderService: OrderService[F],
      auth: SecuredRequestHandler[F, Long, User, AugmentedJWT[Auth, Long]],
  ): HttpRoutes[F] = {
    val authEndpoints: AuthService[F, Auth] =
      Auth.allRolesHandler(placeOrderEndpoint(orderService).orElse(getOrderEndpoint(orderService))) {
        Auth.adminOnly(deleteOrderEndpoint(orderService))
      }

    auth.liftService(authEndpoints)
  }
}

object OrderEndpoints {
  def endpoints[F[_]: Sync, Auth: JWTMacAlgo](
      orderService: OrderService[F],
      auth: SecuredRequestHandler[F, Long, User, AugmentedJWT[Auth, Long]],
  ): HttpRoutes[F] =
    new OrderEndpoints[F, Auth].endpoints(orderService, auth)
} 
Example 20
Source File: HealthCheckRoutes.scala    From http4s-poc-api   with MIT License 5 votes vote down vote up
package server

import cats.effect.Sync
import log.effect.LogWriter
import model.DomainModel._
import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityEncoder, HttpRoutes, Method}
import cats.syntax.flatMap._

sealed abstract class HealthCheckRoutes[F[_]: Sync](
  implicit responseEncoder: EntityEncoder[F, ServiceSignature]
) extends Http4sDsl[F] {
  def make(log: LogWriter[F]): HttpRoutes[F] =
    HttpRoutes.of[F] {
      case Method.GET -> Root => log.debug(s"Serving HealthCheck request") >> Ok(serviceSignature)
    }

  private val serviceSignature =
    ServiceSignature(
      name = BuildInfo.name,
      version = BuildInfo.version,
      scalaVersion = BuildInfo.scalaVersion,
      scalaOrganization = BuildInfo.scalaOrganization,
      buildTime = BuildInfo.buildTime
    )
}

object HealthCheckRoutes {
  def apply[F[_]: Sync: EntityEncoder[*[_], ServiceSignature]]: HealthCheckRoutes[F] =
    new HealthCheckRoutes[F] {}
} 
Example 21
Source File: CorrelationIdMiddlewareTest.scala    From scala-server-toolkit   with MIT License 5 votes vote down vote up
package com.avast.sst.http4s.server.middleware

import java.net.InetSocketAddress

import cats.effect.{ContextShift, IO, Resource, Timer}
import com.avast.sst.http4s.server.Http4sRouting
import org.http4s.client.blaze.BlazeClientBuilder
import org.http4s.dsl.Http4sDsl
import org.http4s.server.blaze.BlazeServerBuilder
import org.http4s.util.CaseInsensitiveString
import org.http4s.{Header, HttpRoutes, Request, Uri}
import org.scalatest.funsuite.AsyncFunSuite

import scala.concurrent.ExecutionContext

@SuppressWarnings(Array("scalafix:Disable.get", "scalafix:Disable.toString", "scalafix:Disable.createUnresolved"))
class CorrelationIdMiddlewareTest extends AsyncFunSuite with Http4sDsl[IO] {

  implicit private val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  implicit private val timer: Timer[IO] = IO.timer(ExecutionContext.global)

  test("CorrelationIdMiddleware fills Request attributes and HTTP response header") {
    val test = for {
      middleware <- Resource.liftF(CorrelationIdMiddleware.default[IO])
      routes = Http4sRouting.make {
        middleware.wrap {
          HttpRoutes.of[IO] {
            case req @ GET -> Root / "test" =>
              val id = middleware.retrieveCorrelationId(req)
              Ok("test").map(_.withHeaders(Header("Attribute-Value", id.toString)))
          }
        }
      }
      server <- BlazeServerBuilder[IO](ExecutionContext.global)
        .bindSocketAddress(InetSocketAddress.createUnresolved("127.0.0.1", 0))
        .withHttpApp(routes)
        .resource
      client <- BlazeClientBuilder[IO](ExecutionContext.global).resource
    } yield (server, client)

    test
      .use {
        case (server, client) =>
          client
            .run(
              Request[IO](uri = Uri.unsafeFromString(s"http://${server.address.getHostString}:${server.address.getPort}/test"))
                .withHeaders(Header("Correlation-Id", "test-value"))
            )
            .use { response =>
              IO.delay {
                assert(response.headers.get(CaseInsensitiveString("Correlation-Id")).get.value === "test-value")
                assert(response.headers.get(CaseInsensitiveString("Attribute-Value")).get.value === "Some(CorrelationId(test-value))")
              }
            }
      }
      .unsafeToFuture()
  }

} 
Example 22
Source File: Http4sBlazeServerModuleTest.scala    From scala-server-toolkit   with MIT License 5 votes vote down vote up
package com.avast.sst.http4s.server

import cats.effect.{ContextShift, IO, Timer}
import com.avast.sst.http4s.client.{Http4sBlazeClientConfig, Http4sBlazeClientModule}
import org.http4s.HttpRoutes
import org.http4s.dsl.Http4sDsl
import org.scalatest.funsuite.AsyncFunSuite

import scala.concurrent.ExecutionContext

class Http4sBlazeServerModuleTest extends AsyncFunSuite with Http4sDsl[IO] {

  implicit private val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
  implicit private val timer: Timer[IO] = IO.timer(ExecutionContext.global)

  test("Simple HTTP server") {
    val routes = Http4sRouting.make(HttpRoutes.of[IO] {
      case GET -> Root / "test" => Ok("test")
    })
    val test = for {
      server <- Http4sBlazeServerModule.make[IO](Http4sBlazeServerConfig("127.0.0.1", 0), routes, ExecutionContext.global)
      client <- Http4sBlazeClientModule.make[IO](Http4sBlazeClientConfig(), ExecutionContext.global)
    } yield (server, client)

    test
      .use {
        case (server, client) =>
          client
            .expect[String](s"http://${server.address.getHostString}:${server.address.getPort}/test")
            .map(response => assert(response === "test"))
      }
      .unsafeToFuture()
  }

} 
Example 23
Source File: Http4sRoutingModule.scala    From scala-server-toolkit   with MIT License 5 votes vote down vote up
package com.avast.sst.example.module

import cats.implicits._
import com.avast.sst.example.service.RandomService
import com.avast.sst.http4s.server.Http4sRouting
import com.avast.sst.http4s.server.micrometer.MicrometerHttp4sServerMetricsModule
import org.http4s.client.Client
import org.http4s.dsl.Http4sDsl
import org.http4s.{HttpApp, HttpRoutes}
import zio.Task
import zio.interop.catz._

class Http4sRoutingModule(
    randomService: RandomService,
    client: Client[Task],
    serverMetricsModule: MicrometerHttp4sServerMetricsModule[Task]
) extends Http4sDsl[Task] {

  import serverMetricsModule._

  private val helloWorldRoute = routeMetrics.wrap("hello")(Ok("Hello World!"))

  private val routes = HttpRoutes.of[Task] {
    case GET -> Root / "hello"           => helloWorldRoute
    case GET -> Root / "random"          => randomService.randomNumber.map(_.show).flatMap(Ok(_))
    case GET -> Root / "circuit-breaker" => client.expect[String]("https://httpbin.org/status/500").flatMap(Ok(_))
  }

  val router: HttpApp[Task] = Http4sRouting.make {
    serverMetrics {
      routes
    }
  }

} 
Example 24
Source File: PushEvents.scala    From event-sourcing-kafka-streams   with MIT License 5 votes vote down vote up
package org.amitayh.invoices.web

import java.util.UUID

import cats.effect._
import fs2.concurrent.Topic
import io.circe.generic.auto._
import io.circe.syntax._
import org.amitayh.invoices.common.domain.{CommandResult, InvoiceSnapshot}
import org.amitayh.invoices.dao.InvoiceRecord
import org.amitayh.invoices.web.PushEvents._
import org.http4s.dsl.Http4sDsl
import org.http4s.{HttpRoutes, ServerSentEvent}

class PushEvents[F[_]: Concurrent] extends Http4sDsl[F] {

  private val maxQueued = 16

  def service(commandResultsTopic: Topic[F, CommandResultRecord],
              invoiceUpdatesTopic: Topic[F, InvoiceSnapshotRecord]): HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root / UuidVar(originId) =>
      val commandResults = commandResultsTopic.subscribe(maxQueued).collect {
        case Some((_, result)) if result.originId == originId =>
          Event(result).asServerSentEvent
      }
      val invoiceUpdates = invoiceUpdatesTopic.subscribe(maxQueued).collect {
        case Some((id, snapshot)) => Event(id, snapshot).asServerSentEvent
      }
      Ok(commandResults merge invoiceUpdates)
  }

}

object PushEvents {
  type CommandResultRecord = Option[(UUID, CommandResult)]
  type InvoiceSnapshotRecord = Option[(UUID, InvoiceSnapshot)]

  def apply[F[_]: Concurrent]: PushEvents[F] = new PushEvents[F]
}

sealed trait Event {
  def asServerSentEvent: ServerSentEvent =
    ServerSentEvent(this.asJson.noSpaces)
}

case class CommandSucceeded(commandId: UUID) extends Event
case class CommandFailed(commandId: UUID, cause: String) extends Event
case class InvoiceUpdated(record: InvoiceRecord) extends Event

object Event {
  def apply(result: CommandResult): Event = result match {
    case CommandResult(_, commandId, _: CommandResult.Success) =>
      CommandSucceeded(commandId)

    case CommandResult(_, commandId, CommandResult.Failure(cause)) =>
      CommandFailed(commandId, cause.message)
  }

  def apply(id: UUID, snapshot: InvoiceSnapshot): Event =
    InvoiceUpdated(InvoiceRecord(id, snapshot))
} 
Example 25
Source File: Statics.scala    From event-sourcing-kafka-streams   with MIT License 5 votes vote down vote up
package org.amitayh.invoices.web

import cats.effect.{ContextShift, Sync}
import org.http4s.dsl.Http4sDsl
import org.http4s.{HttpRoutes, StaticFile}

import scala.concurrent.ExecutionContext.global

class Statics[F[_]: Sync: ContextShift] extends Http4sDsl[F] {

  val service: HttpRoutes[F] = HttpRoutes.of[F] {
    case request @ GET -> fileName =>
      StaticFile
        .fromResource(
          name = s"/statics$fileName",
          blockingExecutionContext = global,
          req = Some(request),
          preferGzipped = true)
        .getOrElseF(NotFound())
  }

}

object Statics {
  def apply[F[_]: Sync: ContextShift]: Statics[F] = new Statics[F]
} 
Example 26
Source File: InvoicesApi.scala    From event-sourcing-kafka-streams   with MIT License 5 votes vote down vote up
package org.amitayh.invoices.web

import java.util.UUID

import cats.effect.{Concurrent, Timer}
import cats.implicits._
import fs2.Stream
import fs2.concurrent.Topic
import io.circe._
import io.circe.generic.auto._
import io.circe.syntax._
import org.amitayh.invoices.common.domain.CommandResult.{Failure, Success}
import org.amitayh.invoices.common.domain.{Command, CommandResult}
import org.amitayh.invoices.dao.InvoiceList
import org.amitayh.invoices.web.CommandDto._
import org.amitayh.invoices.web.PushEvents.CommandResultRecord
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityDecoder, HttpRoutes, Response}

import scala.concurrent.duration._

class InvoicesApi[F[_]: Concurrent: Timer] extends Http4sDsl[F] {

  private val maxQueued = 16

  implicit val commandEntityDecoder: EntityDecoder[F, Command] = jsonOf[F, Command]

  def service(invoiceList: InvoiceList[F],
              producer: Kafka.Producer[F, UUID, Command],
              commandResultsTopic: Topic[F, CommandResultRecord]): HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root / "invoices" =>
      invoiceList.get.flatMap(invoices => Ok(invoices.asJson))

    case request @ POST -> Root / "execute" / "async" / UuidVar(invoiceId) =>
      request
        .as[Command]
        .flatMap(producer.send(invoiceId, _))
        .flatMap(metaData => Accepted(Json.fromLong(metaData.timestamp)))

    case request @ POST -> Root / "execute" / UuidVar(invoiceId) =>
      request.as[Command].flatMap { command =>
        val response = resultStream(commandResultsTopic, command.commandId) merge timeoutStream
        producer.send(invoiceId, command) *> response.head.compile.toList.map(_.head)
      }
  }

  private def resultStream(commandResultsTopic: Topic[F, CommandResultRecord],
                           commandId: UUID): Stream[F, Response[F]] =
    commandResultsTopic.subscribe(maxQueued).collectFirst {
      case Some((_, CommandResult(_, `commandId`, outcome))) => outcome
    }.flatMap {
      case Success(_, _, snapshot) => Stream.eval(Ok(snapshot.asJson))
      case Failure(cause) => Stream.eval(UnprocessableEntity(cause.message))
    }

  private def timeoutStream: Stream[F, Response[F]] =
    Stream.eval(Timer[F].sleep(5.seconds) *> RequestTimeout("timeout"))

}

object InvoicesApi {
  def apply[F[_]: Concurrent: Timer]: InvoicesApi[F] = new InvoicesApi[F]
} 
Example 27
Source File: SwaggerHttp4s.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.swagger.http4s

import java.util.Properties

import cats.effect.{Blocker, ContextShift, Sync}
import org.http4s.{HttpRoutes, StaticFile, Uri}
import org.http4s.dsl.Http4sDsl
import org.http4s.headers.Location

import scala.concurrent.ExecutionContext


class SwaggerHttp4s(
    yaml: String,
    contextPath: String = "docs",
    yamlName: String = "docs.yaml",
    redirectQuery: Map[String, Seq[String]] = Map.empty
) {
  private val swaggerVersion = {
    val p = new Properties()
    val pomProperties = getClass.getResourceAsStream("/META-INF/maven/org.webjars/swagger-ui/pom.properties")
    try p.load(pomProperties)
    finally pomProperties.close()
    p.getProperty("version")
  }

  def routes[F[_]: ContextShift: Sync]: HttpRoutes[F] = {
    val dsl = Http4sDsl[F]
    import dsl._

    HttpRoutes.of[F] {
      case path @ GET -> Root / `contextPath` =>
        val queryParameters = Map("url" -> Seq(s"${path.uri}/$yamlName")) ++ redirectQuery
        Uri
          .fromString(s"${path.uri}/index.html")
          .map(uri => uri.setQueryParams(queryParameters))
          .map(uri => PermanentRedirect(Location(uri)))
          .getOrElse(NotFound())
      case GET -> Root / `contextPath` / `yamlName` =>
        Ok(yaml)
      case GET -> Root / `contextPath` / swaggerResource =>
        StaticFile
          .fromResource(
            s"/META-INF/resources/webjars/swagger-ui/$swaggerVersion/$swaggerResource",
            Blocker.liftExecutionContext(ExecutionContext.global)
          )
          .getOrElseF(NotFound())
    }
  }
} 
Example 28
Source File: RedocHttp4s.scala    From tapir   with Apache License 2.0 5 votes vote down vote up
package sttp.tapir.redoc.http4s

import cats.effect.{ContextShift, Sync}
import org.http4s.dsl.Http4sDsl
import org.http4s.headers._
import org.http4s.{Charset, HttpRoutes, MediaType}

import scala.io.Source


class RedocHttp4s(title: String, yaml: String, yamlName: String = "docs.yaml") {
  private lazy val html = {
    val fileName = "redoc.html"
    val is = getClass.getClassLoader.getResourceAsStream(fileName)
    assert(Option(is).nonEmpty, s"Could not find file ${fileName} on classpath.")
    val rawHtml = Source.fromInputStream(is).mkString
    // very poor man's templating engine
    rawHtml.replaceAllLiterally("{{docsPath}}", yamlName).replaceAllLiterally("{{title}}", title)
  }

  def routes[F[_]: ContextShift: Sync]: HttpRoutes[F] = {
    val dsl = Http4sDsl[F]
    import dsl._

    HttpRoutes.of[F] {
      case req @ GET -> Root if req.pathInfo.endsWith("/") =>
        Ok(html, `Content-Type`(MediaType.text.html, Charset.`UTF-8`))
      // as the url to the yaml file is relative, it is important that there is a trailing slash
      case req @ GET -> Root =>
        val uri = req.uri
        PermanentRedirect(Location(uri.withPath(uri.path.concat("/"))))
      case GET -> Root / `yamlName` =>
        Ok(yaml, `Content-Type`(MediaType.text.yaml, Charset.`UTF-8`))
    }
  }
} 
Example 29
Source File: PriceRoutes.scala    From http4s-poc-api   with MIT License 5 votes vote down vote up
package server

import cats.effect.Sync
import cats.syntax.applicativeError._
import cats.syntax.flatMap._
import cats.syntax.functor._
import cats.syntax.show._
import errors.PriceServiceError
import errors.PriceServiceError._
import external.library.syntax.response._
import model.DomainModel._
import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityDecoder, EntityEncoder, HttpRoutes, Method, Request, Response}
import service.PriceService

sealed abstract class PriceRoutes[F[_]: Sync](
  implicit requestDecoder: EntityDecoder[F, PricesRequestPayload],
  responseEncoder: EntityEncoder[F, List[Price]]
) extends Http4sDsl[F] {
  def make(priceService: PriceService[F]): HttpRoutes[F] =
    HttpRoutes.of[F] {
      case req @ Method.POST -> Root =>
        postResponse(req, priceService) handlingFailures priceServiceErrors handleErrorWith unhandledThrowable
    }

  private[this] def postResponse(request: Request[F], priceService: PriceService[F]): F[Response[F]] =
    for {
      payload <- request.as[PricesRequestPayload]
      prices  <- priceService.prices(payload.userId, payload.productIds)
      resp    <- Ok(prices)
    } yield resp

  private[this] def priceServiceErrors: PriceServiceError => F[Response[F]] = {
    case UserErr(r)                => FailedDependency(r)
    case PreferenceErr(r)          => FailedDependency(r)
    case ProductErr(r)             => FailedDependency(r)
    case ProductPriceErr(r)        => FailedDependency(r)
    case CacheLookupError(r)       => FailedDependency(r)
    case CacheStoreError(r)        => FailedDependency(r)
    case InvalidShippingCountry(r) => BadRequest(r)
  }

  private[this] def unhandledThrowable: Throwable => F[Response[F]] = { th =>
    import external.library.instances.throwable._
    InternalServerError(th.show)
  }
}

object PriceRoutes {
  def apply[
    F[_]: Sync: EntityDecoder[*[_], PricesRequestPayload]: EntityEncoder[*[_], List[Price]]
  ]: PriceRoutes[F] =
    new PriceRoutes[F] {}
} 
Example 30
Source File: SagaEndpoint.scala    From zio-saga   with MIT License 5 votes vote down vote up
package com.vladkopanev.zio.saga.example.endpoint

import com.vladkopanev.zio.saga.example.{ OrderSagaCoordinator, TaskC }
import com.vladkopanev.zio.saga.example.model.OrderInfo
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl
import org.http4s.implicits._
import org.http4s.{ HttpApp, HttpRoutes }
import zio.interop.catz._

final class SagaEndpoint(orderSagaCoordinator: OrderSagaCoordinator) extends Http4sDsl[TaskC] {

  private implicit val decoder = jsonOf[TaskC, OrderInfo]

  val service: HttpApp[TaskC] = HttpRoutes
    .of[TaskC] {
      case req @ POST -> Root / "saga" / "finishOrder" =>
        for {
          OrderInfo(userId, orderId, money, bonuses) <- req.as[OrderInfo]
          resp <- orderSagaCoordinator
                   .runSaga(userId, orderId, money, bonuses, None)
                   .foldM(fail => InternalServerError(fail.getMessage), _ => Ok("Saga submitted"))
        } yield resp
    }
    .orNotFound
} 
Example 31
Source File: BidderHttpAppBuilder.scala    From scala-openrtb   with Apache License 2.0 5 votes vote down vote up
package com.powerspace.openrtb.examples.rtb.http4s.bidder

import com.google.openrtb.BidRequest
import com.powerspace.openrtb.examples.rtb.http4s.common.ExampleSerdeModule
import io.circe.Decoder
import monix.eval.Task
import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityDecoder, HttpApp}

object BidderHttpAppBuilder {

  private val bidder = new Bidder[Task]
  private val dsl = Http4sDsl[Task]
  private val serdeModule = ExampleSerdeModule

  
  private def handleBid(bidRequest: BidRequest) = {
    import dsl._
    import org.http4s.circe._

    bidder
      .bidOn(bidRequest)
      .flatMap {
        case Some(bidResponse) =>
          // encode the bidResponse to a json object as part of the http response body
          Ok(serdeModule.bidResponseEncoder(bidResponse))
        case None =>
          Ok()
      }
  }
} 
Example 32
Source File: StatusesService.scala    From zio-telemetry   with Apache License 2.0 5 votes vote down vote up
package zio.telemetry.opentelemetry.example.http

import io.circe.Encoder
import io.opentelemetry.OpenTelemetry
import io.opentelemetry.context.propagation.HttpTextFormat.Setter
import io.opentelemetry.trace.{ Span, Status => TraceStatus }
import org.http4s.circe.jsonEncoderOf
import org.http4s.dsl.Http4sDsl
import org.http4s.{ EntityEncoder, HttpRoutes }
import zio.UIO
import zio.interop.catz._
import zio.telemetry.opentelemetry.Tracing.root
import zio.telemetry.opentelemetry.attributevalue.AttributeValueConverterInstances._
import zio.telemetry.opentelemetry.Tracing

import scala.collection.mutable

object StatusesService {

  val dsl: Http4sDsl[AppTask] = Http4sDsl[AppTask]
  import dsl._

  implicit def encoder[A: Encoder]: EntityEncoder[AppTask, A] = jsonEncoderOf[AppTask, A]

  val httpTextFormat                              = OpenTelemetry.getPropagators.getHttpTextFormat
  val setter: Setter[mutable.Map[String, String]] = (carrier, key, value) => carrier.update(key, value)

  val errorMapper: PartialFunction[Throwable, TraceStatus] = { case _ => TraceStatus.UNKNOWN }

  val routes: HttpRoutes[AppTask] = HttpRoutes.of[AppTask] {
    case GET -> Root / "statuses" =>
      root("/statuses", Span.Kind.SERVER, errorMapper) {
        for {
          carrier <- UIO(mutable.Map[String, String]().empty)
          _       <- Tracing.setAttribute("http.method", "get")
          _       <- Tracing.addEvent("proxy-event")
          _       <- Tracing.inject(httpTextFormat, carrier, setter)
          res     <- Client.status(carrier.toMap).flatMap(Ok(_))
        } yield res
      }
  }

} 
Example 33
Source File: StatusService.scala    From zio-telemetry   with Apache License 2.0 5 votes vote down vote up
package zio.telemetry.opentelemetry.example.http

import io.circe.Encoder
import io.circe.syntax._
import io.opentelemetry.OpenTelemetry
import io.opentelemetry.context.propagation.HttpTextFormat
import io.opentelemetry.context.propagation.HttpTextFormat.Getter
import io.opentelemetry.trace.Span
import org.http4s._
import org.http4s.circe.jsonEncoderOf
import org.http4s.dsl.Http4sDsl
import org.http4s.util.CaseInsensitiveString
import zio.interop.catz._
import zio.telemetry.opentelemetry.Tracing
import zio.telemetry.opentelemetry.TracingSyntax._
import zio.telemetry.opentelemetry.example.http.{ Status => ServiceStatus }

object StatusService {

  val dsl: Http4sDsl[AppTask] = Http4sDsl[AppTask]
  import dsl._

  implicit def encoder[A: Encoder]: EntityEncoder[AppTask, A] = jsonEncoderOf[AppTask, A]

  val httpTextFormat: HttpTextFormat = OpenTelemetry.getPropagators.getHttpTextFormat
  val getter: Getter[Headers]        = (carrier, key) => carrier.get(CaseInsensitiveString(key)).map(_.value).orNull

  val routes: HttpRoutes[AppTask] = HttpRoutes.of[AppTask] {
    case request @ GET -> Root / "status" =>
      val response = for {
        _        <- Tracing.addEvent("event from backend before response")
        response <- Ok(ServiceStatus.up("backend").asJson)
        _        <- Tracing.addEvent("event from backend after response")
      } yield response

      response.spanFrom(httpTextFormat, request.headers, getter, "/status", Span.Kind.SERVER)

  }

} 
Example 34
Source File: StatusesService.scala    From zio-telemetry   with Apache License 2.0 5 votes vote down vote up
package zio.telemetry.opentracing.example.http

import io.circe.Encoder
import io.opentracing.propagation.Format.Builtin.{ HTTP_HEADERS => HttpHeadersFormat }
import io.opentracing.propagation.TextMapAdapter
import io.opentracing.tag.Tags
import org.http4s.circe.jsonEncoderOf
import org.http4s.dsl.Http4sDsl
import org.http4s.{ EntityEncoder, HttpRoutes }
import sttp.model.Uri
import zio.clock.Clock
import zio.interop.catz._
import zio.telemetry.opentracing.OpenTracing
import zio.UIO
import zio.ZIO
import zio.ZLayer

import scala.collection.mutable
import scala.jdk.CollectionConverters._

object StatusesService {

  def statuses(backendUri: Uri, service: ZLayer[Clock, Throwable, Clock with OpenTracing]): HttpRoutes[AppTask] = {
    val dsl: Http4sDsl[AppTask] = Http4sDsl[AppTask]
    import dsl._

    implicit def encoder[A: Encoder]: EntityEncoder[AppTask, A] = jsonEncoderOf[AppTask, A]

    HttpRoutes.of[AppTask] {
      case GET -> Root / "statuses" =>
        val zio =
          for {
            env     <- ZIO.environment[OpenTracing]
            _       <- env.get.root("/statuses")
            _       <- OpenTracing.tag(Tags.SPAN_KIND.getKey, Tags.SPAN_KIND_CLIENT)
            _       <- OpenTracing.tag(Tags.HTTP_METHOD.getKey, GET.name)
            _       <- OpenTracing.setBaggageItem("proxy-baggage-item-key", "proxy-baggage-item-value")
            buffer  <- UIO.succeed(new TextMapAdapter(mutable.Map.empty[String, String].asJava))
            _       <- OpenTracing.inject(HttpHeadersFormat, buffer)
            headers <- extractHeaders(buffer)
            up      = Status.up("proxy")
            res <- Client
                    .status(backendUri.path("status"), headers)
                    .map(_.body)
                    .flatMap {
                      case Right(s) => Ok(Statuses(List(s, up)))
                      case _        => Ok(Statuses(List(Status.down("backend"), up)))
                    }
          } yield res

        zio.provideLayer(service)
    }
  }

  private def extractHeaders(adapter: TextMapAdapter): UIO[Map[String, String]] = {
    val m = mutable.Map.empty[String, String]
    UIO(adapter.forEach { entry =>
      m.put(entry.getKey, entry.getValue)
      ()
    }).as(m.toMap)
  }

} 
Example 35
Source File: StatusService.scala    From zio-telemetry   with Apache License 2.0 5 votes vote down vote up
package zio.telemetry.opentracing.example.http

import io.circe.Encoder
import io.circe.syntax._
import io.opentracing.propagation.Format.Builtin.{ HTTP_HEADERS => HttpHeadersFormat }
import io.opentracing.propagation.TextMapAdapter
import org.http4s._
import org.http4s.circe.jsonEncoderOf
import org.http4s.dsl.Http4sDsl
import zio.clock.Clock
import zio.interop.catz._
import zio.telemetry.opentracing.example.http.{ Status => ServiceStatus }
import zio.telemetry.opentracing._
import zio.ZIO
import zio.ZLayer

import scala.jdk.CollectionConverters._

object StatusService {

  val dsl: Http4sDsl[AppTask] = Http4sDsl[AppTask]
  import dsl._

  implicit def encoder[A: Encoder]: EntityEncoder[AppTask, A] = jsonEncoderOf[AppTask, A]

  def status(service: ZLayer[Clock, Throwable, Clock with OpenTracing]): HttpRoutes[AppTask] =
    HttpRoutes.of[AppTask] {
      case request @ GET -> Root / "status" =>
        val headers = request.headers.toList.map(h => h.name.value -> h.value).toMap
        ZIO.unit
          .spanFrom(HttpHeadersFormat, new TextMapAdapter(headers.asJava), "/status")
          .provideLayer(service) *> Ok(ServiceStatus.up("backend").asJson)
    }

} 
Example 36
Source File: decoder.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http

import cats.implicits._
import io.circe.Decoder
import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.circe._
import shop.effects._

object decoder {

  implicit class RefinedRequestDecoder[F[_]: JsonDecoder: MonadThrow](req: Request[F]) extends Http4sDsl[F] {

    def decodeR[A: Decoder](f: A => F[Response[F]]): F[Response[F]] =
      req.asJsonDecode[A].attempt.flatMap {
        case Left(e) =>
          Option(e.getCause) match {
            case Some(c) if c.getMessage.startsWith("Predicate") => BadRequest(c.getMessage)
            case _                                               => UnprocessableEntity()
          }
        case Right(a) => f(a)
      }

  }

} 
Example 37
Source File: ItemRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes

import cats._
import org.http4s._
import org.http4s.dsl.Http4sDsl
import org.http4s.server.Router
import shop.algebras.Items
import shop.domain.brand._
import shop.http.json._
import shop.http.params._

final class ItemRoutes[F[_]: Defer: Monad](
    items: Items[F]
) extends Http4sDsl[F] {

  private[routes] val prefixPath = "/items"

  object BrandQueryParam extends OptionalQueryParamDecoderMatcher[BrandParam]("brand")

  private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] {

    case GET -> Root :? BrandQueryParam(brand) =>
      Ok(brand.fold(items.findAll)(b => items.findBy(b.toDomain)))

  }

  val routes: HttpRoutes[F] = Router(
    prefixPath -> httpRoutes
  )

} 
Example 38
Source File: LoginRoutes.scala    From pfps-shopping-cart   with Apache License 2.0 5 votes vote down vote up
package shop.http.routes

import cats._
import cats.implicits._
import org.http4s._
import org.http4s.circe.JsonDecoder
import org.http4s.dsl.Http4sDsl
import org.http4s.server.Router
import shop.algebras.Auth
import shop.domain.auth._
import shop.effects._
import shop.http.decoder._
import shop.http.json._

final class LoginRoutes[F[_]: Defer: JsonDecoder: MonadThrow](
    auth: Auth[F]
) extends Http4sDsl[F] {

  private[routes] val prefixPath = "/auth"

  private val httpRoutes: HttpRoutes[F] = HttpRoutes.of[F] {

    case req @ POST -> Root / "login" =>
      req.decodeR[LoginUser] { user =>
        auth
          .login(user.username.toDomain, user.password.toDomain)
          .flatMap(Ok(_))
          .recoverWith {
            case InvalidUserOrPassword(_) => Forbidden()
          }
      }

  }

  val routes: HttpRoutes[F] = Router(
    prefixPath -> httpRoutes
  )

} 
Example 39
Source File: GenericApi.scala    From freestyle   with Apache License 2.0 5 votes vote down vote up
package examples.todolist
package http

import cats.effect.Effect
import cats.implicits._
import examples.todolist.model.Pong
import freestyle.tagless.logging.LoggingM
import io.circe.Json
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl
import org.http4s.HttpService

class GenericApi[F[_]: Effect](implicit log: LoggingM[F]) extends Http4sDsl[F] {
  val endpoints =
    HttpService[F] {
      case GET -> Root / "ping" =>
        for {
          _        <- log.error("Not really an error")
          _        <- log.warn("Not really a warn")
          _        <- log.debug("GET /ping")
          response <- Ok(Json.fromLong(Pong.current.time))
        } yield response

      case GET -> Root / "hello" =>
        for {
          _        <- log.error("Not really an error")
          _        <- log.warn("Not really a warn")
          _        <- log.debug("GET /Hello")
          response <- Ok("Hello World")
        } yield response
    }
}

object GenericApi {
  implicit def instance[F[_]: Effect](implicit log: LoggingM[F]): GenericApi[F] =
    new GenericApi[F]
} 
Example 40
Source File: UserRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.ResponseGenerator

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object UserRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case req @ POST -> Root / "changePassword" =>
        for {
          data <- req.as[PasswordChange]
          res <- backend.collective.changePassword(
            user.account,
            data.currentPassword,
            data.newPassword
          )
          resp <- Ok(basicResult(res))
        } yield resp

      case GET -> Root =>
        for {
          all <- backend.collective.listUser(user.account.collective)
          res <- Ok(UserList(all.map(mkUser).toList))
        } yield res

      case req @ POST -> Root =>
        for {
          data  <- req.as[User]
          nuser <- newUser(data, user.account.collective)
          added <- backend.collective.add(nuser)
          resp  <- Ok(basicResult(added, "User created."))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data <- req.as[User]
          nuser = changeUser(data, user.account.collective)
          update <- backend.collective.update(nuser)
          resp   <- Ok(basicResult(update, "User updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          ar   <- backend.collective.deleteUser(id, user.account.collective)
          resp <- Ok(basicResult(ar, "User deleted."))
        } yield resp
    }
  }

} 
Example 41
Source File: EquipmentRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.QueryParam

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object EquipmentRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root :? QueryParam.QueryOpt(q) =>
        for {
          data <- backend.equipment.findAll(user.account, q.map(_.q))
          resp <- Ok(EquipmentList(data.map(mkEquipment).toList))
        } yield resp

      case req @ POST -> Root =>
        for {
          data  <- req.as[Equipment]
          equip <- newEquipment(data, user.account.collective)
          res   <- backend.equipment.add(equip)
          resp  <- Ok(basicResult(res, "Equipment created"))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data <- req.as[Equipment]
          equip = changeEquipment(data, user.account.collective)
          res  <- backend.equipment.update(equip)
          resp <- Ok(basicResult(res, "Equipment updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          del  <- backend.equipment.delete(id, user.account.collective)
          resp <- Ok(basicResult(del, "Equipment deleted."))
        } yield resp
    }
  }
} 
Example 42
Source File: CalEventCheckRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.common._
import docspell.restapi.model._

import com.github.eikek.calev.CalEvent
import org.http4s._
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object CalEventCheckRoutes {

  def apply[F[_]: Effect](): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case req @ POST -> Root =>
        for {
          data <- req.as[CalEventCheck]
          res  <- testEvent(data.event)
          resp <- Ok(res)
        } yield resp
    }
  }

  def testEvent[F[_]: Sync](str: String): F[CalEventCheckResult] =
    Timestamp.current[F].map { now =>
      CalEvent.parse(str) match {
        case Right(ev) =>
          val next = ev
            .nextElapses(now.toUtcDateTime, 2)
            .map(Timestamp.atUtc)
          CalEventCheckResult(true, "Valid.", ev.some, next)
        case Left(err) =>
          CalEventCheckResult(false, err, None, Nil)
      }
    }
} 
Example 43
Source File: TagRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s._

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object TagRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root :? QueryParam.QueryOpt(q) =>
        for {
          all  <- backend.tag.findAll(user.account, q.map(_.q))
          resp <- Ok(TagList(all.size, all.map(mkTag).toList))
        } yield resp

      case req @ POST -> Root =>
        for {
          data <- req.as[Tag]
          tag  <- newTag(data, user.account.collective)
          res  <- backend.tag.add(tag)
          resp <- Ok(basicResult(res, "Tag successfully created."))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data <- req.as[Tag]
          tag = changeTag(data, user.account.collective)
          res  <- backend.tag.update(tag)
          resp <- Ok(basicResult(res, "Tag successfully updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          del  <- backend.tag.delete(id, user.account.collective)
          resp <- Ok(basicResult(del, "Tag successfully deleted."))
        } yield resp
    }
  }

} 
Example 44
Source File: UploadRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common._
import docspell.restserver.Config
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.ResponseGenerator

import org.http4s._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl
import org.http4s.multipart.Multipart
import org.log4s._

object UploadRoutes {
  private[this] val logger = getLogger

  def secured[F[_]: Effect](
      backend: BackendApp[F],
      cfg: Config,
      user: AuthToken
  ): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    val submitting = submitFiles[F](backend, cfg, Right(user.account)) _

    HttpRoutes.of {
      case req @ POST -> Root / "item" =>
        submitting(req, None, Priority.High, dsl)

      case req @ POST -> Root / "item" / Ident(itemId) =>
        submitting(req, Some(itemId), Priority.High, dsl)
    }
  }

  def open[F[_]: Effect](backend: BackendApp[F], cfg: Config): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case req @ POST -> Root / "item" / Ident(id) =>
        submitFiles(backend, cfg, Left(id))(req, None, Priority.Low, dsl)

      case req @ POST -> Root / "item" / Ident(itemId) / Ident(id) =>
        submitFiles(backend, cfg, Left(id))(req, Some(itemId), Priority.Low, dsl)
    }
  }

  private def submitFiles[F[_]: Effect](
      backend: BackendApp[F],
      cfg: Config,
      accOrSrc: Either[Ident, AccountId]
  )(
      req: Request[F],
      itemId: Option[Ident],
      prio: Priority,
      dsl: Http4sDsl[F]
  ): F[Response[F]] = {
    import dsl._

    for {
      multipart <- req.as[Multipart[F]]
      updata <- readMultipart(
        multipart,
        logger,
        prio,
        cfg.backend.files.validMimeTypes
      )
      result <- backend.upload.submitEither(updata, accOrSrc, true, itemId)
      res    <- Ok(basicResult(result))
    } yield res
  }
} 
Example 45
Source File: InfoRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect.Sync

import docspell.restapi.model.VersionInfo
import docspell.restserver.BuildInfo

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object InfoRoutes {

  def apply[F[_]: Sync](): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._
    HttpRoutes.of[F] {
      case GET -> (Root / "version") =>
        Ok(
          VersionInfo(
            BuildInfo.version,
            BuildInfo.builtAtMillis,
            BuildInfo.builtAtString,
            BuildInfo.gitHeadCommit.getOrElse(""),
            BuildInfo.gitDescribedVersion.getOrElse("")
          )
        )
    }
  }
} 
Example 46
Source File: ResponseGenerator.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.http4s

import cats.Applicative
import cats.implicits._

import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityEncoder, Header, Response}

trait ResponseGenerator[F[_]] {
  self: Http4sDsl[F] =>

  implicit final class EitherResponses[A, B](e: Either[A, B]) {
    def toResponse(headers: Header*)(implicit
        F: Applicative[F],
        w0: EntityEncoder[F, A],
        w1: EntityEncoder[F, B]
    ): F[Response[F]] =
      e.fold(
        a => UnprocessableEntity(a),
        b => Ok(b)
      ).map(_.withHeaders(headers: _*))
  }

  implicit final class OptionResponse[A](o: Option[A]) {
    def toResponse(
        headers: Header*
    )(implicit F: Applicative[F], w0: EntityEncoder[F, A]): F[Response[F]] =
      o.map(a => Ok(a)).getOrElse(NotFound()).map(_.withHeaders(headers: _*))
  }

}

object ResponseGenerator {} 
Example 47
Source File: JoexRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.joex.routes

import cats.effect._
import cats.implicits._

import docspell.common.{Duration, Ident, Timestamp}
import docspell.joex.JoexApp
import docspell.joexapi.model._
import docspell.store.records.{RJob, RJobLog}

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object JoexRoutes {

  def apply[F[_]: ConcurrentEffect: Timer](app: JoexApp[F]): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._
    HttpRoutes.of[F] {
      case POST -> Root / "notify" =>
        for {
          _    <- app.scheduler.notifyChange
          _    <- app.periodicScheduler.notifyChange
          resp <- Ok(BasicResult(true, "Schedulers notified."))
        } yield resp

      case GET -> Root / "running" =>
        for {
          jobs <- app.scheduler.getRunning
          jj = jobs.map(mkJob)
          resp <- Ok(JobList(jj.toList))
        } yield resp

      case POST -> Root / "shutdownAndExit" =>
        for {
          _ <- ConcurrentEffect[F].start(
            Timer[F].sleep(Duration.seconds(1).toScala) *> app.initShutdown
          )
          resp <- Ok(BasicResult(true, "Shutdown initiated."))
        } yield resp

      case GET -> Root / "job" / Ident(id) =>
        for {
          optJob <- app.scheduler.getRunning.map(_.find(_.id == id))
          optLog <- optJob.traverse(j => app.findLogs(j.id))
          jAndL = for { job <- optJob; log <- optLog } yield mkJobLog(job, log)
          resp <- jAndL.map(Ok(_)).getOrElse(NotFound(BasicResult(false, "Not found")))
        } yield resp

      case POST -> Root / "job" / Ident(id) / "cancel" =>
        for {
          flag <- app.scheduler.requestCancel(id)
          resp <- Ok(
            BasicResult(flag, if (flag) "Cancel request submitted" else "Job not found")
          )
        } yield resp
    }
  }

  def mkJob(j: RJob): Job =
    Job(
      j.id,
      j.subject,
      j.submitted,
      j.priority,
      j.retries,
      j.progress,
      j.started.getOrElse(Timestamp.Epoch)
    )

  def mkJobLog(j: RJob, jl: Vector[RJobLog]): JobAndLog =
    JobAndLog(mkJob(j), jl.map(r => JobLogEvent(r.created, r.level, r.message)).toList)
} 
Example 48
Source File: InfoRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.joex.routes

import cats.effect.Sync

import docspell.joex.BuildInfo
import docspell.joexapi.model.VersionInfo

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object InfoRoutes {

  def apply[F[_]: Sync](): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._
    HttpRoutes.of[F] {
      case GET -> (Root / "version") =>
        Ok(
          VersionInfo(
            BuildInfo.version,
            BuildInfo.builtAtMillis,
            BuildInfo.builtAtString,
            BuildInfo.gitHeadCommit.getOrElse(""),
            BuildInfo.gitDescribedVersion.getOrElse("")
          )
        )
    }
  }
} 
Example 49
Source File: Authenticate.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.data._
import cats.effect._
import cats.implicits._

import docspell.backend.auth._
import docspell.restserver.auth._

import org.http4s._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl
import org.http4s.server._

object Authenticate {

  def authenticateRequest[F[_]: Effect](
      auth: String => F[Login.Result]
  )(req: Request[F]): F[Login.Result] =
    CookieData.authenticator(req) match {
      case Right(str) => auth(str)
      case Left(_)    => Login.Result.invalidAuth.pure[F]
    }

  def of[F[_]: Effect](S: Login[F], cfg: Login.Config)(
      pf: PartialFunction[AuthedRequest[F, AuthToken], F[Response[F]]]
  ): HttpRoutes[F] = {
    val dsl: Http4sDsl[F] = new Http4sDsl[F] {}
    import dsl._

    val authUser = getUser[F](S.loginSession(cfg))

    val onFailure: AuthedRoutes[String, F] =
      Kleisli(req => OptionT.liftF(Forbidden(req.context)))

    val middleware: AuthMiddleware[F, AuthToken] =
      AuthMiddleware(authUser, onFailure)

    middleware(AuthedRoutes.of(pf))
  }

  def apply[F[_]: Effect](S: Login[F], cfg: Login.Config)(
      f: AuthToken => HttpRoutes[F]
  ): HttpRoutes[F] = {
    val dsl: Http4sDsl[F] = new Http4sDsl[F] {}
    import dsl._

    val authUser = getUser[F](S.loginSession(cfg))

    val onFailure: AuthedRoutes[String, F] =
      Kleisli(req => OptionT.liftF(Forbidden(req.context)))

    val middleware: AuthMiddleware[F, AuthToken] =
      AuthMiddleware(authUser, onFailure)

    middleware(AuthedRoutes(authReq => f(authReq.context).run(authReq.req)))
  }

  private def getUser[F[_]: Effect](
      auth: String => F[Login.Result]
  ): Kleisli[F, Request[F], Either[String, AuthToken]] =
    Kleisli(r => authenticateRequest(auth)(r).map(_.toEither))
} 
Example 50
Source File: TodoListApi.scala    From freestyle   with Apache License 2.0 5 votes vote down vote up
package examples.todolist
package http

import cats.effect.Effect
import cats.implicits._
import examples.todolist.service.TodoListService
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl

class TodoListApi[F[_]: Effect](implicit service: TodoListService[F]) extends Http4sDsl[F] {

  import codecs._

  private val prefix = "lists"

  val endpoints = HttpService[F] {
    case POST -> Root / prefix =>
      service.reset.flatMap(e => Ok(e.asJson))

    case GET -> Root / prefix / IntVar(id) =>
      service.retrieve(id) flatMap { item =>
        item.fold(NotFound(s"Could not find ${service.model} with $id"))(todoList =>
          Ok(todoList.asJson))
      }

    case GET -> Root / prefix =>
      service.list.flatMap(l => Ok(l.asJson))

    case req @ POST -> Root / prefix =>
      for {
        todoList         <- req.as[TodoList]
        insertedTodoList <- service.insert(todoList)
        response         <- Ok(insertedTodoList.asJson)
      } yield response

    case req @ PUT -> Root / prefix / IntVar(id) =>
      for {
        todoList        <- req.as[TodoList]
        updatedTodoList <- service.update(todoList.copy(id = Some(id)))
        reponse         <- Ok(updatedTodoList.asJson)
      } yield reponse

    case DELETE -> Root / prefix / IntVar(id) =>
      service.destroy(id) *> Ok()
  }
}

object TodoListApi {
  implicit def instance[F[_]: Effect](implicit service: TodoListService[F]): TodoListApi[F] =
    new TodoListApi[F]
} 
Example 51
Source File: TagApi.scala    From freestyle   with Apache License 2.0 5 votes vote down vote up
package examples.todolist
package http

import cats.effect.Effect
import cats.implicits._
import examples.todolist.service.TagService
import examples.todolist.Tag
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl

class TagApi[F[_]: Effect](implicit service: TagService[F]) extends Http4sDsl[F] {

  import codecs._

  private val prefix = "tags"

  val endpoints = HttpService[F] {
    case POST -> Root / prefix / "reset" =>
      service.reset.flatMap(e => Ok(e.asJson))

    case GET -> Root / prefix / IntVar(id) =>
      service.retrieve(id) flatMap { item =>
        item.fold(NotFound(s"Could not find ${service.model} with $id"))(tag => Ok(tag.asJson))
      }

    case GET -> Root / prefix =>
      service.list.flatMap(l => Ok(l.asJson))

    case req @ POST -> Root / prefix =>
      for {
        tag         <- req.as[Tag]
        insertedTag <- service.insert(tag)
        response    <- Ok(insertedTag.asJson)
      } yield response

    case req @ PUT -> Root / prefix / IntVar(id) =>
      for {
        tag        <- req.as[Tag]
        updatedTag <- service.update(tag.copy(id = Some(id)))
        reponse    <- Ok(updatedTag.asJson)
      } yield reponse

    case DELETE -> Root / prefix / IntVar(id) =>
      service.destroy(id) *> Ok()
  }
}

object TagApi {

  implicit def instance[F[_]: Effect](implicit service: TagService[F]): TagApi[F] = new TagApi[F]
} 
Example 52
Source File: AppApi.scala    From freestyle   with Apache License 2.0 5 votes vote down vote up
package examples.todolist
package http

import cats.effect.Effect
import cats.implicits._
import examples.todolist.service.AppService
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl

class AppApi[F[_]: Effect](implicit service: AppService[F]) extends Http4sDsl[F] {

  import codecs._

  val endpoints = HttpService[F] {
    case POST -> Root / "reset" =>
      service.reset.flatMap(e => Ok(e.asJson))

    case GET -> Root / "list" =>
      service.list.flatMap(l => Ok(l.asJson))

    case req @ POST -> Root / "insert" =>
      for {
        form         <- req.as[TodoForm]
        insertedForm <- service.insert(form)
        response     <- Ok(insertedForm.asJson)
      } yield response

    case req @ PUT -> Root / "update" =>
      for {
        form        <- req.as[TodoForm]
        updatedForm <- service.update(form)
        response    <- Ok(updatedForm.asJson)
      } yield response

    case req @ DELETE -> Root / "delete" =>
      for {
        form     <- req.as[TodoForm]
        deleted  <- service.destroy(form)
        response <- Ok(deleted.asJson)
      } yield response
  }
}

object AppApi {
  implicit def instance[F[_]: Effect](implicit service: AppService[F]): AppApi[F] = new AppApi[F]
} 
Example 53
Source File: TodoItemApi.scala    From freestyle   with Apache License 2.0 5 votes vote down vote up
package examples.todolist
package http

import cats.effect.Effect
import cats.implicits._
import examples.todolist.service.TodoItemService
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl

class TodoItemApi[F[_]: Effect](implicit service: TodoItemService[F]) extends Http4sDsl[F] {

  import codecs._

  private val prefix = "items"

  val endpoints = HttpService[F] {
    case POST -> Root / prefix =>
      service.reset *> Ok()

    case GET -> Root / prefix / IntVar(id) =>
      service.retrieve(id) flatMap { item =>
        item.fold(NotFound(s"Could not find ${service.model} with $id"))(todoItem =>
          Ok(todoItem.asJson))
      }

    case GET -> Root / prefix =>
      service.list.flatMap(l => Ok(l.asJson))

    case req @ POST -> Root / prefix =>
      for {
        todoItem     <- req.as[TodoItem]
        insertedItem <- service.insert(todoItem)
        response     <- Ok(insertedItem.asJson)
      } yield response

    case req @ PUT -> Root / prefix / IntVar(id) =>
      for {
        todoItem    <- req.as[TodoItem]
        updatedItem <- service.update(todoItem.copy(id = Some(id)))
        reponse     <- Ok(updatedItem.asJson)
      } yield reponse

    case DELETE -> Root / prefix / IntVar(id) =>
      service.destroy(id) *> Ok()
  }
}

object TodoItemApi {
  implicit def instance[F[_]: Effect](implicit service: TodoItemService[F]): TodoItemApi[F] =
    new TodoItemApi[F]
} 
Example 54
Source File: Server.scala    From Learn-Scala-Programming   with MIT License 5 votes vote down vote up
package ch14

import cats.effect.IO
import fs2.{Stream, StreamApp}
import fs2.StreamApp.ExitCode
import org.http4s.dsl.Http4sDsl
import org.http4s.server.blaze.BlazeBuilder
import scala.concurrent.ExecutionContext.Implicits.global

object Server extends StreamApp[IO] with Http4sDsl[IO] {
  override def stream(args: List[String],
             requestShutdown: IO[Unit]): Stream[IO, ExitCode] = {
    val config = Config.load("application.conf")
    createServer(config).flatMap(_.serve)
  }

  def createServer(config: IO[Config]): Stream[IO, BlazeBuilder[IO]] = {
    for {
      config <- Stream.eval(config)
      transactor <- Stream.eval(DB.transactor(config.database))
      _ <- Stream.eval(DB.initialize(transactor))
    } yield BlazeBuilder[IO]
      .bindHttp(config.server.port, config.server.host)
      .mountService(new Service(new Repository(transactor)).service, "/")

  }
} 
Example 55
Source File: Service.scala    From Learn-Scala-Programming   with MIT License 5 votes vote down vote up
package ch14

import cats.effect.IO
import ch14.Model.{Inventory, Purchase, Restock}
import org.http4s.{HttpService, MediaType, Response}
import org.http4s.dsl.Http4sDsl
import org.http4s.circe._
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s.headers.`Content-Type`
import fs2.Stream

class Service(repo: Repository) extends Http4sDsl[IO] {

  val service = HttpService[IO] {
    case DELETE -> Root / "articles" / name =>
      repo.deleteArticle(name).flatMap { if (_) NoContent() else NotFound() }

    case POST -> Root / "articles" / name =>
      repo.createArticle(name).flatMap { if (_) NoContent() else Conflict() }

    case GET -> Root / "articles" / name => renderInventory(repo.getArticle(name))

    case req @ POST -> Root / "purchase" =>
      val changes: Stream[IO, Inventory] = for {
        purchase <- Stream.eval(req.decodeJson[Purchase])
        before <- repo.getInventory
        _ <- repo.updateStock(purchase.inventory)
        after <- repo.getInventory
      } yield diff(purchase.order.keys, before, after)
      renderInventory(changes)

    case req @ POST -> Root / "restock" =>
      val newState = for {
        purchase <- Stream.eval(req.decodeJson[Restock])
        _ <- repo.updateStock(purchase.inventory)
        inventory <- repo.getInventory
      } yield inventory
      renderInventory(newState)

    case GET -> Root / "inventory" =>
      getInventory
  }

  private def diff[A](keys: Iterable[A],
                           before: Map[A, Int],
                           after: Map[A, Int]): Map[A, Int] =
    keys.filter(before.contains).map { key =>
      key -> (before(key) - after(key))
    }.toMap

  private def getInventory: IO[Response[IO]] =
    renderInventory(repo.getInventory)

  private def renderInventory(inventory: Stream[IO, Inventory]) =
    Ok(inventory.map(_.asJson.noSpaces),
       `Content-Type`(MediaType.`application/json`))

} 
Example 56
Source File: Hook.scala    From canoe   with MIT License 5 votes vote down vote up
package canoe.api.sources

import canoe.api.{TelegramClient}
import canoe.methods.webhooks.{DeleteWebhook, SetWebhook}
import canoe.models.{InputFile, Update}
import canoe.syntax.methodOps
import cats.Monad
import cats.effect.{ConcurrentEffect, Resource, Timer}
import cats.syntax.all._
import fs2.Stream
import fs2.concurrent.Queue
import io.chrisdavenport.log4cats.Logger
import io.chrisdavenport.log4cats.slf4j.Slf4jLogger
import org.http4s._
import org.http4s.circe.jsonOf
import org.http4s.dsl.Http4sDsl
import org.http4s.implicits._
import org.http4s.server.Server
import org.http4s.server.blaze.BlazeServerBuilder

class Hook[F[_]](queue: Queue[F, Update]) {
  def updates: Stream[F, Update] = queue.dequeue
}

object Hook {

  
  private def listenServer[F[_]: ConcurrentEffect: Timer: Logger](port: Int): Resource[F, Hook[F]] = {
    val dsl = Http4sDsl[F]
    import dsl._

    def app(queue: Queue[F, Update]): HttpApp[F] =
      HttpRoutes
        .of[F] {
          case req @ POST -> Root =>
            req
              .decodeWith(jsonOf[F, Update], strict = true)(queue.enqueue1(_) *> Ok())
              .recoverWith {
                case InvalidMessageBodyFailure(details, _) =>
                  F.error(s"Received unknown type of update. $details") *> Ok()
              }
        }
        .orNotFound

    def server(queue: Queue[F, Update]): Resource[F, Server[F]] =
      BlazeServerBuilder[F].bindHttp(port).withHttpApp(app(queue)).resource

    Resource.suspend(Queue.unbounded[F, Update].map(q => server(q).map(_ => new Hook[F](q))))
  }
} 
Example 57
Source File: http4s.scala    From sup   with Apache License 2.0 5 votes vote down vote up
package sup.modules

import cats.effect.Sync
import cats.Monad
import cats.Reducible
import org.http4s.dsl.Http4sDsl
import org.http4s.EntityEncoder
import org.http4s.HttpRoutes
import org.http4s.Response
import sup.HealthCheck
import sup.HealthResult
import cats.implicits._

object http4s {

  
  def healthCheckRoutes[F[_]: Sync, H[_]: Reducible](
    healthCheck: HealthCheck[F, H],
    path: String = "health-check"
  )(
    implicit encoder: EntityEncoder[F, HealthResult[H]]
  ): HttpRoutes[F] = {

    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of[F] {
      case GET -> Root / `path` =>
        healthCheckResponse(healthCheck)
    }
  }

  def healthCheckResponse[F[_]: Monad, H[_]: Reducible](
    healthCheck: HealthCheck[F, H]
  )(
    implicit encoder: EntityEncoder[F, HealthResult[H]]
  ): F[Response[F]] = {

    val dsl = new Http4sDsl[F] {}
    import dsl._

    healthCheck.check.flatMap { check =>
      if (check.value.reduce.isHealthy) Ok(check)
      else ServiceUnavailable(check)
    }
  }
} 
Example 58
Source File: JobQueueRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restserver.conv.Conversions

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object JobQueueRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root / "state" =>
        for {
          js <- backend.job.queueState(user.account.collective, 200)
          res = Conversions.mkJobQueueState(js)
          resp <- Ok(res)
        } yield resp

      case POST -> Root / Ident(id) / "cancel" =>
        for {
          result <- backend.job.cancelJob(id, user.account.collective)
          resp   <- Ok(Conversions.basicResult(result))
        } yield resp
    }
  }

} 
Example 59
Source File: AccountRoute.scala    From aecor   with MIT License 5 votes vote down vote up
package aecor.example.account

import cats.implicits._
import org.http4s.circe._
import io.circe.generic.auto._
import cats.effect._
import org.http4s._
import org.http4s.dsl.Http4sDsl

trait AccountService[F[_]] {
  def openAccount(accountId: AccountId, checkBalance: Boolean): F[Either[String, Unit]]
}


object AccountRoute {

  class Builder[F[_]: Sync](service: AccountService[F]) extends Http4sDsl[F] with CirceEntityDecoder {
    def routes: HttpRoutes[F] =
      HttpRoutes.of[F] {
        case req @ POST -> Root / "accounts" =>
          for {
            openAccountRequest <- req.as[OpenAccountRequest]
            resp <- service.openAccount(openAccountRequest.accountId, openAccountRequest.checkBalance).flatMap {
              case Left(e)       => BadRequest(e.toString)
              case Right(_) => Ok("")
            }
          } yield resp
      }
  }

  final case class OpenAccountRequest(accountId: AccountId, checkBalance: Boolean)

  implicit val openAccountRequestDecoder =
    io.circe.generic.semiauto.deriveDecoder[OpenAccountRequest]

  def apply[F[_]: Sync](service: AccountService[F]): HttpRoutes[F] =
    new Builder(service).routes

} 
Example 60
Source File: TransactionRoute.scala    From aecor   with MIT License 5 votes vote down vote up
package aecor.example.transaction

import java.util.UUID

import aecor.example.account
import aecor.example.account.AccountId
import aecor.example.common.Amount
import cats.effect.{Effect, Sync}
import cats.implicits._
import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder
import org.http4s.dsl.Http4sDsl
import io.circe.generic.auto._


trait TransactionService[F[_]] {
  def authorizePayment(transactionId: TransactionId,
                       from: From[AccountId],
                       to: To[AccountId],
                       amount: Amount): F[TransactionRoute.ApiResult]
}

object TransactionRoute {

  sealed trait ApiResult
  object ApiResult {
    case object Authorized extends ApiResult
    case class Declined(reason: String) extends ApiResult
  }

  final case class CreateTransactionRequest(from: From[AccountId],
                                            to: To[AccountId],
                                            amount: Amount)

  object TransactionIdVar {
    def unapply(arg: String): Option[TransactionId] = TransactionId(arg).some
  }

  private final class Builder[F[_]: Sync](service: TransactionService[F]) extends Http4sDsl[F] with CirceEntityDecoder {
    def routes: HttpRoutes[F] = HttpRoutes.of[F] {
      case req @ PUT -> Root / "transactions" / TransactionIdVar(transactionId) =>
        for {
          body <- req.as[CreateTransactionRequest]
          CreateTransactionRequest(from, to, amount) = body
          resp <- service.authorizePayment(transactionId, from, to, amount).flatMap {
            case ApiResult.Authorized =>
              Ok("Authorized")
            case ApiResult.Declined(reason) =>
              BadRequest(s"Declined: $reason")
          }
        } yield resp
      case POST -> Root / "test" =>
        service
          .authorizePayment(
            TransactionId(UUID.randomUUID.toString),
            From(account.EventsourcedAlgebra.rootAccountId),
            To(AccountId("foo")),
            Amount(1)
          )
          .flatMap {
          case ApiResult.Authorized =>
            Ok("Authorized")
          case ApiResult.Declined(reason) =>
            BadRequest(s"Declined: $reason")
        }
    }
  }

  def apply[F[_]: Effect](api: TransactionService[F]): HttpRoutes[F] =
    new Builder(api).routes

} 
Example 61
Source File: BookingRoutes.scala    From ticket-booking-aecor   with Apache License 2.0 5 votes vote down vote up
package ru.pavkin.booking.booking.endpoint

import cats.effect.Effect
import cats.implicits._
import io.circe.syntax._
import org.http4s.HttpRoutes
import org.http4s.circe._
import org.http4s.dsl.Http4sDsl
import ru.pavkin.booking.common.models.{BookingKey, ClientId}

final class BookingRoutes[F[_]: Effect](ops: BookingEndpoint[F]) extends Http4sDsl[F] {

  implicit val placeBookingDecoder = jsonOf[F, PlaceBookingRequest]
  implicit val cancelBookingDecoder = jsonOf[F, CancelBookingRequest]

  private val placeBooking: HttpRoutes[F] = HttpRoutes.of {
    case r @ POST -> Root / userId / "bookings" =>
      r.as[PlaceBookingRequest]
        .flatMap(
          r =>
            ops.placeBooking(ClientId(userId), r.concertId, r.seats).flatMap {
              case Left(err) => BadRequest(err.toString)
              case Right(_)  => Ok()
          }
        )
  }

  private val cancelBooking: HttpRoutes[F] = HttpRoutes.of {
    case r @ DELETE -> Root / userId / "bookings" / bookingId =>
      r.as[CancelBookingRequest]
        .flatMap(
          r =>
            ops.cancelBooking(ClientId(userId), BookingKey(bookingId), r.reason).flatMap {
              case Left(err) => BadRequest(err.toString)
              case Right(_)  => Ok()
          }
        )
  }

  private val clientBookings: HttpRoutes[F] = HttpRoutes.of {
    case GET -> Root / userId / "bookings" =>
      ops.clientBookings(ClientId(userId)).flatMap { bookings =>
        Ok(bookings.asJson)
      }
  }

  val routes: HttpRoutes[F] =
    placeBooking <+> clientBookings <+> cancelBooking

} 
Example 62
Source File: TestRoutes.scala    From scala-server-lambda   with MIT License 5 votes vote down vote up
package io.github.howardjohn.lambda.http4s

import cats.{Applicative, MonadError}
import cats.effect.Sync
import cats.implicits._
import io.github.howardjohn.lambda.LambdaHandlerBehavior
import io.github.howardjohn.lambda.LambdaHandlerBehavior._
import org.http4s.dsl.Http4sDsl
import org.http4s.{EntityDecoder, Header, HttpRoutes}
import org.http4s.circe._
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s.dsl.impl.OptionalQueryParamDecoderMatcher

class TestRoutes[F[_]] {

  object TimesQueryMatcher extends OptionalQueryParamDecoderMatcher[Int]("times")

  val dsl = Http4sDsl[F]

  import dsl._

  def routes(implicit sync: Sync[F],
             jsonDecoder: EntityDecoder[F, JsonBody],
             me: MonadError[F, Throwable],
             stringDecoder: EntityDecoder[F, String],
             ap: Applicative[F]): HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root / "hello" :? TimesQueryMatcher(times) =>
      Ok {
        Seq
          .fill(times.getOrElse(1))("Hello World!")
          .mkString(" ")
      }
    case GET -> Root / "long" => Applicative[F].pure(Thread.sleep(1000)).flatMap(_ => Ok("Hello World!"))
    case GET -> Root / "exception" => throw RouteException()
    case GET -> Root / "error" => InternalServerError()
    case req@GET -> Root / "header" =>
      val header = req.headers.find(h => h.name.value == inputHeader).map(_.value).getOrElse("Header Not Found")
      Ok(header, Header(outputHeader, outputHeaderValue))
    case req@POST -> Root / "post" => req.as[String].flatMap(s => Ok(s))
    case req@POST -> Root / "json" => req.as[JsonBody].flatMap(s => Ok(LambdaHandlerBehavior.jsonReturn.asJson))
  }

} 
Example 63
Source File: TimeService.scala    From get-programming-with-scala   with MIT License 5 votes vote down vote up
package org.example.time

import java.time.format.DateTimeFormatter

import cats.effect.IO
import org.http4s.HttpService
import org.http4s.dsl.Http4sDsl

class TimeService extends Http4sDsl[IO] {

  private val printer = new TimePrinter(DateTimeFormatter.RFC_1123_DATE_TIME)

  val service = HttpService[IO] {
    case GET -> Root / "datetime" / country =>
      try {
        Ok(printer.now(country))
      } catch {
        case ex: IllegalStateException => NotFound(ex.getMessage)
      }
  }

} 
Example 64
Source File: SearchService.scala    From franklin   with Apache License 2.0 5 votes vote down vote up
package com.azavea.franklin.api.services

import cats.effect._
import cats.implicits._
import com.azavea.franklin.api.endpoints.SearchEndpoints
import com.azavea.franklin.api.implicits._
import com.azavea.franklin.database.{SearchFilters, StacItemDao}
import com.azavea.franklin.datamodel.SearchMethod
import doobie.implicits._
import doobie.util.transactor.Transactor
import eu.timepit.refined.types.numeric.NonNegInt
import eu.timepit.refined.types.string.NonEmptyString
import io.circe._
import io.circe.syntax._
import org.http4s._
import org.http4s.dsl.Http4sDsl
import sttp.tapir.server.http4s._

class SearchService[F[_]: Sync](
    apiHost: NonEmptyString,
    defaultLimit: NonNegInt,
    enableTiles: Boolean,
    xa: Transactor[F]
)(
    implicit contextShift: ContextShift[F]
) extends Http4sDsl[F] {

  def search(searchFilters: SearchFilters, searchMethod: SearchMethod): F[Either[Unit, Json]] = {
    for {
      searchResult <- StacItemDao
        .getSearchResult(
          searchFilters,
          searchFilters.limit getOrElse defaultLimit,
          apiHost,
          searchMethod
        )
        .transact(xa)
    } yield {
      val updatedFeatures = searchResult.features.map { item =>
        (item.collection, enableTiles) match {
          case (Some(collectionId), true) => item.addTilesLink(apiHost.value, collectionId, item.id)
          case _                          => item
        }
      }
      Either.right(searchResult.copy(features = updatedFeatures).asJson)
    }
  }

  val routes: HttpRoutes[F] =
    SearchEndpoints.searchGet.toRoutes(searchFilters => search(searchFilters, SearchMethod.Get)) <+> SearchEndpoints.searchPost
      .toRoutes {
        case searchFilters => search(searchFilters, SearchMethod.Post)
      }
} 
Example 65
Source File: LandingPageService.scala    From franklin   with Apache License 2.0 5 votes vote down vote up
package com.azavea.franklin.api.services

import cats._
import cats.effect._
import cats.implicits._
import com.azavea.franklin.api.commands.ApiConfig
import com.azavea.franklin.api.endpoints.LandingPageEndpoints
import com.azavea.franklin.api.implicits._
import com.azavea.franklin.datamodel.{LandingPage, Link, Conformance => FranklinConformance}
import com.azavea.stac4s.StacLinkType
import com.azavea.stac4s._
import eu.timepit.refined.auto._
import eu.timepit.refined.types.string.NonEmptyString
import io.circe._
import io.circe.syntax._
import org.http4s._
import org.http4s.dsl.Http4sDsl
import sttp.tapir.server.http4s._

class LandingPageService[F[_]: Sync](apiConfig: ApiConfig)(implicit contextShift: ContextShift[F])
    extends Http4sDsl[F] {

  val links = List(
    Link(
      apiConfig.apiHost,
      StacLinkType.Self,
      Some(`application/json`),
      Some("Franklin Powered Catalog")
    ),
    Link(
      apiConfig.apiHost + "/open-api/spec.yaml",
      StacLinkType.ServiceDesc,
      Some(VendorMediaType("application/vnd.oai.openapi+json;version=3.0")),
      Some("Open API 3 Documentation")
    ),
    Link(
      apiConfig.apiHost + "/conformance",
      StacLinkType.Conformance,
      Some(`application/json`),
      None
    ),
    Link(
      apiConfig.apiHost + "/collections",
      StacLinkType.Data,
      Some(`application/json`),
      Some("Collections Listing")
    ),
    Link(
      apiConfig.apiHost + "/search",
      StacLinkType.Data,
      Some(`application/geo+json`),
      Some("Franklin Powered STAC")
    )
  )

  def landingPage(): F[Either[Unit, Json]] = {
    Applicative[F].pure {
      val title: NonEmptyString = "Franklin Powered OGC API - Features and STAC web service"
      val description: NonEmptyString =
        "Web service powered by [Franklin](https://github.com/azavea/franklin)"
      Right(LandingPage(title, description, links).asJson)
    }
  }

  def conformancePage(): F[Either[Unit, Json]] = {
    Applicative[F].pure {
      val uriList: List[NonEmptyString] = List(
        "http://www.opengis.net/spec/ogcapi-features-1/1.0/req/core",
        "http://www.opengis.net/spec/ogcapi-features-1/1.0/req/oas30",
        "http://www.opengis.net/spec/ogcapi-features-1/1.0/req/geojson"
      )
      Right(FranklinConformance(uriList).asJson)
    }
  }

  val routes: HttpRoutes[F] =
    LandingPageEndpoints.landingPageEndpoint.toRoutes(_ => landingPage()) <+>
      LandingPageEndpoints.conformanceEndpoint.toRoutes(_ => conformancePage())
} 
Example 66
Source File: CollectiveRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OCollective
import docspell.restapi.model._
import docspell.restserver.conv.Conversions
import docspell.restserver.http4s._

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object CollectiveRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root / "insights" =>
        for {
          ins  <- backend.collective.insights(user.account.collective)
          resp <- Ok(Conversions.mkItemInsights(ins))
        } yield resp

      case req @ POST -> Root / "settings" =>
        for {
          settings <- req.as[CollectiveSettings]
          sett = OCollective.Settings(settings.language, settings.integrationEnabled)
          res <-
            backend.collective
              .updateSettings(user.account.collective, sett)
          resp <- Ok(Conversions.basicResult(res, "Settings updated."))
        } yield resp

      case GET -> Root / "settings" =>
        for {
          collDb <- backend.collective.find(user.account.collective)
          sett = collDb.map(c => CollectiveSettings(c.language, c.integrationEnabled))
          resp <- sett.toResponse()
        } yield resp

      case GET -> Root / "contacts" :? QueryParam.QueryOpt(q) +& QueryParam
            .ContactKindOpt(kind) =>
        for {
          res <-
            backend.collective
              .getContacts(user.account.collective, q.map(_.q), kind)
              .take(50)
              .compile
              .toList
          resp <- Ok(ContactList(res.map(Conversions.mkContact)))
        } yield resp

      case GET -> Root =>
        for {
          collDb <- backend.collective.find(user.account.collective)
          coll = collDb.map(c => Collective(c.id, c.state, c.created))
          resp <- coll.toResponse()
        } yield resp
    }
  }

} 
Example 67
Source File: RegisterRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.ops.OCollective.RegisterData
import docspell.backend.signup.{NewInviteResult, SignupResult}
import docspell.restapi.model._
import docspell.restserver.Config
import docspell.restserver.http4s.ResponseGenerator

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl
import org.log4s._

object RegisterRoutes {
  private[this] val logger = getLogger

  def apply[F[_]: Effect](backend: BackendApp[F], cfg: Config): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case req @ POST -> Root / "register" =>
        for {
          data <- req.as[Registration]
          res  <- backend.signup.register(cfg.backend.signup)(convert(data))
          resp <- Ok(convert(res))
        } yield resp

      case req @ POST -> Root / "newinvite" =>
        for {
          data <- req.as[GenInvite]
          res  <- backend.signup.newInvite(cfg.backend.signup)(data.password)
          resp <- Ok(convert(res))
        } yield resp
    }
  }

  def convert(r: NewInviteResult): InviteResult =
    r match {
      case NewInviteResult.Success(id) =>
        InviteResult(true, "New invitation created.", Some(id))
      case NewInviteResult.InvitationDisabled =>
        InviteResult(false, "Signing up is not enabled for invitations.", None)
      case NewInviteResult.PasswordMismatch =>
        InviteResult(false, "Password is invalid.", None)
    }

  def convert(r: SignupResult): BasicResult =
    r match {
      case SignupResult.CollectiveExists =>
        BasicResult(false, "A collective with this name already exists.")
      case SignupResult.InvalidInvitationKey =>
        BasicResult(false, "Invalid invitation key.")
      case SignupResult.SignupClosed =>
        BasicResult(false, "Sorry, registration is closed.")
      case SignupResult.Failure(ex) =>
        logger.error(ex)("Error signing up")
        BasicResult(false, s"Internal error: ${ex.getMessage}")
      case SignupResult.Success =>
        BasicResult(true, "Signup successful")
    }

  def convert(r: Registration): RegisterData =
    RegisterData(r.collectiveName, r.login, r.password, r.invite)
} 
Example 68
Source File: Http4sRpcServer.scala    From iotchain   with MIT License 5 votes vote down vote up
package jbok.network.rpc.http

import cats.effect.{ConcurrentEffect, Resource, Sync, Timer}
import cats.implicits._
import io.circe.Json
import io.circe.syntax._
import jbok.network.rpc.{RpcRequest, RpcService}
import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityCodec._
import org.http4s.dsl.Http4sDsl
import org.http4s.implicits._
import org.http4s.server.Server
import org.http4s.server.blaze.BlazeServerBuilder

object Http4sRpcServer {
  def routes[F[_]](service: RpcService[F, Json])(implicit F: Sync[F]): HttpRoutes[F] = {
    val dsl = Http4sDsl[F]
    import dsl._

    HttpRoutes.of[F] {
      case req @ POST -> path =>
        for {
          json   <- req.as[Json]
          result <- service.handle(RpcRequest(path.toList, json))
          resp   <- Ok(result.asJson)
        } yield resp
    }
  }

  def server[F[_]](service: RpcService[F, Json])(implicit F: ConcurrentEffect[F], T: Timer[F]): Resource[F, Server[F]] =
    BlazeServerBuilder[F]
      .bindLocal(0)
      .withHttpApp(routes[F](service).orNotFound)
      .withWebSockets(true)
      .resource
} 
Example 69
Source File: LoginRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.auth._
import docspell.restapi.model._
import docspell.restserver._
import docspell.restserver.auth._

import org.http4s._
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object LoginRoutes {

  def login[F[_]: Effect](S: Login[F], cfg: Config): HttpRoutes[F] = {
    val dsl: Http4sDsl[F] = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of[F] {
      case req @ POST -> Root / "login" =>
        for {
          up   <- req.as[UserPass]
          res  <- S.loginUserPass(cfg.auth)(Login.UserPass(up.account, up.password))
          resp <- makeResponse(dsl, cfg, res, up.account)
        } yield resp
    }
  }

  def session[F[_]: Effect](S: Login[F], cfg: Config): HttpRoutes[F] = {
    val dsl: Http4sDsl[F] = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of[F] {
      case req @ POST -> Root / "session" =>
        Authenticate
          .authenticateRequest(S.loginSession(cfg.auth))(req)
          .flatMap(res => makeResponse(dsl, cfg, res, ""))

      case POST -> Root / "logout" =>
        Ok().map(_.addCookie(CookieData.deleteCookie(cfg)))
    }
  }

  def makeResponse[F[_]: Effect](
      dsl: Http4sDsl[F],
      cfg: Config,
      res: Login.Result,
      account: String
  ): F[Response[F]] = {
    import dsl._
    res match {
      case Login.Result.Ok(token) =>
        for {
          cd <- AuthToken.user(token.account, cfg.auth.serverSecret).map(CookieData.apply)
          resp <- Ok(
            AuthResult(
              token.account.collective.id,
              token.account.user.id,
              true,
              "Login successful",
              Some(cd.asString),
              cfg.auth.sessionValid.millis
            )
          ).map(_.addCookie(cd.asCookie(cfg)))
        } yield resp
      case _ =>
        Ok(AuthResult("", account, false, "Login failed.", None, 0L))
    }
  }

} 
Example 70
Source File: CheckFileRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model.{BasicItem, CheckFileResult}
import docspell.restserver.http4s.ResponseGenerator
import docspell.store.records.RItem

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object CheckFileRoutes {

  def secured[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root / checksum =>
        for {
          items <-
            backend.itemSearch.findByFileCollective(checksum, user.account.collective)
          resp <- Ok(convert(items))
        } yield resp

    }
  }

  def open[F[_]: Effect](backend: BackendApp[F]): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root / Ident(id) / checksum =>
        for {
          items <- backend.itemSearch.findByFileSource(checksum, id)
          resp  <- Ok(convert(items))
        } yield resp
    }
  }

  def convert(v: Vector[RItem]): CheckFileResult =
    CheckFileResult(
      v.nonEmpty,
      v.map(r => BasicItem(r.id, r.name, r.direction, r.state, r.created, r.itemDate))
        .toList
    )

} 
Example 71
Source File: SentMailRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.data.OptionT
import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OMail.Sent
import docspell.common._
import docspell.restapi.model._

import emil.javamail.syntax._
import org.http4s._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object SentMailRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root / "item" / Ident(id) =>
        for {
          all  <- backend.mail.getSentMailsForItem(user.account, id)
          resp <- Ok(SentMails(all.map(convert).toList))
        } yield resp

      case GET -> Root / "mail" / Ident(mailId) =>
        (for {
          mail <- backend.mail.getSentMail(user.account, mailId)
          resp <- OptionT.liftF(Ok(convert(mail)))
        } yield resp).getOrElseF(NotFound())

      case DELETE -> Root / "mail" / Ident(mailId) =>
        for {
          n    <- backend.mail.deleteSentMail(user.account, mailId)
          resp <- Ok(BasicResult(n > 0, s"Mails deleted: $n"))
        } yield resp
    }
  }

  def convert(s: Sent): SentMail =
    SentMail(
      s.id,
      s.senderLogin,
      s.connectionName,
      s.recipients.map(_.asUnicodeString),
      s.subject,
      s.body,
      s.created
    )
} 
Example 72
Source File: PersonRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.common.syntax.all._
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.QueryParam

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl
import org.log4s._

object PersonRoutes {
  private[this] val logger = getLogger

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root :? QueryParam.FullOpt(full) +& QueryParam.QueryOpt(q) =>
        if (full.getOrElse(false))
          for {
            data <- backend.organization.findAllPerson(user.account, q.map(_.q))
            resp <- Ok(PersonList(data.map(mkPerson).toList))
          } yield resp
        else
          for {
            data <- backend.organization.findAllPersonRefs(user.account, q.map(_.q))
            resp <- Ok(ReferenceList(data.map(mkIdName).toList))
          } yield resp

      case req @ POST -> Root =>
        for {
          data   <- req.as[Person]
          newPer <- newPerson(data, user.account.collective)
          added  <- backend.organization.addPerson(newPer)
          resp   <- Ok(basicResult(added, "New person saved."))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data   <- req.as[Person]
          upPer  <- changePerson(data, user.account.collective)
          update <- backend.organization.updatePerson(upPer)
          resp   <- Ok(basicResult(update, "Person updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          _      <- logger.fdebug(s"Deleting person ${id.id}")
          delOrg <- backend.organization.deletePerson(id, user.account.collective)
          resp   <- Ok(basicResult(delOrg, "Person deleted."))
        } yield resp
    }
  }

} 
Example 73
Source File: SourceRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.ResponseGenerator

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object SourceRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root =>
        for {
          all <- backend.source.findAll(user.account)
          res <- Ok(SourceList(all.map(mkSource).toList))
        } yield res

      case req @ POST -> Root =>
        for {
          data  <- req.as[Source]
          src   <- newSource(data, user.account.collective)
          added <- backend.source.add(src)
          resp  <- Ok(basicResult(added, "Source added."))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data <- req.as[Source]
          src = changeSource(data, user.account.collective)
          updated <- backend.source.update(src)
          resp    <- Ok(basicResult(updated, "Source updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          del  <- backend.source.delete(id, user.account.collective)
          resp <- Ok(basicResult(del, "Source deleted."))
        } yield resp
    }
  }

} 
Example 74
Source File: MailSendRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.backend.ops.OMail.{AttachSelection, ItemMail}
import docspell.backend.ops.SendResult
import docspell.common._
import docspell.restapi.model._

import emil.MailAddress
import emil.javamail.syntax._
import org.http4s._
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object MailSendRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case req @ POST -> Root / Ident(name) / Ident(id) =>
        for {
          in <- req.as[SimpleMail]
          mail = convertIn(id, in)
          res <- mail.traverse(m => backend.mail.sendMail(user.account, name, m))
          resp <- res.fold(
            err => Ok(BasicResult(false, s"Invalid mail data: $err")),
            res => Ok(convertOut(res))
          )
        } yield resp
    }
  }

  def convertIn(item: Ident, s: SimpleMail): Either[String, ItemMail] =
    for {
      rec     <- s.recipients.traverse(MailAddress.parse)
      fileIds <- s.attachmentIds.traverse(Ident.fromString)
      sel =
        if (s.addAllAttachments) AttachSelection.All
        else AttachSelection.Selected(fileIds)
    } yield ItemMail(item, s.subject, rec, s.body, sel)

  def convertOut(res: SendResult): BasicResult =
    res match {
      case SendResult.Success(_) =>
        BasicResult(true, "Mail sent.")
      case SendResult.SendFailure(ex) =>
        BasicResult(false, s"Mail sending failed: ${ex.getMessage}")
      case SendResult.StoreFailure(ex) =>
        BasicResult(
          false,
          s"Mail was sent, but could not be store to database: ${ex.getMessage}"
        )
      case SendResult.NotFound =>
        BasicResult(false, s"There was no mail-connection or item found.")
    }
} 
Example 75
Source File: FullTextIndexRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.data.OptionT
import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common._
import docspell.restserver.Config
import docspell.restserver.conv.Conversions

import org.http4s._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object FullTextIndexRoutes {

  def secured[F[_]: Effect](
      cfg: Config,
      backend: BackendApp[F],
      user: AuthToken
  ): HttpRoutes[F] =
    if (!cfg.fullTextSearch.enabled) notFound[F]
    else {
      val dsl = Http4sDsl[F]
      import dsl._

      HttpRoutes.of {
        case POST -> Root / "reIndex" =>
          for {
            res <- backend.fulltext.reindexCollective(user.account).attempt
            resp <-
              Ok(Conversions.basicResult(res, "Full-text index will be re-created."))
          } yield resp
      }
    }

  def open[F[_]: Effect](cfg: Config, backend: BackendApp[F]): HttpRoutes[F] =
    if (!cfg.fullTextSearch.enabled) notFound[F]
    else {
      val dsl = Http4sDsl[F]
      import dsl._

      HttpRoutes.of {
        case POST -> Root / "reIndexAll" / Ident(id) =>
          for {
            res <-
              if (id.nonEmpty && id == cfg.fullTextSearch.recreateKey)
                backend.fulltext.reindexAll.attempt
              else Left(new Exception("The provided key is invalid.")).pure[F]
            resp <-
              Ok(Conversions.basicResult(res, "Full-text index will be re-created."))
          } yield resp
      }
    }

  private def notFound[F[_]: Effect]: HttpRoutes[F] =
    HttpRoutes(_ => OptionT.pure(Response.notFound[F]))
} 
Example 76
Source File: OrganizationRoutes.scala    From docspell   with GNU General Public License v3.0 5 votes vote down vote up
package docspell.restserver.routes

import cats.effect._
import cats.implicits._

import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken
import docspell.common.Ident
import docspell.restapi.model._
import docspell.restserver.conv.Conversions._
import docspell.restserver.http4s.QueryParam

import org.http4s.HttpRoutes
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.circe.CirceEntityEncoder._
import org.http4s.dsl.Http4sDsl

object OrganizationRoutes {

  def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
    val dsl = new Http4sDsl[F] {}
    import dsl._

    HttpRoutes.of {
      case GET -> Root :? QueryParam.FullOpt(full) +& QueryParam.QueryOpt(q) =>
        if (full.getOrElse(false))
          for {
            data <- backend.organization.findAllOrg(user.account, q.map(_.q))
            resp <- Ok(OrganizationList(data.map(mkOrg).toList))
          } yield resp
        else
          for {
            data <- backend.organization.findAllOrgRefs(user.account, q.map(_.q))
            resp <- Ok(ReferenceList(data.map(mkIdName).toList))
          } yield resp

      case req @ POST -> Root =>
        for {
          data   <- req.as[Organization]
          newOrg <- newOrg(data, user.account.collective)
          added  <- backend.organization.addOrg(newOrg)
          resp   <- Ok(basicResult(added, "New organization saved."))
        } yield resp

      case req @ PUT -> Root =>
        for {
          data   <- req.as[Organization]
          upOrg  <- changeOrg(data, user.account.collective)
          update <- backend.organization.updateOrg(upOrg)
          resp   <- Ok(basicResult(update, "Organization updated."))
        } yield resp

      case DELETE -> Root / Ident(id) =>
        for {
          delOrg <- backend.organization.deleteOrg(id, user.account.collective)
          resp   <- Ok(basicResult(delOrg, "Organization deleted."))
        } yield resp
    }
  }

}