Skip to content

Commit

Permalink
Merge pull request #4 from davenverse/sqlitesjs
Browse files Browse the repository at this point in the history
Add sqlite-sjs support
  • Loading branch information
ChristopherDavenport authored Jul 18, 2022
2 parents 1577295 + 7c819e9 commit 3e93b61
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 16 deletions.
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ lazy val core = crossProject(JVMPlatform, JSPlatform)
scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule)},
libraryDependencies ++= Seq(
"io.chrisdavenport" %%% "publicsuffix-retrieval-client" % publicSuffixV,
"io.chrisdavenport" %%% "sqlite-sjs" % "0.0.1",
)
// TODO Actually Implement with this
// npmDependencies ++= Seq(
Expand All @@ -79,6 +80,7 @@ lazy val examples = crossProject(JVMPlatform, JSPlatform)
"org.http4s" %%% "http4s-ember-client" % http4sV,
)
)
.jsConfigure { project => project.enablePlugins(ScalaJSBundlerPlugin) }
.jsSettings(
scalaJSUseMainModuleInitializer := true,
scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule)},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,96 @@
package io.chrisdavenport.snickerdoodle.persistence

import cats.syntax.all._
import io.chrisdavenport.snickerdoodle.SnCookie
import io.chrisdavenport.snickerdoodle.SnCookie.SnCookieKey
import io.chrisdavenport.snickerdoodle.SnCookiePersistence
import cats.effect.kernel._

import io.chrisdavenport.sqlitesjs.Sqlite
import io.circe.syntax._
import io.circe.Decoder
import io.circe.HCursor

