Skip to content

Commit

Permalink
Use DB, file or memory to store mindmups (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
otrebski authored Oct 21, 2020
1 parent 7eb2da6 commit 3654428
Show file tree
Hide file tree
Showing 16 changed files with 550 additions and 265 deletions.
5 changes: 4 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ lazy val quizz =
library.doobieCore,
library.doobiePostgres,
library.doobieQuill,
library.doobieScalatest % Test
library.doobieScalatest % Test,
// library.tapirHttp4s,
// library.tapirJson,
// library.bazelServer,
// library.bazelClient
library.betterFiles
)
)

Expand All @@ -52,6 +53,7 @@ lazy val library =
val doobie = "0.9.0"
val sttp = "2.2.8"
val sttpTapirJsonCirce = "0.16.16"
val betterFiles = "3.9.1"
}
val scalaCheck = "org.scalacheck" %% "scalacheck" % Version.scalaCheck
val scalaTest = "org.scalatest" %% "scalatest" % Version.scalaTest
Expand All @@ -67,6 +69,7 @@ lazy val library =
val sttpClient = "com.softwaremill.sttp.client" %% "core" % Version.sttp
val sttpClientCirce = "com.softwaremill.sttp.client" %% "circe" % Version.sttp
val tapirJsonCirce = "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % Version.sttpTapirJsonCirce
val betterFiles = "com.github.pathikrit" %% "better-files" % Version.betterFiles

val logback = "ch.qos.logback" % "logback-classic" % Version.logback
val doobieCore = "org.tpolecat" %% "doobie-core" % Version.doobie
Expand Down
28 changes: 25 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,35 @@ services:
quizz:
image: otrebski/quizz:latest
volumes:
- '/tmp/mindmups/:/tmp/mindmups/'
- '/tmp/mindmups/:/mindmups/'
environment:
LOAD_FROM_DIR: "/tmp/mindmups/"
USE_SLACK: "false"
SLACK_TOKEN: ""
FEEDBACK_USE_SLACK: "false"
FEEDBACK_USE_DB: "false"
MINDMUP_STORAGE: "file" #use file, memory or database
FILESTORAGE_DIR: "/mindmups"
# DB_HOST: "db"
# DB_PORT: "5432"
# DB_NAME: "quizz"
# DB_USERNAME: "postgres"
# DB_PASSWORD: "password"
links:
- db


gui:
image: otrebski/quizz-gui:latest
ports:
- 8080:80
links:
- quizz

db:
image: library/postgres:13
# ports:
# - 5432:15432
environment:
POSTGRES_PASSWORD: "password"
POSTGRES_DB: "quizz"
volumes:
- ./postgres-data:/var/lib/postgresql/data
45 changes: 25 additions & 20 deletions src/main/resources/application.conf
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
quizz {
loader {
dir = ""
dir = ${?LOAD_FROM_DIR}
}
}

feedback {
slack {
use = false
use = ${?USE_SLACK}
use = ${?FEEDBACK_USE_SLACK}
token = ""
token = ${?SLACK_TOKEN}
}
database {
use = false
use = ${?FEEDBACK_USE_DB}
}
}

mindmup {
store-type = "file" //file, database or memory
store-type = ${?MINDMUP_STORAGE}
}

filestorage {
dir = "mindmups"
dir = ${?FILESTORAGE_DIR}
}

