Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ATL-1759] feat(castor): castor services scaffolding #24

Merged
merged 29 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e344f97
feat(castor): create empty scala project
Sep 7, 2022
badaa32
feat(castor): bootstrap castor subprojects
Sep 7, 2022
ece8dda
feat(castor): add castor http server subproject
Sep 8, 2022
df062c0
feat(castor): add guardrail openapi codegen
Sep 8, 2022
f40cab3
feat(castor): update gitignore
Sep 8, 2022
03429ed
feat(castor): use OpenAPI-Generator for server codegen
Sep 8, 2022
478341e
feat(castor): update dependencies for openapi codegen
Sep 8, 2022
fe5bba0
feat(castor): restructor openapi directory
Sep 8, 2022
98087f8
feat(castor): add sample http-server
Sep 8, 2022
d90cd3d
feat(castor): reorganize actorsystem layer
Sep 8, 2022
6809005
feat(castor): add template for ApiService generated file
Sep 9, 2022
ffcf4f3
feat(castor): add AkkaZioSupport
Sep 12, 2022
eb2c7ae
Merge branch 'main' into feature/ATL-1759-castor-scaffolding
Sep 12, 2022
20e0cb5
feat(castor): add mock resonse and adjust openapi spec
Sep 12, 2022
93c8b87
feat(castor): add worker subproject
Sep 12, 2022
fcc92e3
feat(castor): add DIDApi api group
Sep 12, 2022
803f073
feat(castor): add DIDOperations api group
Sep 12, 2022
e54e839
feat(castor): add EventConsumer for worker process
Sep 12, 2022
2536249
feat(castor): re-organize structure to account for grpc module
Sep 12, 2022
8781127
feat(castor): add grpc dependencies
Sep 12, 2022
f6fe129
feat(castor): remove models subproject
Sep 12, 2022
0061dab
feat(castor): add grpc server
Sep 13, 2022
ec70f11
feat(castor): format sbt definitions
Sep 13, 2022
70e1961
feat(castor): add grpc services
Sep 13, 2022
e4bc0d0
feat(castor): fix openapi spec unwanted edits
Sep 13, 2022
0cdf232
feat(castor): add local docker-compose db and migrations
Sep 13, 2022
e588abd
feat(castor): add repository module
Sep 14, 2022
eb57758
feat(castor): initializing transactorLayer type can fail
Sep 14, 2022
46dc82f
feat(castor): change doobie Transactor to use hikari pool
Sep 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ megalinter-reports
**/project/metals.sbt

target

shell.nix
.envrc
22 changes: 22 additions & 0 deletions castor/api/grpc/castor_api.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
syntax = "proto3";

import "scalapb/scalapb.proto";

option (scalapb.options) = {
no_default_values_in_constructor: true
package_name: "io.iohk.atala.castor.proto"
};


message Ping {
string message = 1;
float delaySeconds = 2;
}

message Pong {
string message = 1;
}

