Skip to content

Commit

Permalink
add backend project
Browse files Browse the repository at this point in the history
  • Loading branch information
Babali42 committed Dec 18, 2024
1 parent ec23795 commit cc07383
Show file tree
Hide file tree
Showing 22 changed files with 411 additions and 0 deletions.
51 changes: 51 additions & 0 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Scala CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
build:
defaults:
run:
working-directory: ./backend

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'temurin'
cache: 'sbt'

- name: Start MongoDB
uses: supercharge/[email protected]
with:
mongodb-version: '8.0'
mongodb-username: admin
mongodb-password: pass
mongodb-db: auth

- name: Set API Key
env:
PLAY_HTTP_SECRET_KEY: ${{ secrets.PLAY_HTTP_SECRET_KEY }}
run: echo "PLAY_HTTP_SECRET_KEY set from GitHub Secrets"

- name: Run tests
env:
PLAY_HTTP_SECRET_KEY: ${{ secrets.PLAY_HTTP_SECRET_KEY }}
run: sbt test
11 changes: 11 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
logs
target
data
/.bsp
/.idea
/.idea_modules
/.classpath
/.project
/.settings
/RUNNING_PID
.env
33 changes: 33 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Use an official Scala image as the base image
FROM sbtscala/scala-sbt:graalvm-ce-22.3.3-b1-java17_1.10.1_3.4.2 AS builder

# Set the working directory inside the container
WORKDIR /app

ARG ENV=prod
ENV ENV=$ENV

# Copy the project files into the container
COPY . .

# Compile the application and create a distribution package
RUN sbt clean compile stage

# Use a lightweight JRE image for running the app
FROM openjdk:11-jre-slim

# Define a build argument
ARG API_KEY
ENV PLAY_HTTP_SECRET_KEY=${API_KEY}

# Set the working directory inside the container
WORKDIR /app

# Copy the distribution package from the builder stage
COPY --from=builder /app/target/universal/stage /app

# Expose the port that your Play app listens on
EXPOSE 9000

# Set the entry point to start the Play application
ENTRYPOINT ["./bin/drum-beat-repo-backend"]
3 changes: 3 additions & 0 deletions backend/app/domain/Beat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package domain

case class Beat(id: String, label: String, bpm: Int)
11 changes: 11 additions & 0 deletions backend/app/domain/BeatRepository.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package domain

import com.google.inject.ImplementedBy
import secondary.MongoBeatRepository

import scala.concurrent.Future

@ImplementedBy(classOf[MongoBeatRepository])
trait BeatRepository {
def getAllBeats: Future[Seq[Beat]]
}
24 changes: 24 additions & 0 deletions backend/app/primary/GenreController.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package primary

import domain.Beat
import play.api.libs.json._
import play.api.mvc._
import secondary.repositoryFactory.BeatRepositoryFactory

import javax.inject.{Inject, Singleton}
import scala.concurrent.ExecutionContext

@Singleton class GenreController @Inject()(val controllerComponents: ControllerComponents, val factory: BeatRepositoryFactory)(implicit ec: ExecutionContext) extends BaseController {

def getGenres: Action[AnyContent] = Action.async { _ =>
val connectionString = sys.env.getOrElse("DATABASE_CONNECTION_STRING", "mongodb://admin:pass@localhost:27017")
val repository = factory.createMongoRepository(connectionString, "drum-beat-database")

repository.getAllBeats.map { documents =>
implicit val beatWrites: OWrites[Beat] = Json.writes[Beat]
val beats = documents.map(x => Json.toJson(x))
Ok(Json.toJson(beats))
}.recover { case ex: Throwable => InternalServerError(s"Error fetching genres: ${ex.getMessage}")
}
}
}
19 changes: 19 additions & 0 deletions backend/app/secondary/MongoBeatRepository.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package secondary

import domain.{Beat, BeatRepository}
import org.mongodb.scala.bson.collection.immutable.Document
import org.mongodb.scala.{MongoCollection, MongoDatabase}

import scala.concurrent.{ExecutionContext, Future}

