--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base/resources/config.conf Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,7 @@
+config {
+ http {
+ interface = localhost
+ port = 8080
+ prefix = cms
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base/resources/reference.conf Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,1 @@
+include "config.conf"
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/Layout.scala Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,37 @@
+package sqwl.cms
+
+import scalatags.Text.all._
+import scalatags.Text.tags2
+import scalatags.Text.TypedTag
+
+object Layout {
+ def apply(content: iContent, st: ViewState): TypedTag[String] = {
+ html(
+ head(
+ tags2.title(st match {
+ case ViewArticle(v) => v.title
+ case ViewCategory(v) => v.name
+ case ViewTag(v) => v.name
+ case Dashboard => "SQWL"
+ case News => "Novinky"
+ })
+
+ ),
+ body(
+ div("foo"),
+ st match {
+ case ViewArticle(article) => div(
+ a(href:=article.pathSegment, article.title),
+ raw(article.htmlContent)
+ )
+ case _ => ""
+ },
+ div(
+ content.categories map(c => div(a(href:=c.pathSegment, c.name)))
+ )
+
+ )
+ )
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/MD5.scala Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,10 @@
+package sqwl.cms
+
+import java.security.MessageDigest
+
+object MD5 {
+ private val md5 = MessageDigest.getInstance("MD5")
+
+ def apply(value: String): String =
+ md5.digest(value.getBytes("UTF-8")).map("%02x".format(_)).mkString
+}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/Server.scala Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,58 @@
+package sqwl.cms
+
+import akka.actor.ActorSystem
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.model.headers.{ETag, EntityTag}
+import akka.http.scaladsl.model.{ContentTypes, HttpEntity, HttpHeader, HttpResponse}
+import akka.http.scaladsl.server.Directives._
+import akka.stream.{ActorMaterializer, Materializer}
+
+import scala.collection.immutable
+import scala.concurrent.ExecutionContextExecutor
+import scala.io.StdIn
+
+trait Server extends App with Service with config with UrlScheme {
+ override implicit val system: ActorSystem = ActorSystem()
+ override implicit val executor: ExecutionContextExecutor = system.dispatcher
+ override implicit val materializer: Materializer = ActorMaterializer()
+
+ protected def content: iContent
+
+ val asArticle = Segment.flatMap(content.articleByPath(_))
+ val asCategory = Segment.flatMap(content.categoryByPath(_))
+ val asTag = Segment.flatMap(content.tagByPath(_))
+
+ private def asHtml(st: ViewState): HttpResponse = {
+ val s = Layout(content, st).render
+ HttpResponse(
+ headers = immutable.Seq[HttpHeader](ETag(MD5(s))),
+ entity = HttpEntity(ContentTypes.`text/html(UTF-8)`,
+ "<!DOCTYPE html>" + s))
+ }
+
+ private val routes = get {
+ pathPrefix(http.prefix) {
+ pathEnd {
+ complete(asHtml(Dashboard))
+ } ~ pathPrefix(asArticle) { article =>
+ pathEnd {
+ complete(asHtml(ViewArticle(article)))
+ } ~ getFromDirectory(article.assets.toString)
+ } ~ path(asCategory) { category =>
+ complete(asHtml(ViewCategory(category)))
+ } ~ path(TAG / asTag) { tag =>
+ complete(asHtml(ViewTag(tag)))
+ }
+ } ~ pathPrefix(http.prefix / ASSETS) {
+ getFromResourceDirectory("META-INF/resources/webjars")
+ } ~ pathPrefix(http.prefix / PUBLIC) {
+ getFromResourceDirectory("public")
+ }
+ }
+
+ Http().bindAndHandle(routes, http.interface, http.port)
+ system.log.info("Click `Enter` to close application...")
+ StdIn.readLine()
+ system.terminate()
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/Service.scala Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,12 @@
+package sqwl.cms
+
+import akka.actor.ActorSystem
+import akka.stream.Materializer
+
+import scala.concurrent.ExecutionContextExecutor
+
+trait Service {
+ implicit val system: ActorSystem
+ implicit def executor: ExecutionContextExecutor
+ implicit val materializer: Materializer
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/UrlScheme.scala Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,8 @@
+package sqwl.cms
+
+trait UrlScheme {
+ val ASSETS = "assets"
+ val PUBLIC = "public"
+ val TAG = "tag"
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/config.scala Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,13 @@
+package sqwl.cms
+
+import com.wacai.config.annotation.conf
+
+@conf trait config {
+
+ final val http = new {
+ val interface = "localhost"
+ val port = 8080
+ val prefix = "cms"
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/datamodel.scala Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,33 @@
+package sqwl.cms
+
+import java.nio.file.Path
+
+trait Named {
+ def name: String
+}
+
+trait Navigable {
+ def pathSegment: String
+}
+
+trait iCategory extends Named with Navigable
+
+trait iTag extends Named with Navigable
+
+trait iArticle extends Navigable {
+ def title: String
+ def htmlContent: String
+ def category: Option[iCategory]
+ def tags: Seq[iTag]
+ def assets: Path
+}
+
+trait iContent {
+ def articlesByTag(t: iTag): Seq[iArticle]
+ def articlesByCategory(c: iCategory): Seq[iArticle]
+ def tags: Seq[iTag]
+ def categories: Seq[iCategory]
+ def articleByPath(path: String): Option[iArticle]
+ def categoryByPath(path: String): Option[iCategory]
+ def tagByPath(path: String): Option[iTag]
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/base/src/sqwl/cms/viewstates.scala Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,9 @@
+package sqwl.cms
+
+sealed trait ViewState
+
+case object Dashboard extends ViewState
+case object News extends ViewState
+case class ViewArticle(article: iArticle) extends ViewState
+case class ViewCategory(category: iCategory) extends ViewState
+case class ViewTag(tag: iTag) extends ViewState
--- a/build.sc Thu Nov 22 13:15:29 2018 +0100
+++ b/build.sc Thu Nov 29 12:20:20 2018 +0100
@@ -55,7 +55,7 @@
)}
}
-object jvm extends Common {
+object base extends Common {
override def scalacPluginIvyDeps: Target[Loose.Agg[Dep]] = super.scalacPluginIvyDeps() ++ Agg(
ivy"org.scalamacros:::paradise:2.1.0"
)
@@ -72,11 +72,34 @@
)
override def scalacOptions = T{super.scalacOptions.map(_ :+
- "-Xmacro-settings:conf.output.dir=jvm/resources"
+ "-Xmacro-settings:conf.output.dir=base/resources"
)}
- override def mainClass = Some("sqwl.cms.Server")
+}
+
+object content extends Common {
+ override def moduleDeps = Seq(base)
+ override def sources : Sources = T.sources{ millSourcePath / up / 'example / 'src }
+ override def ivyDeps = Agg(
+ ivy"com.beachape::enumeratum:1.5.13"
+ )
+}
+object app extends Common {
+ override def moduleDeps = Seq(content)
+ override def mainClass = Some("sqwl.cms.Main")
+
+ override def generatedSources: Target[Seq[PathRef]] = T{
+ val dir = T.ctx().dest
+ write(dir / "main.scala",
+ """
+ | package sqwl.cms
+ | object Main extends Server {
+ | override def content: iContent = Content
+ | }
+ """.stripMargin)
+ Seq(PathRef(dir))
+ }
}
object js extends Common with ScalaJSModule {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/example/content/a1.html Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,3 @@
+<div>Article 1</div>
+<p>Lorem ipsum...</div>
+<a href="a1/test.txt">test.txt</a>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/example/content/a1/test.txt Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,1 @@
+Test asset.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/example/src/sqwl/cms/Content.scala Thu Nov 29 12:20:20 2018 +0100
@@ -0,0 +1,56 @@
+
+package sqwl.cms
+
+import java.nio.file.{FileSystems, Path, Paths}
+
+import akka.stream.scaladsl.FileIO
+import enumeratum.EnumEntry.Hyphencase
+import enumeratum._
+
+import scala.io.Source
+
+sealed abstract class Category(val name: String) extends EnumEntry with iCategory with Hyphencase {
+ override def pathSegment: String = entryName
+}
+
+object Category extends Enum[Category] {
+ val values = findValues
+ case object Cat1 extends Category("Category 1")
+ case object Cat2 extends Category("Category 2")
+}
+
+sealed abstract class Article(val title: String,
+ val category: Option[iCategory],
+ val tags: Seq[iTag],
+
+) extends EnumEntry with iArticle with Hyphencase {
+ override def htmlContent: String = {
+ val src = Source.fromFile(s"example/content/${entryName}.html", "UTF-8")
+ try {
+ src.getLines.mkString
+ } catch {
+ case _: Throwable => ""
+ } finally {
+ src.close()
+ }
+ }
+
+ override def assets: Path = Paths.get(s"example/content/${entryName}")
+ override def pathSegment: String = entryName
+}
+
+object Article extends Enum[Article] {
+ import Category._
+ val values = findValues
+ case object A1 extends Article("Article 1", Some(Cat1), Seq())
+}
+
+object Content extends iContent {
+ def articlesByTag(t: iTag): Seq[iArticle] = Seq()
+ def articlesByCategory(c: iCategory): Seq[iArticle] = Seq()
+ def tags: Seq[iTag] = Seq()
+ def categories: Seq[iCategory] = Category.values
+ def articleByPath(path: String): Option[iArticle] = Article.withNameOption(path)
+ def categoryByPath(path: String): Option[iCategory] = Category.withNameOption(path)
+ def tagByPath(path: String): Option[iTag] = None
+}
\ No newline at end of file
--- a/jvm/resources/config.conf Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-config {
- http {
- interface = localhost
- port = 8080
- prefix = cms
- }
-}
--- a/jvm/resources/reference.conf Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-include "config.conf"
\ No newline at end of file
--- a/jvm/src/sqwl/cms/Server.scala Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-package sqwl.cms
-
-import akka.actor.ActorSystem
-import akka.http.scaladsl.Http
-import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
-import akka.http.scaladsl.server.Directives._
-import akka.stream.{ActorMaterializer, Materializer}
-
-import scala.concurrent.ExecutionContextExecutor
-
-object Server extends App with Service with config with UrlScheme {
- override implicit val system: ActorSystem = ActorSystem()
- override implicit val executor: ExecutionContextExecutor = system.dispatcher
- override implicit val materializer: Materializer = ActorMaterializer()
-
- private val routes = get {
- path(http.prefix) {
- complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "main"))
- } ~ pathPrefix(http.prefix / ASSETS) {
- getFromResourceDirectory("META-INF/resources/webjars")
- } ~ pathPrefix(http.prefix / PUBLIC) {
- getFromResourceDirectory("public")
- }
- }
-
- Http().bindAndHandle(routes, http.interface, http.port)
-
-}
--- a/jvm/src/sqwl/cms/Service.scala Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-package sqwl.cms
-
-import akka.actor.ActorSystem
-import akka.stream.Materializer
-
-import scala.concurrent.ExecutionContextExecutor
-
-trait Service {
- implicit val system: ActorSystem
- implicit def executor: ExecutionContextExecutor
- implicit val materializer: Materializer
-}
--- a/jvm/src/sqwl/cms/UrlScheme.scala Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-package sqwl.cms
-
-trait UrlScheme {
- val ASSETS = "assets"
- val PUBLIC = "public"
-
-}
--- a/jvm/src/sqwl/cms/config.scala Thu Nov 22 13:15:29 2018 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-package sqwl.cms
-
-import com.wacai.config.annotation.conf
-
-@conf trait config {
-
- final val http = new {
- val interface = "localhost"
- val port = 8080
- val prefix = "cms"
- }
-
-}