From 4522ba01c5fc8b37ab825c4172b0cf70db71c891 Mon Sep 17 00:00:00 2001 From: James Morris Date: Sun, 25 Mar 2018 19:08:44 +0100 Subject: [PATCH] GCE and service integration (#11) 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 --- .gitignore | 2 + bin/build-images.sh | 4 +- bin/gcloud-images.sh | 9 ++ dokusho-server/README.md | 14 ++- dokusho-server/src/main/scala/Main.scala | 12 +- .../main/scala/dokusho/MongoRepository.scala | 31 +---- .../src/main/scala/dokusho/MongoService.scala | 37 ------ .../scala/dokusho/ReadingHistoryRouter.scala | 43 +++++++ .../scala/dokusho/ReadingHistoryService.scala | 52 ++++++++ .../src/main/scala/dokusho/package.scala | 3 +- dokusho/bsconfig.json | 2 + dokusho/package.json | 6 +- dokusho/src/app/Client.re | 51 ++++++++ dokusho/src/app/DokuUtil.re | 49 ++++---- dokusho/src/app/Dokusho.re | 52 ++++++-- dokusho/src/app/PageTypeSelection.re | 51 ++++---- dokusho/src/app/Types.re | 116 ++++++++++++++++-- dokusho/src/index.re | 1 + dokusho/src/test/DokuUtil_test.re | 1 - dokusho/src/test/Input_test.re | 1 - dokusho/src/test/PageTypeSelection_test.re | 4 +- dokusho/yarn.lock | 25 +++- kube/backend-controller.yaml | 2 +- kube/dokusho-ingress.yaml | 11 ++ kube/gcloud/backend-deployment.yaml | 20 +++ kube/gcloud/mongo-deployment.yaml | 28 +++++ kube/gcloud/web-deployment.yaml | 22 ++++ kube/web-controller.yaml | 2 +- 28 files changed, 492 insertions(+), 159 deletions(-) create mode 100644 bin/gcloud-images.sh delete mode 100644 dokusho-server/src/main/scala/dokusho/MongoService.scala create mode 100644 dokusho-server/src/main/scala/dokusho/ReadingHistoryRouter.scala create mode 100644 dokusho-server/src/main/scala/dokusho/ReadingHistoryService.scala create mode 100644 dokusho/src/app/Client.re create mode 100644 kube/dokusho-ingress.yaml create mode 100644 kube/gcloud/backend-deployment.yaml create mode 100644 kube/gcloud/mongo-deployment.yaml create mode 100644 kube/gcloud/web-deployment.yaml diff --git a/.gitignore b/.gitignore index 67e6383..26b1d16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .vscode .idea *.iml +target/ +project/target/ diff --git a/bin/build-images.sh b/bin/build-images.sh index f88db1e..69b52c4 100755 --- a/bin/build-images.sh +++ b/bin/build-images.sh @@ -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 \ No newline at end of file +docker build ../dokusho-server -t gcr.io/dokusho-199010/dokusho-server \ No newline at end of file diff --git a/bin/gcloud-images.sh b/bin/gcloud-images.sh new file mode 100644 index 0000000..ecb32a9 --- /dev/null +++ b/bin/gcloud-images.sh @@ -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 \ No newline at end of file diff --git a/dokusho-server/README.md b/dokusho-server/README.md index e33beb0..d5968c4 100644 --- a/dokusho-server/README.md +++ b/dokusho-server/README.md @@ -2,7 +2,9 @@ [![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 @@ -10,9 +12,15 @@ Backend server for Dokusho. This is a work in progress and is not currently bein `sbt run` will compile and start a server listening on `8080` -### Docker +### Endpoints + +* Fetch the reading history for a user + * GET `/user/` +* Replace a user's reading history + * PUT `user/` +* Add a new entry to the user's reading history + * POST `user//add` -Currently, no docker configuration has been craeted for this module. ## Additional Information diff --git a/dokusho-server/src/main/scala/Main.scala b/dokusho-server/src/main/scala/Main.scala index f64f975..7fc1219 100644 --- a/dokusho-server/src/main/scala/Main.scala +++ b/dokusho-server/src/main/scala/Main.scala @@ -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 @@ -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 } \ No newline at end of file diff --git a/dokusho-server/src/main/scala/dokusho/MongoRepository.scala b/dokusho-server/src/main/scala/dokusho/MongoRepository.scala index 345ac35..0007b63 100644 --- a/dokusho-server/src/main/scala/dokusho/MongoRepository.scala +++ b/dokusho-server/src/main/scala/dokusho/MongoRepository.scala @@ -1,6 +1,5 @@ package dokusho - import cats.data.OptionT import cats.effect.IO import io.circe.generic.auto._ @@ -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) diff --git a/dokusho-server/src/main/scala/dokusho/MongoService.scala b/dokusho-server/src/main/scala/dokusho/MongoService.scala deleted file mode 100644 index 58fe86f..0000000 --- a/dokusho-server/src/main/scala/dokusho/MongoService.scala +++ /dev/null @@ -1,37 +0,0 @@ -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 MongoService(mongoRepository: MongoRepository) { - - case class SuccessfulPut(userId: String) - - val routes: HttpService[IO] = HttpService[IO] { - case GET -> Root / "history" / userId => - for { - userReadingHistory <- mongoRepository.getUnsafe(userId) - json = userReadingHistory.asJson - response <- Ok(json) - } yield response - case GET -> Root / "history" / "safe" / userId => - for { - userReadingHistory <- mongoRepository.get(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 { - userReadingHistory <- req.as[ReadingHistory] - storedHistory <- mongoRepository.put(UserReadingHistory(userId, userReadingHistory)) - json: Json = SuccessfulPut(storedHistory.userId).asJson - response <- Ok(json) - } yield response - } -} \ No newline at end of file diff --git a/dokusho-server/src/main/scala/dokusho/ReadingHistoryRouter.scala b/dokusho-server/src/main/scala/dokusho/ReadingHistoryRouter.scala new file mode 100644 index 0000000..386aa12 --- /dev/null +++ b/dokusho-server/src/main/scala/dokusho/ReadingHistoryRouter.scala @@ -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)) + } +} \ No newline at end of file diff --git a/dokusho-server/src/main/scala/dokusho/ReadingHistoryService.scala b/dokusho-server/src/main/scala/dokusho/ReadingHistoryService.scala new file mode 100644 index 0000000..98a171d --- /dev/null +++ b/dokusho-server/src/main/scala/dokusho/ReadingHistoryService.scala @@ -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)) +} diff --git a/dokusho-server/src/main/scala/dokusho/package.scala b/dokusho-server/src/main/scala/dokusho/package.scala index cd41973..51d7f79 100644 --- a/dokusho-server/src/main/scala/dokusho/package.scala +++ b/dokusho-server/src/main/scala/dokusho/package.scala @@ -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 diff --git a/dokusho/bsconfig.json b/dokusho/bsconfig.json index 4544f97..6da83d2 100644 --- a/dokusho/bsconfig.json +++ b/dokusho/bsconfig.json @@ -15,6 +15,8 @@ "bs-dependencies": [ "reason-react", "bs-jest", + "@glennsl/bs-json", + "bs-fetch", "rationale" ], "bs-dev-dependencies" : [ diff --git a/dokusho/package.json b/dokusho/package.json index b900577..11c2c8a 100644 --- a/dokusho/package.json +++ b/dokusho/package.json @@ -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", diff --git a/dokusho/src/app/Client.re b/dokusho/src/app/Client.re new file mode 100644 index 0000000..5aa3460 --- /dev/null +++ b/dokusho/src/app/Client.re @@ -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) + ); + }; +}; diff --git a/dokusho/src/app/DokuUtil.re b/dokusho/src/app/DokuUtil.re index c8565aa..7501f84 100644 --- a/dokusho/src/app/DokuUtil.re +++ b/dokusho/src/app/DokuUtil.re @@ -1,25 +1,24 @@ -module DokuUtil { - let valueFromEvent = (evt) : string => ( evt - |> ReactEventRe.Form.target - |> ReactDOMRe.domElementToObj - )##value; - - let intFromEvent = (evt) : string => (( evt - |> ReactEventRe.Form.target - |> ReactDOMRe.domElementToObj)##value) - |> (s) => if (int_of_string(s) == 1) s else ""; - - let string_map_partial = (f: char => option(char), s) => { - let buf = Bytes.create(String.length(s)); - let j = ref(0); - for (i in 0 to String.length(s) - 1) { - switch (f(s.[i])) { - | None => () - | Some(c) => - Bytes.set(buf, j^,c ); - incr(j); - }; - }; - Bytes.sub_string(buf, 0, j^); - }; -} \ No newline at end of file +module DokuUtil = { + let valueFromEvent = evt : string => ( + evt + |> ReactEventRe.Form.target + |> ReactDOMRe.domElementToObj + )##value; + let intFromEvent = evt : string => + (evt |> ReactEventRe.Form.target + |> ReactDOMRe.domElementToObj)##value + |> s => if (int_of_string(s) == 1) { s } else { "" }; + let string_map_partial = (f: char => option(char), s) => { + let buf = Bytes.create(String.length(s)); + let j = ref(0); + for (i in 0 to String.length(s) - 1) { + switch (f(s.[i])) { + | None => () + | Some(c) => + Bytes.set(buf, j^, c); + incr(j); + }; + }; + Bytes.sub_string(buf, 0, j^); + }; +}; \ No newline at end of file diff --git a/dokusho/src/app/Dokusho.re b/dokusho/src/app/Dokusho.re index 0299b86..24c34cc 100644 --- a/dokusho/src/app/Dokusho.re +++ b/dokusho/src/app/Dokusho.re @@ -3,6 +3,7 @@ open Entry; open Types; open Day; open PageTypeSelection; +open Client; module Dokusho { let component = ReasonReact.reducerComponent("Dokusho"); @@ -18,17 +19,46 @@ module Dokusho { switch action { | ChangeSelection(pageType) => ReasonReact.Update({readingData: readingData, selectedEntry: pageType}); - | AddEntry(pageTypeString, count) => - ReasonReact.Update(Day.createEntry(9, pageTypeString, count) - |> Day.appendEntry(Day.now()) - |> (ne) => List.map(d => if(d.date != ne.date) d - else { - date: d.date, - entries: List.rev_append(ne.entries, d.entries)} - , readingData.days) - |> (ds) => { days: ds } - |> (rd) => { readingData: rd, selectedEntry: selectedEntry }); - }, + | AddEntry(pageType, count) => + ReasonReact.SideEffects( + ( self => + Js.Promise.( + Client.newEntry(Types.testUser, pageType, count) + |> then_((serverResponse: Client.serverResponse) => { + self.send(UpdateHistory(serverResponse.readingHistory.days)); + resolve(serverResponse); + })) + |> ignore + ) + ); + | UpdateHistory(days) => + ReasonReact.Update({readingData: {days: days}, selectedEntry: selectedEntry}); + | LoadUserData(userId) => + ReasonReact.SideEffects( + (self => + Js.Promise.( + Client.userHistory(userId) + |> then_((serverResponse: Client.serverResponse) => { + self.send(UpdateHistory(serverResponse.readingHistory.days)); + resolve(serverResponse); + })) + |> ignore + ) + ); + }, + didMount: (_self) => { + ReasonReact.SideEffects( + (self => + Js.Promise.( + Client.userHistory(Types.testUser) + |> then_((serverResponse: Client.serverResponse) => { + self.send(UpdateHistory(serverResponse.readingHistory.days)); + resolve(serverResponse); + })) + |> ignore + ) + ); + }, render: (self) => { let pageCount = Day.pageCount(List.hd(self.state.readingData.days)); diff --git a/dokusho/src/app/PageTypeSelection.re b/dokusho/src/app/PageTypeSelection.re index 6180842..1e71483 100644 --- a/dokusho/src/app/PageTypeSelection.re +++ b/dokusho/src/app/PageTypeSelection.re @@ -1,33 +1,30 @@ open PageType; + open Types; + open DokuUtil; module PageTypeSelection = { - type inputState = { - selection: pageType - }; - - let str = ReasonReact.stringToElement; - let component = ReasonReact.statelessComponent("PageTypeSelection"); - - let make = (~onChangeSelect, _) => { - ...component, - render: (_) => { - - } - }; + type inputState = {selection: pageType}; + let str = ReasonReact.stringToElement; + let component = ReasonReact.statelessComponent("PageTypeSelection"); + let make = (~onChangeSelect, _) => { + ...component, + render: (_) => + }; +}; \ No newline at end of file diff --git a/dokusho/src/app/Types.re b/dokusho/src/app/Types.re index 12d4dc0..1f812a3 100644 --- a/dokusho/src/app/Types.re +++ b/dokusho/src/app/Types.re @@ -1,11 +1,19 @@ -type pageType = Manga | News | Book | Lyric | Net; +type pageType = + | Manga + | News + | Book + | Lyric + | Net; -type content = { name: string, pageType: pageType}; +type content = { + name: string, + pageType +}; type entry = { - id: int, - kind: pageType, - value: int + id: int, + kind: pageType, + value: int }; type day = { @@ -13,16 +21,102 @@ type day = { entries: list(entry) }; -type readingHistory = { - days: list(day) -}; +type readingHistory = {days: list(day)}; type mainState = { readingData: readingHistory, - selectedEntry: pageType }; -type action = +type action = | AddEntry(pageType, int) - | ChangeSelection(pageType); \ No newline at end of file + | ChangeSelection(pageType) + | UpdateHistory(list(day)) + | LoadUserData(string); + +module Decoders = { + let parsePageType = (asString:string) => { + Js.Console.log("Got type " ++ asString); + switch (asString) { + | "Manga" => Manga + | "News" => News + | "Book" => Book + | "Lyric" => Lyric + | "Net" => Net + | _ => Book + }; + }; + let parseEntry = (json: Js.Json.t) : entry => + Json.Decode.{ + id: json |> field("id", int), + kind: json |> field("kind", string) |> parsePageType, + value: json |> field("value", int) + }; + let parseEntriesJson = json => Json.Decode.list(parseEntry, json); + let parseDay = (json: Js.Json.t) : day => + Json.Decode.{ + date: json |> field("date", string), + entries: json |> field("entries", parseEntriesJson) + }; + let parseDaysJson = json => Json.Decode.list(parseDay, json); + let parseHistory = (json: Js.Json.t) : readingHistory => + Json.Decode.{days: json |> field("days", parseDaysJson)}; +}; + +module Encoders = { + let encodeEntry = entry => + Json.Encode.( + object_([ + ("id", int(entry.id)), + ( + "kind", + string( + switch entry.kind { + | Manga => "Manga" + | News => "News" + | Book => "Book" + | Lyric => "Lyric" + | Net => "Net" + } + ) + ), + ("value", int(entry.value)) + ]) + ); + let encodeDay = day => + Json.Encode.( + object_([ + ("date", string(day.date)), + ("value", day.entries |> list(encodeEntry)) + ]) + ); + let encodeReadingHistory = readingHistory => + Json.Encode.(object_([("date", readingHistory.days |> list(encodeDay))])); + let endcodeInput = (pageType, value) => + Json.Encode.( + object_([ + ( + "kind", + string( + switch pageType { + | Manga => "Manga" + | News => "News" + | Book => "Book" + | Lyric => "Lyric" + | Net => "Net" + } + ) + ), + ("value", int(value)) + ]) + ); + let endcodeStringInput = (pt, value) => + Json.Encode.( + object_([ + ("kind", string(pt)), + ("value", int(value)) + ]) + ); +}; + +let testUser = "fully"; \ No newline at end of file diff --git a/dokusho/src/index.re b/dokusho/src/index.re index 8bb8034..0308307 100644 --- a/dokusho/src/index.re +++ b/dokusho/src/index.re @@ -1,4 +1,5 @@ [%bs.raw {|require('./styles/index.css')|}]; +[%raw "require('isomorphic-fetch')"]; [@bs.module "./registerServiceWorker"] external register_service_worker : unit => unit = "default"; diff --git a/dokusho/src/test/DokuUtil_test.re b/dokusho/src/test/DokuUtil_test.re index 6337f2a..6ec6788 100644 --- a/dokusho/src/test/DokuUtil_test.re +++ b/dokusho/src/test/DokuUtil_test.re @@ -9,5 +9,4 @@ describe("Util", () => { test("Can filter a string using a predicate", () => expect(DokuUtil.string_map_partial(s => if(s == 'l') Some(s) else None, "Hello")) |> toBe("ll")); }); - }); diff --git a/dokusho/src/test/Input_test.re b/dokusho/src/test/Input_test.re index 4280600..bbfc130 100644 --- a/dokusho/src/test/Input_test.re +++ b/dokusho/src/test/Input_test.re @@ -9,5 +9,4 @@ describe("Input", () => { test("Is an alias for React", () => expect(Input.str("Hello")) |> toBe(ReasonReact.stringToElement("Hello"))); }); - }); diff --git a/dokusho/src/test/PageTypeSelection_test.re b/dokusho/src/test/PageTypeSelection_test.re index ce0057f..176db2c 100644 --- a/dokusho/src/test/PageTypeSelection_test.re +++ b/dokusho/src/test/PageTypeSelection_test.re @@ -6,9 +6,7 @@ describe("PageTypeSelection", () => { open ExpectJs; test("Renders when given an onChangeSelect function", () => { - - let onChangeSelectFunction = (pt) => (); - + let onChangeSelectFunction = (_pt) => (); let component = ReactShallowRenderer.renderWithRenderer( ); diff --git a/dokusho/yarn.lock b/dokusho/yarn.lock index b0b0ee4..267213a 100644 --- a/dokusho/yarn.lock +++ b/dokusho/yarn.lock @@ -16,6 +16,14 @@ esutils "^2.0.2" js-tokens "^3.0.0" +"@glennsl/bs-json@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@glennsl/bs-json/-/bs-json-1.1.3.tgz#492e0336777087356968ae4d57515365d6a18b38" + +"@glennsl/rebase@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@glennsl/rebase/-/rebase-0.2.0.tgz#dc1d99296dd7f7c47c90b5a4518dd598a71bedfa" + abab@^1.0.3, abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -1213,6 +1221,14 @@ browserslist@^2.1.2, browserslist@^2.5.1: caniuse-lite "^1.0.30000792" electron-to-chromium "^1.3.30" +bs-fetch@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/bs-fetch/-/bs-fetch-0.2.1.tgz#165bed867009f2e91efb959ca1c0ff940d8cd19a" + +bs-fetch@reasonml-community/bs-fetch#next: + version "0.2.0" + resolved "https://codeload.github.com/reasonml-community/bs-fetch/tar.gz/e4d3fb4751ace0f5d64713f9c947fe62790c67c8" + bs-jest@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/bs-jest/-/bs-jest-0.2.0.tgz#62576e00c35f069d7681b9bb9bfd744dc7197eb7" @@ -3555,7 +3571,7 @@ isobject@^2.0.0: dependencies: isarray "1.0.0" -isomorphic-fetch@^2.1.1: +isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" dependencies: @@ -6034,6 +6050,13 @@ reduce-function-call@^1.0.1: dependencies: balanced-match "^0.4.2" +refetch@glennsl/refetch: + version "0.1.0" + resolved "https://codeload.github.com/glennsl/refetch/tar.gz/c33aadd461bf32a520c282d3444f2602affe4401" + dependencies: + "@glennsl/rebase" "^0.2.0" + bs-fetch reasonml-community/bs-fetch#next + regenerate@^1.2.1: version "1.3.3" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" diff --git a/kube/backend-controller.yaml b/kube/backend-controller.yaml index 4d3cafc..92bfc9c 100644 --- a/kube/backend-controller.yaml +++ b/kube/backend-controller.yaml @@ -14,7 +14,7 @@ spec: name: backend spec: containers: - - image: dokusho:server + - image: gcr.io/dokusho-199010/dokusho-server name: backend ports: - containerPort: 8080 diff --git a/kube/dokusho-ingress.yaml b/kube/dokusho-ingress.yaml new file mode 100644 index 0000000..1e40d75 --- /dev/null +++ b/kube/dokusho-ingress.yaml @@ -0,0 +1,11 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: dokusho-ingress +spec: + web: + serviceName: web + servicePort: 3000 + backend: + serviceName: backend + servicePort: 8080 \ No newline at end of file diff --git a/kube/gcloud/backend-deployment.yaml b/kube/gcloud/backend-deployment.yaml new file mode 100644 index 0000000..b18ff56 --- /dev/null +++ b/kube/gcloud/backend-deployment.yaml @@ -0,0 +1,20 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: backend +spec: + selector: + matchLabels: + name: backend + replicas: 1 + template: + metadata: + labels: + name: backend + spec: + containers: + - image: gcr.io/dokusho-199010/dokusho-server:latest + name: backend + ports: + - containerPort: 8080 + name: backend \ No newline at end of file diff --git a/kube/gcloud/mongo-deployment.yaml b/kube/gcloud/mongo-deployment.yaml new file mode 100644 index 0000000..e37ce30 --- /dev/null +++ b/kube/gcloud/mongo-deployment.yaml @@ -0,0 +1,28 @@ +apiVersion: extensions/v1beta1 # for versions before 1.9.0 use apps/v1beta2 +kind: Deployment +metadata: + name: mongo +spec: + selector: + matchLabels: + name: mongo + replicas: 1 + template: + metadata: + labels: + name: mongo + spec: + containers: + - image: mongo + name: mongo + ports: + - name: mongo + containerPort: 27017 + hostPort: 27017 + volumeMounts: + - name: mongo-persistent-storage + mountPath: /data/db + volumes: + - name: mongo-persistent-storage + persistentVolumeClaim: + claimName: mongo-claim \ No newline at end of file diff --git a/kube/gcloud/web-deployment.yaml b/kube/gcloud/web-deployment.yaml new file mode 100644 index 0000000..5304fa0 --- /dev/null +++ b/kube/gcloud/web-deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + name: web + name: web +spec: + selector: + matchLabels: + name: web + replicas: 1 + template: + metadata: + labels: + name: web + spec: + containers: + - image: gcr.io/dokusho-199010/dokusho-web:latest + name: web + ports: + - containerPort: 3000 + name: web-port \ No newline at end of file diff --git a/kube/web-controller.yaml b/kube/web-controller.yaml index 1645246..d1809b6 100644 --- a/kube/web-controller.yaml +++ b/kube/web-controller.yaml @@ -14,7 +14,7 @@ spec: name: web spec: containers: - - image: dokusho:web + - image: gcr.io/dokusho-199010/dokusho-frontend:latest name: web ports: - containerPort: 3000