trait SqlitePersistencePlatform {

def apply[F[_]: Async](path: fs2.io.file.Path): SnCookiePersistence[F] =
throw new RuntimeException("scala.js sqlite not yet implemented")
def apply[F[_]: Async](path: fs2.io.file.Path): Resource[F, SnCookiePersistence[F]] =
Sqlite.fromFile[F](path.toString).map(new SqlitePersistenceImpl[F](_))


private class SqlitePersistenceImpl[F[_]: Async](sqlite: Sqlite[F]) extends SnCookiePersistence[F]{

def updateLastAccessed(key: SnCookieKey, lastAccessed: Long): F[Unit] = {
val updateStatement = "Update cookies SET lastAccessed = ? where name = ? and domain = ? and path = ?"
sqlite.run(updateStatement, List(lastAccessed.asJson, key.name.asJson, key.domain.asJson, key.path.asJson))
.void
}

def clear: F[Unit] = {
val clearStatement = "DELETE FROM cookies"
sqlite.exec(clearStatement)
}
def clearExpired(now: Long): F[Unit] = {
val clearExpiredStatment = "DELETE FROM cookies where expiry < ?"
sqlite.run(clearExpiredStatment, List(now.asJson)).void
}
def create(cookie: SnCookie): F[Unit] = {
val raw = SnCookie.toRaw(cookie)
val insertStatement = "INSERT OR REPLACE INTO cookies (name, value, domain, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly, isHostOnly, sameSite, scheme, extension) values (?,?,?,?,?,?,?,?,?,?,?,?,?)"
sqlite.run(insertStatement, List(raw.name.asJson, raw.value.asJson, raw.domain.asJson, raw.path.asJson, raw.expiry.asJson, raw.lastAccessed.asJson, raw.creationTime.asJson, raw.isSecure.asJson, raw.isHttpOnly.asJson, raw.isHostOnly.asJson, raw.sameSite.asJson, raw.scheme.asJson, raw.extension.asJson))
.void
}

def createTable: F[Unit] = {
sqlite.exec(createTableStatement)
}
def getAll: F[List[SnCookie]] = {
val selectStatement = "SELECT name,value,domain,path,expiry,lastAccessed,creationTime,isSecure,isHttpOnly, isHostOnly, sameSite,scheme,extension FROM cookies"
sqlite.all(selectStatement).flatMap(
l => l.traverse(_.as[SnCookie.RawSnCookie](rawDecoder).liftTo[F].map(SnCookie.fromRaw))
)
}

}

private val createTableStatement = {
"""CREATE TABLE IF NOT EXISTS cookies (
name TEXT NOT NULL,
value TEXT NOT NULL,
domain TEXT NOT NULL,
path TEXT NOT NULL,
expiry INTEGER NOT NULL, -- Either MaxAge (relative to time called) or Expires (explicit) or HttpDate.MaxValue
lastAccessed INTEGER NOT NULL,
creationTime INTEGER NOT NULL,
isSecure INTEGER NOT NULL, -- Boolean
isHttpOnly INTEGER NOT NULL, -- Boolean
isHostOnly INTEGER NOT NULL, -- Boolean
sameSite INTEGER NOT NULL, --
scheme INTEGER,
extension TEXT,
CONSTRAINT cookiesunique UNIQUE (name, domain, path)
)"""
}

private val rawDecoder = new Decoder[SnCookie.RawSnCookie]{
def apply(c: HCursor): Decoder.Result[SnCookie.RawSnCookie] = (
c.downField("name").as[String],
c.downField("value").as[String],
c.downField("domain").as[String],
c.downField("path").as[String],
c.downField("expiry").as[Long],
c.downField("lastAccessed").as[Long],
c.downField("creationTime").as[Long],
c.downField("isSecure").as[Boolean](intBoolean),
c.downField("isHttpOnly").as[Boolean](intBoolean),
c.downField("isHostOnly").as[Boolean](intBoolean),
c.downField("sameSite").as[Int],
c.downField("scheme").as[Option[Int]],
c.downField("extensione").as[Option[String]]
).mapN(SnCookie.RawSnCookie.apply)
}

private val intBoolean: Decoder[Boolean] = Decoder[Int].emap{
case 0 => false.asRight
case 1 => true.asRight
case _ => "Invalid Integer Boolean".asLeft
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ trait SqlitePersistencePlatform { self =>
}

private[snickerdoodle] def create[F[_]: MonadCancelThrow](xa: Transactor[F])(cookie: SnCookie): F[Int] = {
Update[SnCookie.RawSnCookie]("INSERT OR REPLACE INTO cookies values (?,?,?,?,?,?,?,?,?,?,?,?,?)")
Update[SnCookie.RawSnCookie]("INSERT OR REPLACE INTO cookies (name, value, domain, path, expiry, lastAccessed, creationTime, isSecure, isHttpOnly, isHostOnly, sameSite, scheme, extension) values (?,?,?,?,?,?,?,?,?,?,?,?,?)")
.run(SnCookie.toRaw(cookie))
.transact(xa)
}
Expand All @@ -76,7 +76,7 @@ trait SqlitePersistencePlatform { self =>
.transact(xa)
}

def apply[F[_]: Async](path: fs2.io.file.Path): SnCookiePersistence[F] = {
def apply[F[_]: Async](path: fs2.io.file.Path): Resource[F, SnCookiePersistence[F]] = {
val xa = transactor[F](path)
new SnCookiePersistence[F] {
def updateLastAccessed(key: SnCookie.SnCookieKey, lastAccessed: Long): F[Unit] =
Expand All @@ -96,6 +96,6 @@ trait SqlitePersistencePlatform { self =>

def getAll: F[List[SnCookie]] = self.selectAll(xa)

}
}.pure[Resource[F, *]]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class SnCookieJarBuilder[F[_]: Async] private (
def withSupervisor(s: Supervisor[F]) = copy(supervisorO = s.some)

def withSqlitePersistence(path: Path) =
copy(persistenceO = SnCookiePersistence.sqlite(path).pure[Resource[F, *]].some)
copy(persistenceO = SnCookiePersistence.sqlite(path).some)
def withPersistence(c: SnCookiePersistence[F]) =
copy(persistenceO = c.pure[Resource[F, *]].some)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ trait SnCookiePersistence[F[_]]{


object SnCookiePersistence {
def sqlite[F[_]: Async](path: fs2.io.file.Path): SnCookiePersistence[F] = persistence.Sqlite[F](path)
def sqlite[F[_]: Async](path: fs2.io.file.Path): Resource[F, SnCookiePersistence[F]] = persistence.SqlitePersistence[F](path)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.chrisdavenport.snickerdoodle.persistence

object SqlitePersistence extends SqlitePersistencePlatform
8 changes: 4 additions & 4 deletions examples/src/main/scala/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ object Main extends IOApp {
def run(args: List[String]): IO[ExitCode] = {
(
SnCookieJarBuilder.default[IO]
.withSqlitePersistence(fs2.io.file.Path("sample.sqlite")) // Comment this line for JS
.withSqlitePersistence(fs2.io.file.Path("sample.sqlite"))
.expert // Usually you would just use `build` we use buildWithState to expose the internals
.buildWithState,
EmberClientBuilder.default[IO].build
Expand Down Expand Up @@ -47,10 +47,10 @@ object Main extends IOApp {
} >>
{
IO.println("") >>
// Comment this block for JS
IO.println("As well as persisted to disk in the sqlite database.") >>
SnCookiePersistence.sqlite[IO](fs2.io.file.Path("sample.sqlite"))
.getAll
SnCookiePersistence.sqlite[IO](fs2.io.file.Path("sample.sqlite")).use(
_.getAll
)
.flatTap(_.traverse_(Console[IO].println(_))) >>
IO.println("") >>
//
Expand Down
3 changes: 2 additions & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ addSbtPlugin("org.typelevel" % "sbt-typelevel-ci-release" % "0.4.12")
addSbtPlugin("org.typelevel" % "sbt-typelevel-site" % "0.4.12")
addSbtPlugin("org.typelevel" % "sbt-typelevel-settings" % "0.4.12")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.20.0")

0 comments on commit 3e93b61

Please sign in to comment.