Skip to content

Commit

Permalink
feat(pollux): CredentialSchema service, repository and sql (#416)
Browse files Browse the repository at this point in the history
  • Loading branch information
yshyn-iohk committed Mar 6, 2023
1 parent 90a5d00 commit ffa5f7e
Show file tree
Hide file tree
Showing 14 changed files with 715 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package io.iohk.atala.pollux.core.model

import java.time.{OffsetDateTime, ZoneOffset}
import java.util.UUID
import io.circe.Json
import zio.*

//TODO: test CredentialSchema DAL
//TODO: define the business logic:
// 1. create new schema
// 2. update the existing schema
// 3. delete the existing schema
// 4. business rules for breaking changes and versioning
//
//TODO: implement a new CredentialSchema endpoint

type Schema = zio.json.ast.Json

/** @param guid
* Globally unique identifier of the CredentialSchema object It's calculated as a UUID from string that contains the
* following fields: author, id and version
* @param id
* Locally unique identifier of the CredentialSchema. It is UUID When the version of the credential schema changes
* this `id` keeps the same value
* @param name
* Human readable name of the CredentialSchema
* @param version
* Version of the CredentialSchema
* @param author
* DID of the CredentialSchema's author
* @param authored
* Datetime stamp of the schema creation
* @param tags
* Tags of the CredentialSchema used for convenient lookup
* @param description
* Human readable description of the schema
* @param schema
* Internal schema object that depends on concrete implementation For W3C JsonSchema it is a JsonSchema object For
* AnonCreds schema is a AnonCreds schema
*/
case class CredentialSchema(
guid: UUID,
id: UUID,
name: String,
version: String,
author: String,
authored: OffsetDateTime,
tags: Seq[String],
description: String,
schemaType: String,
schema: Schema
) {
def longId = CredentialSchema.makeLongId(author, id, version)
}

object CredentialSchema {

def makeLongId(author: String, id: UUID, version: String) =
s"$author/${id.toString}?version=${version}"
def makeGUID(author: String, id: UUID, version: String) =
UUID.fromString(makeLongId(author, id, version))
def make(in: Input) = {
for {
id <- zio.Random.nextUUID
ts <- zio.Clock.currentDateTime.map(
_.atZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime
)
guid = makeGUID(in.author, id, in.version)
} yield CredentialSchema(
guid = guid,
id = id,
name = in.name,
version = in.version,
author = in.author,
authored = ts,
tags = in.tags,
description = in.description,
schemaType = in.schemaType,
schema = in.schema
)
}

val defaultAgentDid = "did:prism:agent"

case class Input(
name: String,
version: String,
description: String,
authored: Option[OffsetDateTime],
tags: Seq[String],
author: String = defaultAgentDid,
schemaType: String,
schema: Schema
)

case class Filter(
author: Option[String] = None,
name: Option[String] = None,
version: Option[String] = None,
tags: Option[String] = None
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.iohk.atala.pollux.core.repository

import io.iohk.atala.pollux.core.model.CredentialSchema
import io.iohk.atala.pollux.core.model.CredentialSchema.*
import io.iohk.atala.pollux.core.repository.Repository.SearchCapability
import java.util.UUID

trait CredentialSchemaRepository[F[_]]
extends Repository[F, CredentialSchema]
with SearchCapability[F, CredentialSchema.Filter, CredentialSchema] {
def create(cs: CredentialSchema): F[CredentialSchema]

def getByGuid(guid: UUID): F[Option[CredentialSchema]]

def update(cs: CredentialSchema): F[Option[CredentialSchema]]

def delete(guid: UUID): F[Option[CredentialSchema]]

def deleteAll(): F[Long]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.iohk.atala.pollux.core.repository

import io.iohk.atala.pollux.core.repository.Repository.{SearchQuery, SearchResult}

trait Repository[F[_], T]

object Repository {
case class SearchQuery[Filter](filter: Filter, skip: Int, limit: Int)

case class SearchResult[T](entries: Seq[T], count: Long, totalCount: Long)

trait SearchCapability[F[_], Filter, T] {
self: Repository[F, T] =>
def search(query: SearchQuery[Filter]): F[SearchResult[T]]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.iohk.atala.pollux.core.service

import zio.{Task, ZIO, ZLayer, IO}
import io.iohk.atala.pollux.core.model.CredentialSchema
import io.iohk.atala.pollux.core.model.CredentialSchema.*

import java.util.UUID
trait CredentialSchemaService {
type Result[T] = IO[CredentialSchemaService.Error, T]

/** @param in
* CredentialSchema form for creating the instance
* @return
* Created instance of the Credential Schema
*/
def create(in: CredentialSchema.Input): Result[CredentialSchema]

/** @param guid
* Globally unique UUID of the credential schema
* @return
* The instance of the credential schema or credential service error
*/
def getByGUID(guid: UUID): Result[CredentialSchema]

def update(in: CredentialSchema.Input): Result[CredentialSchema]

def delete(id: UUID): Result[CredentialSchema]
}

object CredentialSchemaService {
sealed trait Error

object Error {
def apply(throwable: Throwable): Error = RepositoryError(throwable)

final case class RepositoryError(cause: Throwable) extends Error

final case class NotFoundError(guid: UUID) extends Error

final case class UnexpectedError(msg: String) extends Error
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.iohk.atala.pollux.core.service
import io.iohk.atala.pollux.core.model.CredentialSchema
import io.iohk.atala.pollux.core.repository.CredentialSchemaRepository
import zio.Task
import zio.ZIO.getOrFailWith
import CredentialSchemaService.Error.*

import java.util.UUID

class CredentialSchemaServiceImpl(
credentialSchemaRepository: CredentialSchemaRepository[Task]
) extends CredentialSchemaService {
override def create(in: CredentialSchema.Input): Result[CredentialSchema] = {
for {
credentialSchema <- CredentialSchema.make(in)
createdCredentialSchema <- credentialSchemaRepository
.create(credentialSchema)
.mapError(t => RepositoryError(t))
} yield createdCredentialSchema
}

override def getByGUID(guid: UUID): Result[CredentialSchema] = {
credentialSchemaRepository
.getByGuid(guid)
.mapError[CredentialSchemaService.Error](t => RepositoryError(t))
.flatMap(
getOrFailWith(NotFoundError(guid))(_)
)
}

def getBy(
author: String,
id: UUID,
version: String
): Result[CredentialSchema] = {
getByGUID(CredentialSchema.makeGUID(author, id, version))
}

// TODO: Implement a business logic for a real schema update
override def update(in: CredentialSchema.Input): Result[CredentialSchema] = {
for {
cs <- CredentialSchema.make(in)
updated_opt <- credentialSchemaRepository
.update(cs)
.mapError(RepositoryError.apply)
updated <- getOrFailWith(NotFoundError(cs.guid))(updated_opt)
} yield updated
}

override def delete(guid: UUID): Result[CredentialSchema] = {
for {
deleted_row_opt <- credentialSchemaRepository
.delete(guid)
.mapError(RepositoryError.apply)
deleted_row <- getOrFailWith(NotFoundError(guid))(deleted_row_opt)
} yield deleted_row
}
}
5 changes: 5 additions & 0 deletions pollux/lib/project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import sbt._
object Dependencies {
object Versions {
val zio = "2.0.4"
val zioJson = "0.4.2"
val doobie = "1.0.0-RC2"
val zioCatsInterop = "3.3.0"
val prismSdk = "v1.4.1" // scala-steward:off
Expand All @@ -23,6 +24,7 @@ object Dependencies {
private lazy val slf4jSimple = "org.slf4j" % "slf4j-simple" % "2.0.6" % Test

private lazy val zio = "dev.zio" %% "zio" % Versions.zio
private lazy val zioJson = "dev.zio" %% "zio-json" % Versions.zioJson
private lazy val zioCatsInterop = "dev.zio" %% "zio-interop-cats" % Versions.zioCatsInterop
private lazy val zioTest = "dev.zio" %% "zio-test" % Versions.zio % Test
private lazy val zioTestSbt = "dev.zio" %% "zio-test-sbt" % Versions.zio % Test
Expand All @@ -37,6 +39,7 @@ object Dependencies {

private lazy val flyway = "org.flywaydb" % "flyway-core" % Versions.flyway

private lazy val quillJdbcZio = "io.getquill" %% "quill-jdbc-zio" % Versions.quill
private lazy val quillDoobie =
"io.getquill" %% "quill-doobie" % Versions.quill exclude ("org.scala-lang.modules", "scala-java8-compat_3")
private lazy val testcontainers =
Expand All @@ -61,6 +64,7 @@ object Dependencies {
// Dependency Modules
private lazy val baseDependencies: Seq[ModuleID] = Seq(
zio,
zioJson,
zioTest,
zioTestSbt,
zioTestMagnolia,
Expand All @@ -78,6 +82,7 @@ object Dependencies {
doobieHikari,
flyway,
quillDoobie,
quillJdbcZio,
testcontainers
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
CREATE TABLE public.credential_schema
(
guid UUID PRIMARY KEY default gen_random_uuid(),
id UUID NOT NULL,
schema_type varchar(63) NOT NULL,
name VARCHAR(128) NOT NULL,
version VARCHAR(64) NOT NULL,
tags VARCHAR(64) ARRAY NULL,
description TEXT NULL,
schema json NOT NULL,
author VARCHAR(255) NOT NULL,
authored TIMESTAMP WITH TIME ZONE NOT NULL,
proof_id UUID NULL,
UNIQUE (name, version, author)
);

CREATE INDEX credential_schema_name_index ON public.credential_schema (name);
CREATE INDEX credential_schema_schema_type_index ON public.credential_schema (schema_type);
CREATE INDEX credential_schema_version_index ON public.credential_schema (version);
CREATE INDEX credential_schema_tags_index ON public.credential_schema (tags);
CREATE INDEX credential_schema_author_index ON public.credential_schema (author);
CREATE INDEX credential_schema_authored_index ON public.credential_schema (authored);



0 comments on commit ffa5f7e

Please sign in to comment.