service DIDService {
rpc SendPing(Ping) returns (Pong) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ paths:
schema:
$ref: "#/components/schemas/ErrorResponse"

/did-authentication/v1/challenges:
/did-authentication/challenges:
post:
tags: ["DID Authentication"]
operationId: createDidAuthenticationChallenge
Expand Down Expand Up @@ -330,7 +330,7 @@ paths:
schema:
$ref: "#/components/schemas/ErrorResponse"

/did-authentication/v1/challenge-submissions:
/did-authentication/challenge-submissions:
post:
tags: ["DID Authentication"]
operationId: createDidAuthenticationChallengeSubmission
Expand Down Expand Up @@ -514,10 +514,6 @@ components:
items:
$ref: "#/components/schemas/Service"

DIDRef:
type: string
example: "did:example:123456789abcdefghi"

VerificationMethodRef:
type: string
example: "did:example:123456789abcdefghi#keys-1"
Expand Down Expand Up @@ -843,7 +839,7 @@ components:
- ttl
properties:
ttl:
type: number
type: integer
description: A number of seconds that challenge will be considered valid.
example: 900
state:
Expand Down Expand Up @@ -898,11 +894,10 @@ components:
AuthenticationChallengeSubject:
description: |
A challenged subject that must complete the challenge.
If VerificationMethodRef is used, it must be a verification method
in the authentication relationship.
oneOf:
- $ref: "#/components/schemas/DIDRef"
- $ref: "#/components/schemas/VerificationMethodRef"
May refer to DID or VerificationMethod inside a DID. If VerificationMethod
is used, it must be inside the authentication verification relationship.
type: string
example: "did:example:123456789abcdefghi"

AuthenticationChallengeJwt:
type: string
Expand Down
32 changes: 32 additions & 0 deletions castor/service/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# macOS
.DS_Store

# sbt specific
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/
project/local-plugins.sbt
.history
.ensime
.ensime_cache/
.sbt-scripted/
local.sbt

# Bloop
patlo-iog marked this conversation as resolved.
Show resolved Hide resolved
.bsp

# VS Code
.vscode/

# Metals
.bloop/
.metals/
metals.sbt

# IDEA
.idea
.idea_modules
/.worksheet/
4 changes: 4 additions & 0 deletions castor/service/.scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
version = 3.5.8
runner.dialect = scala3

maxColumn = 120
10 changes: 10 additions & 0 deletions castor/service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Castor BB service

## Quickstart

__Running Castor service locally for development__

```bash
docker-compose -f docker/docker-compose-local.yaml up -d
sbt api-server/run
```
17 changes: 17 additions & 0 deletions castor/service/api-server/openapi/generator-config/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# The folder where templates can be found
templateDir: "api-server/openapi/generator-config/scala-akka-http-server"

# Only generate output for templates registered with the 'Apis' or 'Models' types
globalProperties:
apis: ""
models: ""

# Define output packages for generated classes
apiPackage: "io.iohk.atala.castor.openapi.api"
modelPackage: "io.iohk.atala.castor.openapi.model"

# The generator to use
generatorName: "scala-akka-http-server"

# Use handwritten classes for the types below. This is useful to customize the (de)serialization
#importMappings:
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# {{&appName}}

{{&appDescription}}

{{^hideGenerationTimestamp}}
This Scala akka-http framework project was generated by the OpenAPI generator tool at {{generatedDate}}.
{{/hideGenerationTimestamp}}

{{#generateApis}}
## API

{{#apiInfo}}
{{#apis}}
### {{baseName}}

|Name|Role|
|----|----|
|`{{importPath}}Controller`|akka-http API controller|
|`{{importPath}}Api`|Representing trait|
{{^skipStubs}}
|`{{importPath}}ApiImpl`|Default implementation|
{{/skipStubs}}

{{#operations}}
{{#operation}}
* `{{httpMethod}} {{contextPath}}{{path}}{{#queryParams.0}}?{{/queryParams.0}}{{#queryParams}}{{paramName}}=[value]{{^-last}}&{{/-last}}{{/queryParams}}` - {{summary}}
{{/operation}}
{{/operations}}

{{/apis}}
{{/apiInfo}}
{{/generateApis}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package {{package}}

import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.model.StatusCodes
{{^pathMatcherPatterns.isEmpty}}import akka.http.scaladsl.server.{PathMatcher, PathMatcher1}
{{/pathMatcherPatterns.isEmpty}}
{{#hasMarshalling}}import akka.http.scaladsl.marshalling.ToEntityMarshaller
import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
import akka.http.scaladsl.unmarshalling.FromStringUnmarshaller
{{/hasMarshalling}}
{{#hasCookieParams}}import akka.http.scaladsl.model.headers.HttpCookiePair
{{/hasCookieParams}}
{{#hasMultipart}}import {{invokerPackage}}.StringDirectives
import {{invokerPackage}}.MultipartDirectives
import {{invokerPackage}}.FileField
import {{invokerPackage}}.PartsAndFiles
{{/hasMultipart}}
{{#imports}}import {{import}}
{{/imports}}
{{#hasMultipart}}import scala.util.Try
import akka.http.scaladsl.server.MalformedRequestContentRejection
import akka.http.scaladsl.server.directives.FileInfo
{{/hasMultipart}}


{{#operations}}
class {{classname}}(
{{classVarName}}Service: {{classname}}Service{{#hasMarshalling}},
{{classVarName}}Marshaller: {{classname}}Marshaller{{/hasMarshalling}}
) {{#hasMultipart}} extends MultipartDirectives with StringDirectives {{/hasMultipart}}{

{{#pathMatcherPatterns}}import {{classname}}Patterns.{{pathMatcherVarName}}
{{/pathMatcherPatterns}}

{{#hasMarshalling}}import {{classVarName}}Marshaller._
{{/hasMarshalling}}

lazy val route: Route =
{{#operation}}
path({{#vendorExtensions.x-paths}}{{#isText}}"{{/isText}}{{value}}{{#isText}}"{{/isText}}{{^-last}} / {{/-last}}{{/vendorExtensions.x-paths}}) { {{^pathParams.isEmpty}}({{#pathParams}}{{paramName}}{{^-last}}, {{/-last}}{{/pathParams}}) => {{/pathParams.isEmpty}}
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}} { {{^queryParams.isEmpty}}
parameters({{#queryParams}}"{{baseName}}".as[{{dataType}}]{{^required}}.?{{#vendorExtensions.x-has-default-value}}({{{defaultValue}}}){{/vendorExtensions.x-has-default-value}}{{/required}}{{^-last}}, {{/-last}}{{/queryParams}}) { ({{#queryParams}}{{paramName}}{{^-last}}, {{/-last}}{{/queryParams}}) =>{{/queryParams.isEmpty}} {{^headerParams.isEmpty}}
{{#headerParams}}{{#required}}headerValueByName{{/required}}{{^required}}optionalHeaderValueByName{{/required}}("{{baseName}}") { {{paramName}} => {{/headerParams}}{{/headerParams.isEmpty}}{{^cookieParams.isEmpty}}
{{#cookieParams}}{{#required}}cookie({{/required}}{{^required}}optionalCookie({{/required}}"{{baseName}}"){ {{paramName}} => {{/cookieParams}}{{/cookieParams.isEmpty}}{{#isMultipart}}
{{> multipart}}{{/isMultipart}}{{^isMultipart}}{{> noMultipart}}{{/isMultipart}}{{^cookieParams.isEmpty}}
}{{/cookieParams.isEmpty}}{{^headerParams.isEmpty}}
}{{/headerParams.isEmpty}}{{^queryParams.isEmpty}}
}{{/queryParams.isEmpty}}
}
}{{^-last}} ~{{/-last}}
{{/operation}}
}

{{^pathMatcherPatterns.isEmpty}}
object {{classname}}Patterns {

{{#pathMatcherPatterns}}val {{pathMatcherVarName}}: PathMatcher1[String] = PathMatcher("{{pattern}}".r)
{{/pathMatcherPatterns}}
}
{{/pathMatcherPatterns.isEmpty}}

trait {{classname}}Service {

{{#operation}}
{{#responses}} def {{operationId}}{{#vendorExtensions.x-is-default}}Default{{/vendorExtensions.x-is-default}}{{^vendorExtensions.x-is-default}}{{code}}{{/vendorExtensions.x-is-default}}{{#baseType}}({{#vendorExtensions.x-is-default}}statusCode: Int, {{/vendorExtensions.x-is-default}}response{{baseType}}{{containerType}}: {{dataType}})(implicit toEntityMarshaller{{baseType}}{{containerType}}: ToEntityMarshaller[{{dataType}}]){{/baseType}}{{^baseType}}{{#vendorExtensions.x-is-default}}(statusCode: Int){{/vendorExtensions.x-is-default}}{{/baseType}}: Route ={{#vendorExtensions.x-empty-response}}
complete({{#vendorExtensions.x-is-default}}statusCode{{/vendorExtensions.x-is-default}}{{^vendorExtensions.x-is-default}}StatusCodes.getForKey({{code}}){{/vendorExtensions.x-is-default}}){{/vendorExtensions.x-empty-response}}{{^vendorExtensions.x-empty-response}}
complete(({{#vendorExtensions.x-is-default}}statusCode{{/vendorExtensions.x-is-default}}{{^vendorExtensions.x-is-default}}{{code}}{{/vendorExtensions.x-is-default}}, {{#baseType}}response{{baseType}}{{containerType}}{{/baseType}}{{^baseType}}"{{message}}"{{/baseType}})){{/vendorExtensions.x-empty-response}}
{{/responses}}
/**
{{#responses}} * {{#code}}Code: {{.}}{{/code}}{{#message}}, Message: {{.}}{{/message}}{{#dataType}}, DataType: {{.}}{{/dataType}}
{{/responses}}
*/
def {{operationId}}({{> operationParam}}){{^vendorExtensions.x-specific-marshallers.isEmpty}}
(implicit {{#vendorExtensions.x-specific-marshallers}}toEntityMarshaller{{varName}}: ToEntityMarshaller[{{dataType}}]{{^-last}}, {{/-last}}{{/vendorExtensions.x-specific-marshallers}}){{/vendorExtensions.x-specific-marshallers.isEmpty}}: Route

{{/operation}}
}

{{#hasMarshalling}}
trait {{classname}}Marshaller {
{{#entityUnmarshallers}} implicit def fromEntityUnmarshaller{{varName}}: FromEntityUnmarshaller[{{dataType}}]

{{/entityUnmarshallers}}

{{#stringUnmarshallers}} implicit def fromStringUnmarshaller{{varName}}: FromStringUnmarshaller[{{dataType}}]

{{/stringUnmarshallers}}

{{#entityMarshallers}} implicit def toEntityMarshaller{{varName}}: ToEntityMarshaller[{{dataType}}]

{{/entityMarshallers}}
}
{{/hasMarshalling}}

{{/operations}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version := "{{artifactVersion}}"
name := "{{artifactId}}"
organization := "{{groupId}}"
scalaVersion := "2.12.8"

val AkkaVersion = "2.6.19"
val AkkaHttpVersion = "10.2.9"
val LogbackVersion = "1.2.11"

libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion,
"com.typesafe.akka" %% "akka-stream" % AkkaVersion,
"com.typesafe.akka" %% "akka-http" % AkkaHttpVersion,
"com.typesafe.akka" %% "akka-http-spray-json" % AkkaHttpVersion,
"ch.qos.logback" % "logback-classic" % LogbackVersion % Runtime
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package {{invokerPackage}}

import akka.actor.typed.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Route
{{#apiInfo}}{{#apis}}{{#operations}}import {{package}}.{{classname}}
{{/operations}}{{/apis}}{{/apiInfo}}
import akka.http.scaladsl.server.Directives._

class Controller[T]({{#apiInfo}}{{#apis}}{{#operations}}{{classVarName}}: {{classname}}{{^-last}}, {{/-last}}{{/operations}}{{/apis}}{{/apiInfo}})(implicit system: ActorSystem[T]) {

lazy val routes: Route = {{#apiInfo}}{{#apis}}{{#operations}}{{classVarName}}.route {{^-last}}~ {{/-last}}{{/operations}}{{/apis}}{{/apiInfo}}

Http().newServerAt("0.0.0.0", 9000).bind(routes)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package {{invokerPackage}}

import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{PathMatcher, PathMatcher1}
import scala.util.{Failure, Success, Try}
import scala.util.control.NoStackTrace

object AkkaHttpHelper {
def optToTry[T](opt: Option[T], err: => String): Try[T] =
opt.map[Try[T]](Success(_)) getOrElse Failure(new RuntimeException(err) with NoStackTrace)

/**
* A PathMatcher that matches and extracts a Float value. The matched string representation is the pure decimal,
* optionally signed form of a float value, i.e. without exponent.
*
* @group pathmatcher
*/
val FloatNumber: PathMatcher1[Float] =
PathMatcher("""[+-]?\d*\.?\d*""".r) flatMap { string =>
try Some(java.lang.Float.parseFloat(string))
catch { case _: NumberFormatException => None }
}

/**
* A PathMatcher that matches and extracts a Boolean value.
*
* @group pathmatcher
*/
val Boolean: PathMatcher1[Boolean] =
Segment.flatMap { string =>
try Some(string.toBoolean)
catch { case _: IllegalArgumentException => None }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package {{package}}

{{#imports}}
import {{import}}
{{/imports}}

{{#models}}
{{#model}}
/**
{{#title}} * = {{{.}}} =
*
{{/title}}
{{#description}} * {{{.}}}
*
{{/description}}
{{#vars}}
* @param {{{name}}} {{{description}}}{{#example}} for example: ''{{{.}}}''{{/example}}
{{/vars}}
*/
final case class {{classname}} (
{{#vars}}
{{{name}}}: {{^required}}Option[{{/required}}{{datatype}}{{^required}}] = None{{/required}}{{^-last}},{{/-last}}
{{/vars}}
)

{{/model}}
{{/models}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
formAndFiles({{#vendorExtensions.x-file-params}}FileField("{{baseName}}")){{/vendorExtensions.x-file-params}}{{^-last}}, {{/-last}} { partsAndFiles => {{^vendorExtensions.x-file-params.isEmpty}}
val _____ : Try[Route] = for {
{{#vendorExtensions.x-file-params}}{{baseName}} <- optToTry(partsAndFiles.files.get("{{baseName}}"), s"File {{baseName}} missing")
{{/vendorExtensions.x-file-params}}
} yield { {{/vendorExtensions.x-file-params.isEmpty}}
implicit val vp: StringValueProvider = partsAndFiles.form{{^vendorExtensions.x-non-file-params.isEmpty}}
stringFields({{#vendorExtensions.x-non-file-params}}"{{baseName}}".as[{{dataType}}]{{^required}}.?{{#vendorExtensions.x-has-default-value}}({{defaultValue}}){{/vendorExtensions.x-has-default-value}}{{/required}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-non-file-params}}) { ({{#vendorExtensions.x-non-file-params}}{{paramName}}{{^-last}}, {{/-last}}{{/vendorExtensions.x-non-file-params}}) =>{{/vendorExtensions.x-non-file-params.isEmpty}}
{{classVarName}}Service.{{operationId}}({{#allParams}}{{paramName}} = {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}){{^vendorExtensions.nonFileFormParams.isEmpty}}
}{{/vendorExtensions.nonFileFormParams.isEmpty}}{{^vendorExtensions.x-file-params.isEmpty}}
}
_____.fold[Route](t => reject(MalformedRequestContentRejection("Missing file.", t)), identity){{/vendorExtensions.x-file-params.isEmpty}}
}
Loading