Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create restful CRUD helper #67

Open
Krever opened this issue Nov 13, 2017 · 2 comments
Open

Create restful CRUD helper #67

Krever opened this issue Nov 13, 2017 · 2 comments

Comments

@Krever
Copy link
Collaborator

Krever commented Nov 13, 2017

In one of our projects we developed following piece of code to easily create generic CRUD for any type:

import endpoints.algebra

trait ApiBase extends algebra.Endpoints with algebra.CirceEntities with algebra.OptionalResponses {

  def restfulCrud[Entity: JsonResponse, Id: Segment, EntityReq: JsonRequest](basePath: String)(
      implicit eList: JsonResponse[Seq[Entity]]): Crud[Entity, Id, EntityReq] = {
    Crud(
      getAll = endpoint(
        get[Unit, Unit](path / basePath),
        jsonResponse[Seq[Entity]]
      ),
      create = endpoint(
        post[Unit, EntityReq, Unit, EntityReq](path / basePath, jsonRequest[EntityReq]),
        jsonResponse[Entity]
      ),
      getById = endpoint(
        get[Id, Unit](path / basePath / segment[Id]),
        option(jsonResponse[Entity])
      ),
      update = endpoint(
        request[Id, EntityReq, Unit, (Id, EntityReq)](
          Put,
          path / basePath / segment[Id],
          jsonRequest[EntityReq]
        ),
        option(jsonResponse[Entity])
      ),
      delete = endpoint(
        request[Id, Unit, Unit, Id](
          Delete,
          path / basePath / segment[Id]
        ),
        option(emptyResponse)
      )
    )

  }

  case class Crud[Entity, Id, EntityReq](
      getAll: Endpoint[Unit, Seq[Entity]],
      create: Endpoint[EntityReq, Entity],
      getById: Endpoint[Id, Option[Entity]],
      update: Endpoint[(Id, EntityReq), Option[Entity]],
      delete: Endpoint[Id, Option[Unit]]
  )
}

This code is working but requires maybe some polish and testing.

Beside that we implemented following on server side to get such crud endpoints implemented automatically with slick-repo.

import akka.http.scaladsl.server.{Directives, Route}
import com.byteslounge.slickrepo.meta.Entity
import com.byteslounge.slickrepo.repository.Repository
import com.nordea.gpdw.dq.ws.data.DBExecutor
import com.nordea.gpdw.dq.ws.shared.api.ApiBase

import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try

trait CrudRoutesBase extends ApiBase with AkkaHttpServer {

  import Directives._

  val dbExecutor: DBExecutor
  implicit def ec: ExecutionContext

  import dbExecutor.DBIOOps

  case class CrudOperationsRoutes(create: Route, getAll: Route, getById: Route, update: Route, delete: Route) {
    def allRoutes = create ~ getAll ~ getById ~ update ~ delete
  }

  def implementCrudWithRepo[DtoRequest, Dto, DBEntity <: Entity[DBEntity, Id], Id](
      crud: Crud[Dto, Id, DtoRequest],
      repo: Repository[DBEntity, Id],
      makeEntityForCreate: DtoRequest => DBEntity,
      makeEntityForUpdate: (Id, DtoRequest) => DBEntity,
      entityToDto: DBEntity => Dto) =
    CrudOperationsRoutes(
      crud.create
        .implementedByAsync { dto =>
          repo
            .save(makeEntityForCreate(dto))
            .run
            .map(entityToDto)
        },
      crud.getAll
        .implementedByAsync { _ =>
          repo
            .findAll()
            .run
            .map(_.map(entityToDto))
        },
      crud.getById
        .implementedByAsync { id =>
          repo
            .findOne(id)
            .run
            .map(enOpt => enOpt.map(entityToDto))
        },
      crud.update
        .implementedByAsync {
          case (id, dto) => {
            repo.findOne(id).run.flatMap {
              case Some(_) =>
                repo
                  .update(makeEntityForUpdate(id, dto))
                  .run
                  .map(en => Some(entityToDto(en)))
              case None => Future(None)
            }
          }
        },
      crud.delete
        .implementedByAsync { id =>
          Try(
            (for {
              dto <- repo.findOne(id)
              _ <- repo.delete(dto.get)
            } yield Some(())).run
          ).getOrElse(Future.successful(None))
        }
    )
}

Feel free to add any suggestions to this functionalities before someone(probably me) make a PR.

@julienrf
Copy link
Member

This is super nice and the ability to define such abstractions was definitely a motivation for developing endpoints (doing the same with Play is not as simple!). I’d be very happy to have it in endpoints :)
I would just have a couple of comments:

  • IMHO the basePath should be a Path[_], not a String,
  • I would require algebra.JsonEntities instead of algebra.CirceEntities.

@Krever Krever added the spree label Aug 29, 2018
@liosedhel
Copy link

Hi :) I will try to work on that on the scala spree event.

liosedhel added a commit to liosedhel/endpoints that referenced this issue Sep 9, 2018
liosedhel added a commit to liosedhel/endpoints that referenced this issue Sep 9, 2018
liosedhel added a commit to liosedhel/endpoints that referenced this issue Sep 9, 2018
liosedhel added a commit to liosedhel/endpoints that referenced this issue Sep 9, 2018
@julienrf julienrf removed the spree label Jun 10, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants