Skip to content

Commit

Permalink
Store feedback in PostgreSQL (#10)
Browse files Browse the repository at this point in the history
Store feedback in PostgresSql
  • Loading branch information
otrebski authored Oct 12, 2020
1 parent 6a6919f commit 14996ef
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 48 deletions.
15 changes: 15 additions & 0 deletions src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,19 @@ feedback {
token = ""
token = ${?SLACK_TOKEN}
}
}

database {
use = true
user = ${?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}
}
34 changes: 34 additions & 0 deletions src/main/scala/quizz/db/DatabaseInitializer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package quizz.db

import cats.effect.IO

object DatabaseInitializer {

def initDatabase(cfg: DatabaseConfig): IO[Int] = {
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](
"org.postgresql.Driver", // driver classname
s"jdbc:postgresql://${cfg.host}:${cfg.port}/${cfg.database}", // connect URL (driver-specific)
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)
}

}
12 changes: 12 additions & 0 deletions src/main/scala/quizz/db/Feedback.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package quizz.db

import java.util.Date

case class Feedback(
id: Int,
timestamp: Date,
quizzId: String,
path: String,
comment: String,
rate: Int
)
24 changes: 24 additions & 0 deletions src/main/scala/quizz/db/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package quizz

import com.typesafe.config.Config

package object db {

case class DatabaseConfig(
host: String,
port: Int,
database: String,
user: String,
password: String
)

def databaseConfig(config: Config): DatabaseConfig =
DatabaseConfig(
host = config.getString("database.host"),
port = config.getInt("database.port"),
database = config.getString("database.dbname"),
user = config.getString("database.user"),
password = config.getString("database.password")
)

}
79 changes: 64 additions & 15 deletions src/main/scala/quizz/feedback/FeedbackSender.scala
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
package quizz.feedback

import cats.effect.Sync
import cats.effect.{ Clock, IO, Sync }
import cats.syntax.option._
import com.typesafe.scalalogging.LazyLogging
import quizz.web.WebApp.Api.{ Feedback, QuizzState }
import doobie.quill.DoobieContext
import io.getquill.Literal
import quizz.db.{ DatabaseConfig, Feedback }
import quizz.web.WebApp.Api.{ FeedbackSend, QuizzState }

import scala.language.higherKinds

trait FeedbackSender[F[_]] {

def send(feedback: Feedback, quizzState: QuizzState): F[Unit]
def send(feedback: FeedbackSend, quizzState: QuizzState): F[Unit]
}

class LogFeedbackSender[F[_]: Sync] extends FeedbackSender[F] with LazyLogging {
override def send(feedback: Feedback, quizzState: QuizzState): F[Unit] =
override def send(feedback: FeedbackSend, quizzState: QuizzState): F[Unit] =
Sync[F].delay(logger.info(s"Have feedback: $feedback for $quizzState"))
}

object SlackFeedbackSender {

case class SlackMessage(blocks: List[Block])

case class Block(text: Text, `type`: String = "section", block_id: Option[String] = None)

case class Text(text: String, `type`: String = "mrkdwn")

private def feedbackIcon(feedback: Feedback): String =
private def feedbackIcon(feedback: FeedbackSend): String =
feedback.rate match {
case a if a > 0 => ":+1:"
case a if a < 0 => ":-1:"
case a if a == 0 => ":point_right:"
}

def convertFeedback(feedback: Feedback, quizzState: QuizzState): SlackMessage = {
def convertFeedback(feedback: FeedbackSend, quizzState: QuizzState): SlackMessage = {
val history: List[Block] = quizzState.history.foldRight(List.empty[Block]) {
(historyStep, list) =>
val answers = historyStep.answers
.map(answer =>
s" - ${answer.selected.map(if (_) ":ballot_box_with_check:" else ":black_square_button:").getOrElse(":black_square_button:")} ${answer.text}"
s" - ${answer.selected
.map(if (_) ":ballot_box_with_check:" else ":black_square_button:")
.getOrElse(":black_square_button:")} ${answer.text}"
)
.mkString("\n")
Block(
Expand Down Expand Up @@ -73,18 +81,13 @@ object SlackFeedbackSender {

class SlackFeedbackSender[F[_]: Sync](token: String) extends FeedbackSender[F] {

override def send(feedback: Feedback, quizzState: QuizzState): F[Unit] =
override def send(feedback: FeedbackSend, quizzState: QuizzState): F[Unit] =
Sync[F].delay {
import sttp.client.circe._

val url: String = s"https://hooks.slack.com/services/$token"
import sttp.client._
val uri = uri"$url"
val rate = feedback.rate match {
case a if a > 0 => ":+1:"
case a if a < 0 => ":-1:"
case a if a == 0 => ":point_right:"
}

val message = SlackFeedbackSender.convertFeedback(feedback, quizzState)
import io.circe.generic.auto._
Expand All @@ -95,7 +98,53 @@ class SlackFeedbackSender[F[_]: Sync](token: String) extends FeedbackSender[F] {
.body(message)

implicit val backend: SttpBackend[Identity, Nothing, NothingT] = HttpURLConnectionBackend()
val response: Identity[Response[Either[String, String]]] = myRequest.send()
response
myRequest.send()
}
}

class FeedbackDBSender(dbConfig: DatabaseConfig)(implicit clock: Clock[IO])
extends FeedbackSender[IO] {

import cats.effect._
import doobie._

val trivial = LogHandler(e => Console.println("*** " + e))
val dc = new DoobieContext.Postgres(Literal) // Literal naming scheme

import dc._

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

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

override def send(feedback: FeedbackSend, quizzState: QuizzState): IO[Unit] = {
import java.util.Date

import doobie.implicits._
for {
now <- clock.realTime(scala.concurrent.duration.MILLISECONDS)
fb = Feedback(
id = 0,
timestamp = new Date(now),
quizzId = feedback.quizzId,
path = feedback.path,
comment = feedback.comment,
rate = feedback.rate
)
_ <- addFeedbackToDb(fb).transact(xa)
} yield ()
}

protected def addFeedbackToDb(feedback: Feedback): doobie.ConnectionIO[Index] = {
val q1 = quote {
query[Feedback].insert(lift(feedback)).returningGenerated(_.id)
}
run(q1)
}
}
Loading

0 comments on commit 14996ef

Please sign in to comment.