Skip to content

Commit 50b5fe1

Browse files
committed
adding play2 with slick and flyway
1 parent 2abf69f commit 50b5fe1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1723
-4
lines changed

.DS_Store

8 KB
Binary file not shown.

.gitignore

+41-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,41 @@
1-
*.class
2-
*.log
1+
/.idea
2+
3+
modules/play/logs/
4+
5+
# sbt specific
6+
.cache
7+
.history
8+
.lib/
9+
dist/*
10+
target/
11+
lib_managed/
12+
src_managed/
13+
project/boot/
14+
project/plugins/project/
15+
16+
# Scala-IDE specific
17+
.scala_dependencies
18+
.worksheet
19+
20+
### PlayFramework template
21+
# Ignore Play! working directory #
22+
bin/
23+
/db
24+
.eclipse
25+
/lib/
26+
/logs/
27+
/project/project
28+
/project/target
29+
/target
30+
tmp/
31+
test-result
32+
server.pid
33+
*.iml
34+
*.eml
35+
/dist/
36+
37+
38+
test.mv.db
39+
40+
#Firebase
41+
firebase.json

LICENSE

100644100755
File mode changed.

README.md

100644100755
+55-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,55 @@
1-
# bootstrap-play2
2-
Bootstrap a REST Service with Play 2 Framework
1+
# Play2-Bootstrap
2+
3+
[![codecov.io](https://img.shields.io/codecov/c/github/innFactory/bootstrap-akka-http/master.svg?style=flat)](https://codecov.io/github/innFactory/bootstrap-akka-http)
4+
5+
Bootstrap a rest service with Play2, isolated Slick and isolated Flyway
6+
7+
This project is built with:
8+
- Play Framework 2.6
9+
- Slick 3.2.3
10+
- Flyway 5.1.1
11+
12+
It needs a PostgresQL database
13+
14+
15+
## Database Migration
16+
17+
```bash
18+
sbt flyway/flywayMigrate
19+
```
20+
21+
## Slick Code Generation
22+
23+
You will need to run the flywayMigrate task first, and then you will be able to generate tables using sbt-codegen.
24+
25+
```bash
26+
sbt slickGen
27+
```
28+
29+
## Testing
30+
31+
You can run functional tests against an in memory database and Slick easily with Play from a clean slate:
32+
33+
```bash
34+
sbt ciTest
35+
```
36+
37+
## Running
38+
39+
To run the project, start up Play:
40+
41+
```bash
42+
sbt run
43+
```
44+
45+
## Running
46+
47+
To create a docker Container:
48+
49+
```bash
50+
sbt docker:publishLocal
51+
```
52+
53+
And that's it!
54+
55+
Its locally aviable at: <http://localhost:9000>

app/.DS_Store

6 KB
Binary file not shown.

app/Module.scala

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
2+
import javax.inject.{Inject, Provider, Singleton}
3+
import com.google.inject.AbstractModule
4+
import com.typesafe.config.Config
5+
import common.jwt.FirebaseJWTValidator
6+
import models.db._
7+
import play.api.inject.ApplicationLifecycle
8+
import play.api.{Configuration, Environment}
9+
import slick.jdbc.JdbcBackend.Database
10+
11+
import scala.concurrent.Future
12+
13+
/**
14+
* This module handles the bindings for the API to the Slick implementation.
15+
*
16+
* https://www.playframework.com/documentation/latest/ScalaDependencyInjection#Programmatic-bindings
17+
*/
18+
class Module(environment: Environment,
19+
configuration: Configuration) extends AbstractModule {
20+
override def configure(): Unit = {
21+
bind(classOf[Database]).toProvider(classOf[DatabaseProvider])
22+
bind(classOf[ContactDAO]).to(classOf[SlickContactDAO])
23+
bind(classOf[ContactDAOCloseHook]).asEagerSingleton()
24+
bind(classOf[firebaseCreationService]).asEagerSingleton()
25+
bind(classOf[firebaseDeletionService]).asEagerSingleton()
26+
bind(classOf[configuration]).asEagerSingleton()
27+
}
28+
}
29+
30+
class configuration @Inject() (config: Config) {
31+
config
32+
}
33+
34+
/** Creates FirebaseApp on Application creation */
35+
class firebaseCreationService @Inject() (config: Config) {
36+
FirebaseJWTValidator.instanciateFirebase(config.getString("firebase.file"), "https://innfactory-inntend.firebaseio.com/")
37+
}
38+
39+
/** Deletes FirebaseApp safely. Important on dev restart. */
40+
class firebaseDeletionService @Inject()(lifecycle: ApplicationLifecycle) {
41+
lifecycle.addStopHook { () =>
42+
Future.successful(FirebaseJWTValidator.deleteFirebase())
43+
}
44+
}
45+
46+
@Singleton
47+
class DatabaseProvider @Inject() (config: Config) extends Provider[Database] {
48+
lazy val get = Database.forConfig("innSide.database", config)
49+
}
50+
51+
/** Closes database connections safely. Important on dev restart. */
52+
class ContactDAOCloseHook @Inject()(dao: ContactDAO, lifecycle: ApplicationLifecycle) {
53+
lifecycle.addStopHook { () =>
54+
Future.successful(dao.close())
55+
}
56+
}
57+
58+

app/common/.DS_Store

6 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package common.errorHandling
2+
3+
import play.api.http.HttpErrorHandler
4+
import play.api.mvc._
5+
import play.api.mvc.Results._
6+
import scala.concurrent._
7+
import javax.inject.Singleton
8+
9+
@Singleton
10+
class ErrorHandler extends HttpErrorHandler {
11+
12+
def onClientError(request: RequestHeader, statusCode: Int, message: String) = {
13+
Future.successful(
14+
Status(statusCode)("A client error occurred: " + message)
15+
)
16+
}
17+
18+
def onServerError(request: RequestHeader, exception: Throwable) = {
19+
Future.successful(
20+
InternalServerError("A server error occurred: " + exception.getMessage)
21+
)
22+
}
23+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package common.jwt
2+
import com.google.auth.oauth2.GoogleCredentials
3+
import com.google.firebase.auth.{FirebaseAuth, FirebaseToken}
4+
import com.google.firebase.{FirebaseApp, FirebaseOptions}
5+
import com.nimbusds.jwt.proc.BadJWTException
6+
7+
import scala.util.{Failure, Success, Try}
8+
9+
object FirebaseJWTValidator {
10+
11+
def instanciateFirebase(jsonFilePath: String, databseUrl: String) = {
12+
val serviceAccount =
13+
getClass().getClassLoader().getResourceAsStream(jsonFilePath)
14+
15+
val options: FirebaseOptions = new FirebaseOptions.Builder()
16+
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
17+
.setDatabaseUrl(databseUrl)
18+
.build
19+
FirebaseApp.initializeApp(options)
20+
}
21+
22+
def deleteFirebase(): Unit = {
23+
FirebaseApp.getInstance().delete()
24+
}
25+
26+
def apply(): FirebaseJWTValidator = new FirebaseJWTValidator()
27+
}
28+
29+
class FirebaseJWTValidator extends JwtValidator[FirebaseToken] {
30+
31+
override def validate(
32+
jwtToken: JwtToken): Either[BadJWTException, FirebaseToken] = {
33+
val decodedToken = Try(
34+
FirebaseAuth.getInstance.verifyIdToken(jwtToken.content))
35+
decodedToken match {
36+
case Success(dt) => Right(dt)
37+
case Failure(f) => Left(new BadJWTException(f.getMessage, f))
38+
}
39+
}
40+
}

app/common/jwt/JwtFilter.scala

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package common.jwt
2+
3+
import akka.stream.Materializer
4+
import com.nimbusds.jwt.proc.BadJWTException
5+
import javax.inject.Inject
6+
import play.api.mvc.Results.Unauthorized
7+
import play.api.mvc._
8+
9+
import scala.concurrent.{ExecutionContext, Future}
10+
11+
class JwtFilter @Inject() (implicit val mat: Materializer, ec: ExecutionContext) extends Filter {
12+
def apply(nextFilter: RequestHeader => Future[Result])
13+
(requestHeader: RequestHeader): Future[Result] = {
14+
if(sys.env.getOrElse("SWAGGER_AUTH", "EXPOSE_SWAGGER") == "EXPOSE_SWAGGER" && requestHeader.path == "/v1/swagger.json") {
15+
nextFilter.apply(requestHeader)
16+
} else {
17+
requestHeader.headers.get("Authorization") match {
18+
case Some(header) =>{
19+
20+
val token = header match {
21+
case x : String if x.startsWith("Bearer") => JwtToken(x.splitAt(7)._2)
22+
case x => JwtToken(x)
23+
}
24+
FirebaseJWTValidator.apply().validate(token) match {
25+
case Left(error: BadJWTException) => Future(Unauthorized(error.getMessage))
26+
case Right(dt) => nextFilter.apply(requestHeader)
27+
}
28+
}
29+
case None => Future.successful(
30+
Unauthorized("No authorization header present")
31+
)
32+
}
33+
}
34+
35+
}
36+
}

app/common/jwt/JwtValidator.scala

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package common.jwt
2+
3+
import com.nimbusds.jwt.JWTClaimsSet
4+
import com.nimbusds.jwt.proc.BadJWTException
5+
6+
final case class JwtToken(content: String) extends AnyVal
7+
8+
sealed abstract class ValidationError(message: String)
9+
extends BadJWTException(message)
10+
case object EmptyJwtTokenContent extends ValidationError("Empty JWT token")
11+
case object AutoInvalidByValidator extends ValidationError("auto-invalid")
12+
case object InvalidJwtToken extends ValidationError("Invalid JWT token")
13+
case object MissingExpirationClaim
14+
extends ValidationError("Missing `exp` claim")
15+
case object InvalidTokenUseClaim
16+
extends ValidationError("Invalid `token_use` claim")
17+
case object InvalidTokenIssuerClaim
18+
extends ValidationError("Invalid `iss` claim")
19+
case object InvalidTokenSubject extends ValidationError("Invalid `sub` claim")
20+
case class UnknownException(exception: Exception)
21+
extends ValidationError("Unknown JWT validation error")
22+
23+
case class DefaultAuthResult(token: JwtToken, jwtClaimSet: JWTClaimsSet)
24+
25+
trait JwtValidator[T] {
26+
def validate(jwtToken: JwtToken): Either[BadJWTException, T]
27+
}

app/common/messages/SlickIO.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package common.messages
2+
3+
object SlickIO {
4+
abstract class Error(message: String, statusCode: Int)
5+
6+
case class SuccessWithStatusCode[T](body: T, status: Int)
7+
8+
case class DatabaseError(message: String, statusCode: Int) extends Error(message = message, statusCode = statusCode)
9+
10+
type Result[T] = Either[Error, T]
11+
12+
}

0 commit comments

Comments
 (0)