database {
use = false
use = ${?USE_DB}
host = "localhost"
host = ${?DB_HOST}
port = 5432
port = ${?DB_PORT}
dbname = "quizz"
dbname = ${?DB_NAME}
user = "postgres"
user = ${?DB_USERNAME}
password = "password"
password = ${?DB_PASSWORD}
host = "localhost"
host = ${?DB_HOST}
port = 5432
port = ${?DB_PORT}
dbname = "quizz"
dbname = ${?DB_NAME}
user = "postgres"
user = ${?DB_USERNAME}
password = "password"
password = ${?DB_PASSWORD}
}
4 changes: 3 additions & 1 deletion src/main/scala/mindmup/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ object Parser extends LazyLogging {
val r: Either[Error, V3IdString.Mindmap] = parsedJson.flatMap(mindMupDecoder.decodeJson)
r match {
case Left(e) =>
logger.info(s"Parsing was not successful due to ${e.getMessage}, will try different parser")
logger.debug(
s"Parsing was not successful due to ${e.getMessage}, will try different parser"
)
parsedJson.flatMap(mindMupDecoderInt.decodeJson).map(_.toV3IdString) match {
case Left(_) =>
logger.info(
Expand Down
1 change: 0 additions & 1 deletion src/main/scala/mindmup/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ package object mindmup extends LazyLogging {
label.getOrElse("?") -> toStep(v)
}
Question(id, title, stringToStep)

}
}

Expand Down
10 changes: 5 additions & 5 deletions src/main/scala/quizz/data/ExamplesData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import cats.syntax.show._
import com.typesafe.scalalogging.LazyLogging
import mindmup.Parser
import quizz.model.{ FailureStep, Question, Quizz, SuccessStep }
import quizz.web.WebApp.Api.{ Answer, HistoryStep, QuizzState, Step }
import quizz.web.Api.{ Answer, HistoryStep, QuizzState, Step }

object ExamplesData extends LazyLogging {

val quiz = Question(
val quiz: Question = Question(
"root",
"What kind of problem do you have",
Map(
Expand Down Expand Up @@ -117,7 +117,7 @@ object ExamplesData extends LazyLogging {

object Fake {

val exampleStateInProgress = QuizzState(
val exampleStateInProgress: QuizzState = QuizzState(
path = "root",
currentStep = Step(
"a",
Expand Down Expand Up @@ -154,12 +154,12 @@ object ExamplesData extends LazyLogging {
)
)
)
val exampleStateFinalSuccess = QuizzState(
val exampleStateFinalSuccess: QuizzState = QuizzState(
path = "asdfsdf",
currentStep = Step("a", "I co dalej?", List.empty, success = Some(true)),
history = List()
)
val exampleStateFinalFailure = QuizzState(
val exampleStateFinalFailure: QuizzState = QuizzState(
path = "asdfsdf",
currentStep = Step("a", "I co dalej?", List.empty, success = Some(false)),
history = List()
Expand Down
142 changes: 142 additions & 0 deletions src/main/scala/quizz/data/MindmupStore.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package quizz.data

import cats.effect.{ Async, ContextShift, IO, Sync }

import scala.language.higherKinds
import better.files._
import cats.{ Applicative, FlatMap }
import cats.effect.concurrent.Ref
import doobie.Transactor
import doobie.util.transactor.Transactor.Aux
import quizz.db.DatabaseConfig

trait MindmupStore[F[_]] {

def store(name: String, content: String): F[Unit]

def listNames(): F[Set[String]]

def load(name: String): F[String]

def delete(name: String): F[Unit]
}

object FileMindmupStore {
def apply[F[_]](dir: File)(implicit ev: Sync[F]): F[FileMindmupStore[F]] =
Sync[F].delay {
dir.createDirectoryIfNotExists(createParents = true)
new FileMindmupStore(dir)
}
}

class FileMindmupStore[F[_]: Sync](dir: File) extends MindmupStore[F] {

override def store(name: String, content: String): F[Unit] =
Sync[F].delay {
(dir / name).overwrite(content)
}

override def listNames(): F[Set[String]] =
Sync[F].delay {
dir.list
.filter(_.isRegularFile)
.map(_.name)
.toSet
}

override def load(name: String): F[String] =
Sync[F].delay {
(dir / name).contentAsString
}

override def delete(name: String): F[Unit] =
Sync[F].delay {
val toDelete = dir / name
toDelete.delete(swallowIOExceptions = false)
}
}

object MemoryMindmupStore {
def apply[F[_]]()(implicit ev: Sync[F]): F[MemoryMindmupStore[F]] = {
val ref: F[Ref[F, Map[String, String]]] =
Ref.of[F, Map[String, String]](Map.empty[String, String])
Applicative[F].map(ref)(r => new MemoryMindmupStore[F](r))
}

}

class MemoryMindmupStore[F[_]: Sync](ref: Ref[F, Map[String, String]]) extends MindmupStore[F] {
override def store(name: String, content: String): F[Unit] =
ref.update(x => x.updated(name, content))

override def listNames(): F[Set[String]] =
FlatMap[F].flatMap(ref.get)(m => Applicative[F].pure(m.toList.map(_._1).toSet))

override def load(name: String): F[String] =
FlatMap[F].flatMap(ref.get)(m => Applicative[F].pure(m(name)))

override def delete(name: String): F[Unit] =
FlatMap[F].flatMap(ref.update(v => v.removed(name)))(_ => Applicative[F].pure(()))
}

object DbMindMupStore {
def apply[F[_]](
dbConfig: DatabaseConfig
)(implicit ec2: Async[F], ev3: ContextShift[F]): F[DbMindMupStore[F]] =
Async[F].delay {

implicit val cs: ContextShift[IO] = IO.contextShift(scala.concurrent.ExecutionContext.global)

val xa: Aux[F, Unit] = Transactor.fromDriverManager[F](
driver = "org.postgresql.Driver",
url = s"jdbc:postgresql://${dbConfig.host}:${dbConfig.port}/${dbConfig.database}",
user = dbConfig.user,
pass = dbConfig.password
)

new DbMindMupStore(xa)
}
}

class DbMindMupStore[F[_]: Async: ContextShift](xa: Aux[F, Unit]) extends MindmupStore[F] {

case class Mindmup(id: String, json: String)

import doobie.quill.DoobieContext
import io.getquill.Literal

val dc = new DoobieContext.Postgres(Literal) // Literal naming scheme

import dc._
import doobie.implicits._

override def store(name: String, json: String): F[Unit] = {
val q: dc.Quoted[dc.Insert[Mindmup]] = quote {
query[Mindmup]
.insert(lift(Mindmup(name, json)))
.onConflictUpdate(_.id)((t, e) => t.json -> e.json)
}
Applicative[F].map(run(q).transact(xa))(_ => ())
}

override def listNames(): F[Set[String]] = {
val q = quote {
query[Mindmup].map(_.id)
}
Applicative[F].map(run(q).transact(xa))(_.toSet)
}

override def load(name: String): F[String] = {
val q = quote {
query[Mindmup].map(x => x.json)
}
Applicative[F].map(run(q).transact(xa))(_.head)
}

override def delete(name: String): F[Unit] = {
val q = quote {
query[Mindmup].filter(_.id == lift(name)).delete
}
Applicative[F].map(run(q).transact(xa))(_ => ())
}
}
36 changes: 23 additions & 13 deletions src/main/scala/quizz/db/DatabaseInitializer.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package quizz.db

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

object DatabaseInitializer {

def initDatabase(cfg: DatabaseConfig): IO[Int] = {
import cats.effect._
import doobie._
import doobie.implicits._
import doobie.util.ExecutionContexts
import cats.effect._
implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContexts.synchronous)

val xa = Transactor.fromDriverManager[IO](
Expand All @@ -17,18 +18,27 @@ object DatabaseInitializer {
cfg.user, // user
cfg.password // password
)
val create =
sql""" CREATE TABLE IF NOT EXISTS feedback
(
id SERIAL PRIMARY KEY,
timestamp timestamp NOT NULL,
quizzId varchar(300) NOT NULL,
path varchar(2000) NOT NULL,
comment varchar(5000) NOT NULL,
rate INT NOT NULL
);
""".update.run
create.transact(xa)

val createFeedback: doobie.ConnectionIO[Int] =
sql"""CREATE TABLE IF NOT EXISTS feedback
|(
| id SERIAL PRIMARY KEY,
| timestamp timestamp NOT NULL,
| quizzId varchar(300) NOT NULL,
| path varchar(2000) NOT NULL,
| comment varchar(5000) NOT NULL,
| rate INT NOT NULL
|);
| """.stripMargin.update.run
val createMindmup: doobie.ConnectionIO[Int] =
sql"""CREATE TABLE IF NOT EXISTS mindmup
|(
| id varchar(500) primary key,
| json varchar(100000)
|)""".stripMargin.update.run

(createFeedback, createMindmup).mapN(_ + _).transact(xa)

}

}
2 changes: 1 addition & 1 deletion src/main/scala/quizz/feedback/FeedbackSender.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.typesafe.scalalogging.LazyLogging
import doobie.quill.DoobieContext
import io.getquill.Literal
import quizz.db.{ DatabaseConfig, Feedback }
import quizz.web.WebApp.Api.{ FeedbackSend, QuizzState }
import quizz.web.Api.{ FeedbackSend, QuizzState }

import scala.language.higherKinds

Expand Down
Loading

0 comments on commit 3654428

Please sign in to comment.