Skip to content

Commit

Permalink
GCE and service integration (#11)
Browse files Browse the repository at this point in the history
Basic hardcoded integration and kube deployment.

* Fetch client for fetching history, reseting history, and adding new entries

* Created JSON encoders and decoders

* Add entry adjustments, reduced cluster size

* Updated readmes, to reflect the multi-module nature of the project

* Additional .gitignore entries for erroneous target directories

* Created deployment configuration for Google Cloud
  • Loading branch information
RawToast committed Mar 25, 2018
1 parent 34c54cd commit 4522ba0
Show file tree
Hide file tree
Showing 28 changed files with 492 additions and 159 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.vscode
.idea
*.iml
target/
project/target/
4 changes: 2 additions & 2 deletions bin/build-images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

eval $(minikube docker-env)

docker build ../dokusho -t dokusho:web
docker build ../dokusho -t gcr.io/dokusho-199010/dokusho-web

docker build ../dokusho-server -t dokusho:server
docker build ../dokusho-server -t gcr.io/dokusho-199010/dokusho-server
9 changes: 9 additions & 0 deletions bin/gcloud-images.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

docker build ../dokusho -t gcr.io/dokusho-199010/dokusho-web

docker build ../dokusho-server -t gcr.io/dokusho-199010/dokusho-server

gcloud docker -- push gcr.io/dokusho-199010/dokusho-web:latest

gcloud docker -- push gcr.io/dokusho-199010/dokusho-server:latest
14 changes: 11 additions & 3 deletions dokusho-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@

[![Build Status](https://travis-matrix-badges.herokuapp.com/repos/RawToast/dokusho/branches/master/2)](https://travis-ci.org/RawToast/dokusho)

Backend server for Dokusho. This is a work in progress and is not currently being used by the frontend.
Throwaway backend server for Dokusho. This is a work in progress service for experimenting and empowering frontend development.

Once the frontend reaches a reasonable level of functionality, this backend service will be revisited.

## Running the backend

### Using sbt

`sbt run` will compile and start a server listening on `8080`

### Docker
### Endpoints

* Fetch the reading history for a user
* GET `/user/<userID>`
* Replace a user's reading history
* PUT `user/<userID>`
* Add a new entry to the user's reading history
* POST `user/<userID>/add`

Currently, no docker configuration has been craeted for this module.

## Additional Information

Expand Down
12 changes: 6 additions & 6 deletions dokusho-server/src/main/scala/Main.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import cats.effect._
import dokusho.{MongoRepository, MongoService}
import org.http4s.dsl.io._
import org.http4s.server.blaze.BlazeBuilder
import fs2.{Stream, StreamApp}
import dokusho.{MongoRepository, ReadingHistoryRouter, ReadingHistoryService}
import fs2.StreamApp.ExitCode
import fs2.{Stream, StreamApp}
import org.http4s.server.ServerBuilder
import org.http4s.server.blaze.BlazeBuilder

import scala.concurrent.ExecutionContext.Implicits.global

Expand All @@ -16,13 +15,14 @@ object Main extends StreamApp[IO] {
"test",
"dokusho")

val mongoService = new MongoService(mongo)
val readingHistoryService = new ReadingHistoryService(mongo)
val historyService = new ReadingHistoryRouter(readingHistoryService)


override def stream(args: List[String], requestShutdown: IO[Unit]): Stream[IO, ExitCode] =
BlazeBuilder[IO]
.bindHttp(8080, "0.0.0.0")
.mountService(mongoService.routes, "/")
.mountService(historyService.routes, "/")
.withBanner(ServerBuilder.DefaultBanner)
.serve
}
31 changes: 4 additions & 27 deletions dokusho-server/src/main/scala/dokusho/MongoRepository.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package dokusho


import cats.data.OptionT
import cats.effect.IO
import io.circe.generic.auto._
Expand All @@ -27,44 +26,22 @@ class MongoRepository(connectionString: String, databaseName: String, collection
.getCollection(collectionName)

def get(userId: String): IO[Option[UserReadingHistory]] =
OptionT(getDocumentIoOpt(userId))
OptionT(getDocument(userId).asIOOpt)
.semiflatMap(toUserReadingHistory)
.value


def getUnsafe(userId: String): IO[UserReadingHistory] =
getDocumentIO(userId)
getDocument(userId)
.asIO
.flatMap(toUserReadingHistory)

def put(g: UserReadingHistory): IO[UserReadingHistory] =
collection.replaceOne(equal("uuid", g.userId.toString), Document.parse(g.asJson.spaces2),
collection.replaceOne(equal("userId", g.userId), Document.parse(g.asJson.spaces2),
model.UpdateOptions().upsert(true)).asIO
.map(_ => g)

def addEntry(userId: String, date: String, entry: Entry): IO[UserReadingHistory] = {
for {
urh <- getUnsafe(userId)
days = urh.readingHistory.days
dayToUpdate = days.find(_.date == userId).getOrElse(Day(date, Seq.empty))
updatedDay = entriesLens.modify(_ :+ entry)(dayToUpdate)
doc = daysLens.modify(upsertDay(updatedDay))(urh)
} yield doc
}

private def upsertDay(day: Day)(days: Seq[Day]): Seq[Day] =
if (days.exists(_.date == day.date)) {
days.withFilter(_.date == day.date)
.map(_ => day)
} else {
days :+ day
}

private def getDocument(id: String): FindObservable[Document] = collection.find(equal("userId", id))

private def getDocumentIO(id: String): IO[Document] = getDocument(id).asIO

private def getDocumentIoOpt(id: String): IO[Option[Document]] = getDocument(id).asIOOpt

private def toUserReadingHistory(task: Document): IO[UserReadingHistory] = {
for {
json <- parseJson(task.toJson)
Expand Down
37 changes: 0 additions & 37 deletions dokusho-server/src/main/scala/dokusho/MongoService.scala

This file was deleted.

43 changes: 43 additions & 0 deletions dokusho-server/src/main/scala/dokusho/ReadingHistoryRouter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package dokusho

import cats.effect.IO
import io.circe.Json
import io.circe.generic.auto._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.dsl.io.{->, /, GET, Ok, Root, _}

class ReadingHistoryRouter(readingHistoryService: ReadingHistoryService) {

case class SuccessfulPut(userId: String)

val routes: HttpService[IO] = HttpService[IO] {
case GET -> Root / "history" / userId =>
for {
userReadingHistory <- readingHistoryService.getReadingHistory(userId)
json: Option[Json] = userReadingHistory.map(_.asJson)
resp <- json.fold(NotFound())(j => Ok(j))
} yield resp
case req@PUT -> Root / "history" / userId =>
implicit val userDecoder: EntityDecoder[IO, ReadingHistory] = jsonOf[IO, ReadingHistory]
for {
readingHistory <- req.as[ReadingHistory]
storedHistory <- readingHistoryService.upsert(UserReadingHistory(userId, readingHistory))
json: Json = SuccessfulPut(storedHistory.userId).asJson
response <- Ok(json)
} yield response
case req@POST -> Root / "history" / userId / "add" =>
implicit val entryDecoder: EntityDecoder[IO, NewEntry] = jsonOf[IO, NewEntry]
for {
entry <- req.as[NewEntry]
storedHistory <- readingHistoryService.addNewEntry(userId, entry)
json = storedHistory.map(_.asJson)
result <- json.fold(NotFound())(j => Ok(j))
} yield result
case PUT -> Root / "history" / userId / "reset" =>
readingHistoryService.reset(userId)
.map(_.asJson)
.flatMap(j => Ok(j))
}
}
52 changes: 52 additions & 0 deletions dokusho-server/src/main/scala/dokusho/ReadingHistoryService.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package dokusho

import java.time.LocalDate

import cats.data.OptionT
import cats.effect.IO
import monocle.macros.GenLens
import org.bson.Document
import org.mongodb.scala.model
import org.mongodb.scala.model.Filters.equal

class ReadingHistoryService(mongoRepository: MongoRepository) {

private lazy val daysLens = GenLens[UserReadingHistory](_.readingHistory.days)
private lazy val entriesLens = GenLens[Day](_.entries)

def getReadingHistory(userId: String): IO[Option[UserReadingHistory]] =
mongoRepository.get(userId)

def addNewEntry(userId: String, entry: NewEntry): IO[Option[UserReadingHistory]] = {
lazy val update = daysLens.modify(updateDay(entry))
OptionT(getReadingHistory(userId))
.map(update)
.semiflatMap(upsert)
.value
}

def upsert(userReadingHistory: UserReadingHistory): IO[UserReadingHistory] =
mongoRepository.put(userReadingHistory)

def reset(userId: String): IO[UserReadingHistory] = {
val emptyHistory = UserReadingHistory(userId, ReadingHistory(Seq.empty))
upsert(emptyHistory)
}

private def updateDay(entry: NewEntry)(days: Seq[Day]) = {
val currentDay = Day(LocalDate.now().atStartOfDay().toString, Seq.empty)

val daysWithUpdatedDay =
if (days.exists(_.date == currentDay.date)) days
else currentDay +: days

daysWithUpdatedDay.withFilter(_.date == currentDay.date)
.map(addEntry(entry))
}

private def addEntry(entry: NewEntry) = entriesLens
.modify { es => Entry(getNextId(es), entry.kind, entry.value) +: es}

private def getNextId(entries: Seq[Entry]) = 1l +
entries.foldLeft(-1l)((currMax, entry) => Math.max(currMax, entry.id))
}
3 changes: 2 additions & 1 deletion dokusho-server/src/main/scala/dokusho/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ package object dokusho {
case class Day(date: String, entries: Seq[Entry])

case class Entry(id: Long, kind: PageType, value: Int)
case class Testy(id: Long, value: Int)

case class NewEntry(kind: PageType, value: Int)

sealed trait PageType

Expand Down
2 changes: 2 additions & 0 deletions dokusho/bsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"bs-dependencies": [
"reason-react",
"bs-jest",
"@glennsl/bs-json",
"bs-fetch",
"rationale"
],
"bs-dev-dependencies" : [
Expand Down
6 changes: 5 additions & 1 deletion dokusho/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@glennsl/bs-json": "^1.1.3",
"bs-fetch": "^0.2.1",
"isomorphic-fetch": "^2.2.1",
"rationale": "^0.1.3",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"reason-scripts": "0.8.0"
"reason-scripts": "0.8.0",
"refetch": "glennsl/refetch"
},
"scripts": {
"start": "react-scripts start",
Expand Down
51 changes: 51 additions & 0 deletions dokusho/src/app/Client.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[%raw "require('isomorphic-fetch')"];

open Types;

module Client = {
type serverResponse = {
userId: string,
readingHistory: readingHistory
};
let parseResponse = (json: Js.Json.t) : serverResponse =>
Json.Decode.{
userId: json |> field("userId", string),
readingHistory: json |> field("readingHistory", Decoders.parseHistory)
};
let backendURI = "http://35.189.70.144:8080";
/* let backendURI = "http://localhost:8080"; */
let jsonHeader = Fetch.HeadersInit.make({"Content-Type": "application/json"});

/* Fetches the given user's reading history */
let userHistory = (userId:string) =>
Js.Promise.(
Fetch.fetch(backendURI ++ "/history/" ++ userId)
|> then_(Fetch.Response.json)
|> then_(resp => resp |> parseResponse |> resolve)
);

/* Adds a new reading entry for today to a user's reading history */
let newEntry = (userId:string, kind: pageType, value: int) => {
Js.Promise.(
Fetch.fetchWithInit(backendURI ++ "/history/" ++ userId ++ "/add",
Fetch.RequestInit.make(~method_=Post,
~body=Fetch.BodyInit.make(Encoders.endcodeInput(kind, value) |> Js.Json.stringify),
~headers=jsonHeader,
()))
|> then_(Fetch.Response.json)
|> then_(resp => resp |> parseResponse |> resolve)
);
};

/* Resets a user's reading history */
let resetUser = (userId:string) => {
Js.Promise.(
Fetch.fetchWithInit(backendURI ++ "/history/" ++ userId ++ "/reset",
Fetch.RequestInit.make(~method_=Put,
~headers=jsonHeader,
()))
|> then_(Fetch.Response.json)
|> then_(resp => resp |> parseResponse |> resolve)
);
};
};
Loading

0 comments on commit 4522ba0

Please sign in to comment.