class MongoBeatRepository(database: MongoDatabase, collectionName: String = "beats") extends BeatRepository {
private lazy val collection: MongoCollection[Document] = database.getCollection(collectionName)

private def documentToUser(doc: Document): Beat = {
Beat(doc.getOrElse("id", "").asString().getValue, doc.getOrElse("label", "").asString().getValue, doc.getOrElse("bpm", 0).asInt32().getValue)
}

override def getAllBeats: Future[Seq[Beat]] = {
collection.find().toFuture().map(documents => documents.map(documentToUser))(ExecutionContext.global)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package secondary.repositoryFactory

import com.google.inject.ImplementedBy
import domain.BeatRepository

@ImplementedBy(classOf[MongoBeatRepositoryFactory])
trait BeatRepositoryFactory {
def createMongoRepository(connectionString: String, databaseName: String): BeatRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package secondary.repositoryFactory
import domain.{Beat, BeatRepository}

import scala.concurrent.Future

class InMemoryBeatRepositoryFactory extends BeatRepositoryFactory {

override def createMongoRepository(connectionString: String, databaseName: String): BeatRepository = {
new BeatRepository {
override def getAllBeats: Future[Seq[Beat]] = Future.successful(Seq(Beat("0", "Techno", 128), Beat("0", "Metal", 128)))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package secondary.repositoryFactory

import domain.BeatRepository
import org.mongodb.scala.MongoClient
import secondary.MongoBeatRepository

class MongoBeatRepositoryFactory extends BeatRepositoryFactory {
override def createMongoRepository(connectionString: String, databaseName: String): BeatRepository = {
val mongoClient = MongoClient(connectionString)
val database = mongoClient.getDatabase(databaseName)
new MongoBeatRepository(database)
}
}
14 changes: 14 additions & 0 deletions backend/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name := """drum-beat-repo-backend"""
organization := "babali"

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)
val currentEnv: String = sys.env.getOrElse("ENV", "development")

scalaVersion := "2.13.15"
coverageEnabled := (currentEnv == "development")

libraryDependencies += guice
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.1" % Test
libraryDependencies += "org.mongodb.scala" %% "mongo-scala-driver" % "4.10.1"
6 changes: 6 additions & 0 deletions backend/conf/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# https://www.playframework.com/documentation/latest/Configuration
play.http.secret.key=${?PLAY_HTTP_SECRET_KEY}
play.filters.hosts {
# Allow requests to example.com, its subdomains, and localhost:9000.
allowed = ["drum-beat-repo-backend.onrender.com", "localhost:9000", "localhost", "drum-beat-repo-app.onrender.com"]
}
50 changes: 50 additions & 0 deletions backend/conf/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" ?>

<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->

<!DOCTYPE configuration>

<configuration>
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
<import class="ch.qos.logback.classic.AsyncAppender"/>
<import class="ch.qos.logback.core.FileAppender"/>
<import class="ch.qos.logback.core.ConsoleAppender"/>

<appender name="FILE" class="FileAppender">
<file>${application.home:-.}/logs/application.log</file>
<encoder class="PatternLayoutEncoder">
<charset>UTF-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{36}) %magenta(%X{pekkoSource}) %msg%n</pattern>
</encoder>
</appender>

<appender name="STDOUT" class="ConsoleAppender">
<!--
On Windows, enabling Jansi is recommended to benefit from color code interpretation on DOS command prompts,
which otherwise risk being sent ANSI escape sequences that they cannot interpret.
See https://logback.qos.ch/manual/layouts.html#coloring
-->
<!-- <withJansi>true</withJansi> -->
<encoder class="PatternLayoutEncoder">
<charset>UTF-8</charset>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) %cyan(%logger{36}) %magenta(%X{pekkoSource}) %msg%n</pattern>
</encoder>
</appender>

<appender name="ASYNCFILE" class="AsyncAppender">
<appender-ref ref="FILE"/>
</appender>

<appender name="ASYNCSTDOUT" class="AsyncAppender">
<appender-ref ref="STDOUT"/>
</appender>

<logger name="play" level="INFO"/>
<logger name="application" level="DEBUG"/>

<root level="WARN">
<appender-ref ref="ASYNCFILE"/>
<appender-ref ref="ASYNCSTDOUT"/>
</root>

</configuration>
1 change: 1 addition & 0 deletions backend/conf/messages
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# https://www.playframework.com/documentation/latest/ScalaI18N
1 change: 1 addition & 0 deletions backend/conf/routes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GET /genres/ primary.GenreController.getGenres()
36 changes: 36 additions & 0 deletions backend/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
services:
mongo-db-service:
container_name: mongo-db
image: mongo:4.2
environment:
- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_DATABASE=auth
- MONGO_INITDB_ROOT_PASSWORD=pass
ports:
- '27017:27017'
volumes:
- mongo-data:/data/db

mongo-express-service:
container_name: mongo-express
image: mongo-express
environment:
- ME_CONFIG_MONGODB_SERVER=mongo-dev
- ME_CONFIG_MONGODB_ADMINUSERNAME=admin
- ME_CONFIG_MONGODB_ADMINPASSWORD=pass
- ME_CONFIG_BASICAUTH=false
ports:
- '8081:8081'

drum-beat-repo-app-service:
container_name: drum-beat-repo-app
image: drum-beat-repo-app-image
environment:
- DATABASE_CONNECTION_STRING=mongodb://admin:pass@mongo-db-service:27017
ports:
- "9000:9000"
depends_on:
- mongo-db-service

volumes:
mongo-data:
1 change: 1 addition & 0 deletions backend/project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=1.10.5
3 changes: 3 additions & 0 deletions backend/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
addSbtPlugin("org.playframework" % "sbt-plugin" % "3.0.6")
addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.16.2")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.2.1")
42 changes: 42 additions & 0 deletions backend/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Drum Beat Repo back-end

## Run

Run mongo db & mongo express

```docker-compose up```

```sbt run```

## Execute the tests

```docker-compose up```

```sbt test```

## Useful links

- Drum Beat Database in express :
http://localhost:8081/db/drum-beat-database/
- Get genres api : http://localhost:9000/genres/

## Coverage

- Run coverage
- ``sbt coverage test``
- Generate report
- ``sbt coverageReport``


## How to build docker image

Build image

``` docker build -t drum-beat-repo-app-image . ```

``` docker build --build-arg API_KEY='<YOURAPIKEY>' -t drum-beat-repo-app-image . ```

Run a container with this image

``` docker run -p 9000:9000 -it drum-beat-repo-app-image ```

10 changes: 10 additions & 0 deletions backend/render.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
# Backend Application Service
- type: web
name: drum-beat-repo-app
plan: free
env: docker
dockerfilePath: ./Dockerfile
envVars:
- key: DATABASE_CONNECTION_STRING
value: mongodb://admin:pass@mongo-db-service:27017
Loading

0 comments on commit cc07383

Please sign in to comment.