diff --git a/api/.dockerignore b/api/.dockerignore new file mode 100644 index 0000000..4fd4896 --- /dev/null +++ b/api/.dockerignore @@ -0,0 +1,263 @@ +### JetBrains+all Patch template +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### JetBrains+all template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### VirtualEnv template +# Virtualenv +# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ +.Python +[Bb]in +[Ii]nclude +[Ll]ib +[Ll]ib64 +[Ll]ocal +[Ss]cripts +pyvenv.cfg +.venv +pip-selfcheck.json + +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..8844340 --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,10 @@ +FROM sbtscala/scala-sbt:graalvm-community-21.0.2_1.10.0_3.4.2 +WORKDIR /app +COPY build.sbt /app/ +COPY project /app/project +COPY src /app/src +RUN sbt update +RUN sbt compile +RUN sbt package +EXPOSE 8080 +CMD ["sbt", "run"] diff --git a/api/build.sbt b/api/build.sbt index 5d17444..811ba02 100644 --- a/api/build.sbt +++ b/api/build.sbt @@ -1,9 +1,24 @@ -ThisBuild / version := "0.1.0-SNAPSHOT" +name := "api" +version := "1.0" +scalaVersion := "3.4.2" -ThisBuild / scalaVersion := "3.4.2" +resolvers += "Akka library repository" at "https://repo.akka.io/maven" -lazy val root = (project in file(".")) - .settings( - name := "api", - libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.18" % "test" +enablePlugins(JavaAppPackaging) + +val scalatestVersion = "3.2.18" +val AkkaVersion = "2.9.3" +val AkkaHttpVersion = "10.6.3" +val MongoDBVersion = "5.1.0" +libraryDependencies ++= { + Seq( + "org.scalatest" %% "scalatest" % scalatestVersion % "test", + "com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion, + "com.typesafe.akka" %% "akka-stream" % AkkaVersion, + "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion, + "org.slf4j" % "slf4j-simple" % "2.0.13", ) +} +libraryDependencies += ("org.mongodb.scala" %% "mongo-scala-driver" % MongoDBVersion).cross( + CrossVersion.for3Use2_13 +) diff --git a/api/docker-compose.yaml b/api/docker-compose.yaml new file mode 100644 index 0000000..4682422 --- /dev/null +++ b/api/docker-compose.yaml @@ -0,0 +1,16 @@ +name: elevator-system + +services: + api: + build: + context: . + ports: + - 8080:8080 + environment: + - DATABASE_URL=mongodb://db:27017 + stdin_open: true + tty: true + db: + image: mongo:7 + ports: + - 27017:27017 diff --git a/api/project/plugins.sbt b/api/project/plugins.sbt new file mode 100644 index 0000000..591f3db --- /dev/null +++ b/api/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.0") diff --git a/api/src/main/scala/App.scala b/api/src/main/scala/App.scala deleted file mode 100644 index 5f871d8..0000000 --- a/api/src/main/scala/App.scala +++ /dev/null @@ -1,6 +0,0 @@ -@main -def main(): Unit = - println("Hello world!") - -def add(a: Int, b: Int): Int = - a + b diff --git a/api/src/main/scala/Database.scala b/api/src/main/scala/Database.scala new file mode 100644 index 0000000..338d729 --- /dev/null +++ b/api/src/main/scala/Database.scala @@ -0,0 +1,27 @@ +import org.mongodb.scala.Document +import org.mongodb.scala.MongoClient +import org.mongodb.scala.MongoCollection + +import tour.Helpers._ + +object Database { + + def test(): Unit = + println("Connecting to MongoDB...") + val mongoClient = MongoClient(sys.env("DATABASE_URL")) + val database = mongoClient.getDatabase("mydb") + database.createCollection("test2") + val collection: MongoCollection[Document] = database.getCollection("test2") + + val document = Document( + "name" -> "MongoDB", + "type" -> "database", + "count" -> 1, + "info" -> Document("x" -> 203, "y" -> 102), + ) + + collection.insertOne(document).results() + collection.find().printResults() + println("Document inserted") + +} diff --git a/api/src/main/scala/Helpers.scala b/api/src/main/scala/Helpers.scala new file mode 100644 index 0000000..eaafb52 --- /dev/null +++ b/api/src/main/scala/Helpers.scala @@ -0,0 +1,56 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tour + +import java.util.concurrent.TimeUnit + +import scala.concurrent.Await +import scala.concurrent.duration.Duration + +import org.mongodb.scala._ + +object Helpers { + + implicit class DocumentObservable[C](val observable: Observable[Document]) + extends ImplicitObservable[Document] { + override val converter: (Document) => String = doc => doc.toJson + } + + implicit class GenericObservable[C](val observable: Observable[C]) extends ImplicitObservable[C] { + override val converter: (C) => String = doc => Option(doc).map(_.toString).getOrElse("") + } + + trait ImplicitObservable[C] { + val observable: Observable[C] + val converter: (C) => String + + def results(): Seq[C] = Await.result(observable.toFuture(), Duration(10, TimeUnit.SECONDS)) + def headResult() = Await.result(observable.head(), Duration(10, TimeUnit.SECONDS)) + + def printResults(initial: String = ""): Unit = { + if (initial.length > 0) + print(initial) + results().foreach(res => println(converter(res))) + } + + def printHeadResult(initial: String = ""): Unit = println( + s"${initial}${converter(headResult())}" + ) + + } + +} diff --git a/api/src/main/scala/Main.scala b/api/src/main/scala/Main.scala new file mode 100644 index 0000000..17dd7d4 --- /dev/null +++ b/api/src/main/scala/Main.scala @@ -0,0 +1,3 @@ +object Main { + def main(args: Array[String]): Unit = Server.run() +} diff --git a/api/src/main/scala/Routes.scala b/api/src/main/scala/Routes.scala new file mode 100644 index 0000000..ee863cc --- /dev/null +++ b/api/src/main/scala/Routes.scala @@ -0,0 +1,17 @@ +import akka.http.scaladsl.model.ContentTypes +import akka.http.scaladsl.model.HttpEntity +import akka.http.scaladsl.server.Directives.complete +import akka.http.scaladsl.server.Directives.get +import akka.http.scaladsl.server.Directives.path +import akka.http.scaladsl.server.Route + +object Routes { + + val route: Route = + path("hello") { + get { + complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "

Say hello to akka-http

")) + } + } + +} diff --git a/api/src/main/scala/Server.scala b/api/src/main/scala/Server.scala new file mode 100644 index 0000000..1c1a902 --- /dev/null +++ b/api/src/main/scala/Server.scala @@ -0,0 +1,30 @@ +import akka.actor.typed.ActorSystem +import akka.actor.typed.scaladsl.Behaviors +import akka.http.scaladsl.Http + +import scala.concurrent.ExecutionContextExecutor +import scala.io.StdIn + +object Server { + + def run(): Unit = + implicit val system: ActorSystem[Any] = ActorSystem(Behaviors.empty, "my-system") + implicit val executionContext: ExecutionContextExecutor = system.executionContext + + val host = "0.0.0.0" + val port = 8080 + + val bindingFuture = Http() + .newServerAt(host, port) + .bind(Routes.route) + + println(s"Server online at http://$host:$port/\nPress RETURN to stop...") + + Database.test() + + StdIn.readLine() + bindingFuture + .flatMap(_.unbind()) + .onComplete(_ => system.terminate()) + +} diff --git a/api/src/test/scala/AppSuite.scala b/api/src/test/scala/AppSuite.scala index 82c8a99..f5701b0 100644 --- a/api/src/test/scala/AppSuite.scala +++ b/api/src/test/scala/AppSuite.scala @@ -2,6 +2,6 @@ import org.scalatest.funsuite.AnyFunSuite class AppSuite extends AnyFunSuite { test("Adding 2 and 2 should produce 4") { - assert(add(2, 2) == 4) + assert(2 + 2 == 4) } -} \ No newline at end of file +}