Add linters and update ci file
This commit is contained in:
parent
04a8e8fd2e
commit
584e61fdb0
31
.github/workflows/ci.yaml
vendored
31
.github/workflows/ci.yaml
vendored
@ -2,16 +2,34 @@ name: Continuous Integration
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["*", series/*]
|
branches: ["*", series/*]
|
||||||
|
paths-ignore:
|
||||||
|
- ".dockerignore"
|
||||||
|
- ".github/workflow/ci.yml"
|
||||||
|
- "Changelog.md"
|
||||||
|
- "Dockerfile"
|
||||||
|
- "doc/**"
|
||||||
|
- "docker/**"
|
||||||
|
- "LICENSE"
|
||||||
|
- "README.md"
|
||||||
|
# - "tests/e2e/**"
|
||||||
push:
|
push:
|
||||||
branches: ["*", series/*]
|
branches: ["*", series/*]
|
||||||
tags: [v*]
|
tags: [v*]
|
||||||
|
paths-ignore:
|
||||||
|
- ".dockerignore"
|
||||||
|
- ".github/workflow/ci.yml"
|
||||||
|
- "Changelog.md"
|
||||||
|
- "Dockerfile"
|
||||||
|
- "doc/**"
|
||||||
|
- "docker/**"
|
||||||
|
- "LICENSE"
|
||||||
|
- "README.md"
|
||||||
|
# - "tests/e2e/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Label of the container job
|
|
||||||
build:
|
build:
|
||||||
name: Build and Test
|
name: Build and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# container: node:12-buster
|
|
||||||
env:
|
env:
|
||||||
CODEGEN_DB_HOST: localhost
|
CODEGEN_DB_HOST: localhost
|
||||||
CODEGEN_DB_PORT: 5432
|
CODEGEN_DB_PORT: 5432
|
||||||
@ -46,19 +64,21 @@ jobs:
|
|||||||
- name: Migrate
|
- name: Migrate
|
||||||
run: csbt flyway/flywayMigrate
|
run: csbt flyway/flywayMigrate
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: csbt scalafmtCheckAll
|
run: csbt "scalafmtCheckAll;scalafixAll --check"
|
||||||
- name: Compile
|
- name: Compile
|
||||||
run: |
|
run: |
|
||||||
csbt "compile; test:compile"
|
csbt "compile; test:compile"
|
||||||
- name: Run Tests
|
- name: Run Unit Tests
|
||||||
run: |
|
run: |
|
||||||
csbt test
|
csbt test
|
||||||
|
- name: Run Integration Tests
|
||||||
|
run: |
|
||||||
|
csbt it:test
|
||||||
publish:
|
publish:
|
||||||
name: Publish Release Docker Image
|
name: Publish Release Docker Image
|
||||||
needs: [build]
|
needs: [build]
|
||||||
if: github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/v')
|
if: github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/v')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# container: node:12-buster
|
|
||||||
env:
|
env:
|
||||||
CODEGEN_DB_HOST: localhost
|
CODEGEN_DB_HOST: localhost
|
||||||
CODEGEN_DB_PORT: 5432
|
CODEGEN_DB_PORT: 5432
|
||||||
@ -106,7 +126,6 @@ jobs:
|
|||||||
needs: [build]
|
needs: [build]
|
||||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/devel'
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/devel'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# container: node:12-buster
|
|
||||||
env:
|
env:
|
||||||
CODEGEN_DB_HOST: localhost
|
CODEGEN_DB_HOST: localhost
|
||||||
CODEGEN_DB_PORT: 5432
|
CODEGEN_DB_PORT: 5432
|
||||||
|
1
.scalafix.conf
Normal file
1
.scalafix.conf
Normal file
@ -0,0 +1 @@
|
|||||||
|
rules = [OrganizeImports]
|
87
build.sbt
87
build.sbt
@ -6,7 +6,7 @@ val MunitCatsEffectVersion = "0.13.0"
|
|||||||
val FlywayVersion = "7.5.3"
|
val FlywayVersion = "7.5.3"
|
||||||
scalaVersion in ThisBuild := "2.13.4"
|
scalaVersion in ThisBuild := "2.13.4"
|
||||||
|
|
||||||
resolvers += "jitpack" at "https://jitpack.io"
|
resolvers in ThisBuild += "jitpack" at "https://jitpack.io"
|
||||||
|
|
||||||
import com.github.tototoshi.sbt.slick.CodegenPlugin.autoImport.{
|
import com.github.tototoshi.sbt.slick.CodegenPlugin.autoImport.{
|
||||||
slickCodegenDatabasePassword,
|
slickCodegenDatabasePassword,
|
||||||
@ -14,8 +14,8 @@ import com.github.tototoshi.sbt.slick.CodegenPlugin.autoImport.{
|
|||||||
slickCodegenJdbcDriver
|
slickCodegenJdbcDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
import _root_.slick.codegen.SourceCodeGenerator
|
import slick.codegen.SourceCodeGenerator
|
||||||
import _root_.slick.{model => m}
|
import slick.{model => m}
|
||||||
|
|
||||||
lazy val codegenDbHost = sys.env.getOrElse("CODEGEN_DB_HOST", "localhost")
|
lazy val codegenDbHost = sys.env.getOrElse("CODEGEN_DB_HOST", "localhost")
|
||||||
lazy val codegenDbPort = sys.env.getOrElse("CODEGEN_DB_PORT", "5432")
|
lazy val codegenDbPort = sys.env.getOrElse("CODEGEN_DB_PORT", "5432")
|
||||||
@ -42,24 +42,39 @@ lazy val flyway = (project in file("modules/flyway"))
|
|||||||
flywayBaselineOnMigrate := true
|
flywayBaselineOnMigrate := true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
lazy val testCommon = (project in file("modules/test-common"))
|
||||||
|
.settings(
|
||||||
|
libraryDependencies ++= Seq(
|
||||||
|
"com.github.monix" % "monix-bio" % "0a2ad29275",
|
||||||
|
"com.github.valskalla" %% "odin-monix" % "0.9.1",
|
||||||
|
"de.lolhens" %% "munit-tagless-final" % "0.0.1"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
lazy val root = (project in file("."))
|
lazy val root = (project in file("."))
|
||||||
.enablePlugins(CodegenPlugin, DockerPlugin, JavaAppPackaging, AshScriptPlugin)
|
.enablePlugins(
|
||||||
|
CodegenPlugin,
|
||||||
|
DockerPlugin,
|
||||||
|
JavaAppPackaging,
|
||||||
|
AshScriptPlugin,
|
||||||
|
BuildInfoPlugin,
|
||||||
|
GitBranchPrompt
|
||||||
|
)
|
||||||
.configs(IntegrationTest)
|
.configs(IntegrationTest)
|
||||||
.settings(
|
.settings(
|
||||||
organization := "wow.doge",
|
organization := "wow.doge",
|
||||||
name := "http4s-demo",
|
name := "http4s-demo",
|
||||||
// version := releaseVersion.getOrElse(dynver.value),
|
|
||||||
version in Docker := sys.env
|
version in Docker := sys.env
|
||||||
.getOrElse(
|
.get("DOCKER_PUBLISH_TAG")
|
||||||
"DOCKER_PUBLISH_TAG", {
|
.map(s => if (s.startsWith("v")) s.tail else s)
|
||||||
val s = version.value
|
.getOrElse(version.value),
|
||||||
if (s.startsWith("v")) s.tail else s
|
|
||||||
}
|
|
||||||
),
|
|
||||||
dockerBaseImage := dockerJavaImage,
|
dockerBaseImage := dockerJavaImage,
|
||||||
dockerExposedPorts := Seq(8081),
|
dockerExposedPorts := Seq(8081),
|
||||||
dockerUsername := Some("rohansircar"),
|
dockerUsername := Some("rohansircar"),
|
||||||
Defaults.itSettings,
|
Defaults.itSettings,
|
||||||
|
inConfig(IntegrationTest)(scalafixConfigSettings(IntegrationTest)),
|
||||||
|
buildInfoOptions ++= Seq(BuildInfoOption.ToJson, BuildInfoOption.BuildTime),
|
||||||
|
buildInfoPackage := "wow.doge.http4sdemo",
|
||||||
scalacOptions ++= Seq(
|
scalacOptions ++= Seq(
|
||||||
"-encoding",
|
"-encoding",
|
||||||
"UTF-8",
|
"UTF-8",
|
||||||
@ -77,8 +92,16 @@ lazy val root = (project in file("."))
|
|||||||
"-Wconf:cat=lint-byname-implicit:s",
|
"-Wconf:cat=lint-byname-implicit:s",
|
||||||
//give errors on non exhaustive matches
|
//give errors on non exhaustive matches
|
||||||
"-Wconf:msg=match may not be exhaustive:e",
|
"-Wconf:msg=match may not be exhaustive:e",
|
||||||
|
// """-Wconf:site=wow\.doge\.http4sdemo\.slickcodegen\Tables\$:i""",
|
||||||
|
"-Wconf:msg=early initializers are deprecated:i",
|
||||||
|
"""-Wconf:site=wow\.doge\.http4sdemo\.slickcodegen\..*:i""",
|
||||||
|
// """-Wconf:src=target/src_managed/Tables.scala:s""",
|
||||||
"-explaintypes" // Explain type errors in more detail.
|
"-explaintypes" // Explain type errors in more detail.
|
||||||
),
|
),
|
||||||
|
scalacOptions ++= {
|
||||||
|
if (insideCI.value) Seq("-Xfatal-warnings")
|
||||||
|
else Seq.empty
|
||||||
|
},
|
||||||
javacOptions ++= Seq("-source", "11", "-target", "11"),
|
javacOptions ++= Seq("-source", "11", "-target", "11"),
|
||||||
//format: off
|
//format: off
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
@ -134,9 +157,14 @@ lazy val root = (project in file("."))
|
|||||||
"com.dimafeng" %% "testcontainers-scala-munit" % "0.39.3" % IntegrationTest,
|
"com.dimafeng" %% "testcontainers-scala-munit" % "0.39.3" % IntegrationTest,
|
||||||
"com.dimafeng" %% "testcontainers-scala-postgresql" % "0.39.3" % IntegrationTest
|
"com.dimafeng" %% "testcontainers-scala-postgresql" % "0.39.3" % IntegrationTest
|
||||||
),
|
),
|
||||||
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.10.3"),
|
testFrameworks += new TestFramework("munit.Framework"),
|
||||||
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"),
|
buildInfoKeys := Seq[BuildInfoKey](
|
||||||
testFrameworks += new TestFramework("munit.Framework")
|
name,
|
||||||
|
version,
|
||||||
|
scalaVersion,
|
||||||
|
sbtVersion,
|
||||||
|
libraryDependencies
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.settings(
|
.settings(
|
||||||
slickCodegenDatabaseUrl := databaseUrl,
|
slickCodegenDatabaseUrl := databaseUrl,
|
||||||
@ -149,6 +177,9 @@ lazy val root = (project in file("."))
|
|||||||
slickCodegenCodeGenerator := { (model: m.Model) =>
|
slickCodegenCodeGenerator := { (model: m.Model) =>
|
||||||
new SourceCodeGenerator(model) {
|
new SourceCodeGenerator(model) {
|
||||||
override def Table = new Table(_) {
|
override def Table = new Table(_) {
|
||||||
|
// override def EntityType = new EntityType {
|
||||||
|
// override def caseClassFinal = true
|
||||||
|
// }
|
||||||
override def Column = new Column(_) {
|
override def Column = new Column(_) {
|
||||||
override def rawType = model.tpe match {
|
override def rawType = model.tpe match {
|
||||||
case "java.sql.Timestamp" =>
|
case "java.sql.Timestamp" =>
|
||||||
@ -162,13 +193,37 @@ lazy val root = (project in file("."))
|
|||||||
},
|
},
|
||||||
sourceGenerators in Compile += slickCodegen.taskValue
|
sourceGenerators in Compile += slickCodegen.taskValue
|
||||||
)
|
)
|
||||||
.dependsOn(flyway)
|
.dependsOn(flyway, testCommon)
|
||||||
|
|
||||||
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"
|
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"
|
||||||
inThisBuild(
|
inThisBuild(
|
||||||
List(
|
List(
|
||||||
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
|
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
|
||||||
semanticdbEnabled := true, // enable SemanticDB
|
semanticdbEnabled := true, // enable SemanticDB
|
||||||
semanticdbVersion := "4.4.2" // use Scalafix compatible version
|
semanticdbVersion := "4.4.2", // use Scalafix compatible version
|
||||||
|
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.10.3"),
|
||||||
|
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
wartremoverErrors in (Compile, compile) ++=
|
||||||
|
Warts.allBut(
|
||||||
|
Wart.Any,
|
||||||
|
Wart.NonUnitStatements,
|
||||||
|
Wart.StringPlusAny,
|
||||||
|
Wart.Overloading,
|
||||||
|
Wart.PublicInference,
|
||||||
|
Wart.Nothing,
|
||||||
|
Wart.Var,
|
||||||
|
Wart.DefaultArguments,
|
||||||
|
Wart.OptionPartial,
|
||||||
|
// Wart.MutableDataStructures,
|
||||||
|
Wart.ImplicitConversion,
|
||||||
|
Wart.ImplicitParameter,
|
||||||
|
Wart.ToString,
|
||||||
|
Wart.Recursion,
|
||||||
|
Wart.While,
|
||||||
|
Wart.ExplicitImplicitTypes,
|
||||||
|
Wart.ListUnapply
|
||||||
|
)
|
||||||
|
wartremoverExcluded += (sourceManaged in Compile).value
|
||||||
|
@ -27,7 +27,7 @@ trait MonixBioSuite extends munit.TaglessFinalSuite[Task] {
|
|||||||
(
|
(
|
||||||
options: TestOptions,
|
options: TestOptions,
|
||||||
value: Logger[Task]
|
value: Logger[Task]
|
||||||
) => Task(options.name),
|
) => Task.unit,
|
||||||
(_: Logger[Task]) => Task.unit
|
(_: Logger[Task]) => Task.unit
|
||||||
)
|
)
|
||||||
|
|
@ -1,16 +1,15 @@
|
|||||||
// addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.14")
|
// addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.14")
|
||||||
addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
|
|
||||||
|
|
||||||
// https://github.com/tototoshi/sbt-slick-codegen
|
|
||||||
libraryDependencies += "com.h2database" % "h2" % "1.4.196"
|
libraryDependencies += "com.h2database" % "h2" % "1.4.196"
|
||||||
libraryDependencies += "org.postgresql" % "postgresql" % "42.2.18"
|
libraryDependencies += "org.postgresql" % "postgresql" % "42.2.18"
|
||||||
|
|
||||||
|
addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
|
||||||
addSbtPlugin("com.github.tototoshi" % "sbt-slick-codegen" % "1.4.0")
|
addSbtPlugin("com.github.tototoshi" % "sbt-slick-codegen" % "1.4.0")
|
||||||
// Database migration
|
|
||||||
// https://github.com/flyway/flyway-sbt
|
|
||||||
addSbtPlugin("io.github.davidmweber" % "flyway-sbt" % "7.4.0")
|
addSbtPlugin("io.github.davidmweber" % "flyway-sbt" % "7.4.0")
|
||||||
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23")
|
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.8.0")
|
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.8.0")
|
||||||
|
|
||||||
addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1")
|
addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1")
|
||||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2")
|
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2")
|
||||||
|
addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0")
|
||||||
|
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0")
|
||||||
|
addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.4.13")
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package wow.doge.http4sdemo
|
|
||||||
|
|
||||||
import cats.syntax.all._
|
|
||||||
import io.odin.consoleLogger
|
|
||||||
import io.odin.fileLogger
|
|
||||||
import io.odin.syntax._
|
|
||||||
import monix.bio.Task
|
|
||||||
import monix.execution.Scheduler
|
|
||||||
|
|
||||||
import scala.concurrent.Future
|
|
||||||
import munit.TestOptions
|
|
||||||
import cats.effect.Resource
|
|
||||||
import io.odin.Logger
|
|
||||||
|
|
||||||
trait MonixBioSuite extends munit.TaglessFinalSuite[Task] {
|
|
||||||
override protected def toFuture[A](f: Task[A]): Future[A] = {
|
|
||||||
implicit val s = Scheduler.global
|
|
||||||
f.runToFuture
|
|
||||||
}
|
|
||||||
|
|
||||||
def loggerFixture(fileName: Option[String] = None)(implicit
|
|
||||||
enc: sourcecode.Enclosing
|
|
||||||
) =
|
|
||||||
ResourceFixture(
|
|
||||||
consoleLogger[Task]().withAsync() |+| fileLogger[Task](
|
|
||||||
fileName.getOrElse(enc.value.split("#").head + ".log")
|
|
||||||
),
|
|
||||||
(
|
|
||||||
options: TestOptions,
|
|
||||||
value: Logger[Task]
|
|
||||||
) => Task(options.name),
|
|
||||||
(_: Logger[Task]) => Task.unit
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
@ -1,56 +1,35 @@
|
|||||||
|
|
||||||
myapp = {
|
myapp = {
|
||||||
database = {
|
database = {
|
||||||
driver = org.postgresql.Driver
|
driver = org.postgresql.Driver
|
||||||
# url = "jdbc:postgresql://localhost:5432/test_db"
|
dbHost = localhost
|
||||||
dbHost = localhost
|
dbHost = ${?APP_DB_HOST}
|
||||||
dbHost = ${?APP_DB_HOST}
|
dbPort = 5432
|
||||||
dbPort = 5432
|
dbPort = ${?APP_DB_PORT}
|
||||||
dbPort = ${?APP_DB_PORT}
|
dbName = test_db
|
||||||
dbName = test_db
|
dbName = ${?APP_DB_NAME}
|
||||||
dbName = ${?APP_DB_NAME}
|
url = "jdbc:postgresql://"${myapp.database.dbHost}":"${myapp.database.dbPort}"/"${myapp.database.dbName}
|
||||||
url = "jdbc:postgresql://"${myapp.database.dbHost}":"${myapp.database.dbPort}"/"${myapp.database.dbName}
|
user = "test_user"
|
||||||
user = "test_user"
|
password = "password"
|
||||||
password = "password"
|
numThreads = 16
|
||||||
|
queueSize = 1000
|
||||||
// The number of threads determines how many things you can *run* in parallel
|
maxConnections = 16
|
||||||
// the number of connections determines you many things you can *keep in memory* at the same time
|
connectionTimeout = 5000
|
||||||
// on the database server.
|
validationTimeout = 5000
|
||||||
// numThreads = (core_count (hyperthreading included))
|
# connectionPool = disabled
|
||||||
numThreads = 16
|
keepAlive = true
|
||||||
|
migrations-table = "flyway_schema_history"
|
||||||
// queueSize = ((core_count * 2) + effective_spindle_count)
|
|
||||||
// on a MBP 13, this is 2 cores * 2 (hyperthreading not included) + 1 hard disk
|
|
||||||
queueSize = 1000
|
|
||||||
|
|
||||||
// https://blog.knoldus.com/2016/01/01/best-practices-for-using-slick-on-production/
|
|
||||||
// make larger than numThreads + queueSize
|
|
||||||
maxConnections = 16
|
|
||||||
|
|
||||||
connectionTimeout = 5000
|
|
||||||
validationTimeout = 5000
|
|
||||||
|
|
||||||
# connectionPool = disabled
|
|
||||||
keepAlive = true
|
|
||||||
|
|
||||||
migrations-table = "flyway_schema_history"
|
|
||||||
|
|
||||||
migrations-locations = [
|
migrations-locations = [
|
||||||
# "classpath:example/jdbc"
|
|
||||||
"classpath:db/migration/default"
|
"classpath:db/migration/default"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
testDatabase = {
|
testDatabase = {
|
||||||
driver = org.postgresql.Driver
|
driver = org.postgresql.Driver
|
||||||
user = "scala"
|
user = "scala"
|
||||||
password = "scala"
|
password = "scala"
|
||||||
|
numThreads = 16
|
||||||
numThreads = 16
|
queueSize = 10
|
||||||
|
|
||||||
queueSize = 10
|
|
||||||
|
|
||||||
maxConnections = 36
|
maxConnections = 36
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ object DBMigrations extends LazyLogging {
|
|||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings(Array("org.wartremover.warts.Null"))
|
||||||
private def unsafeMigrate(config: JdbcDatabaseConfig): Int = {
|
private def unsafeMigrate(config: JdbcDatabaseConfig): Int = {
|
||||||
val m: FluentConfiguration = Flyway.configure
|
val m: FluentConfiguration = Flyway.configure
|
||||||
.dataSource(
|
.dataSource(
|
||||||
|
@ -11,6 +11,7 @@ import org.http4s.Method
|
|||||||
import org.http4s.Request
|
import org.http4s.Request
|
||||||
import org.http4s.Uri
|
import org.http4s.Uri
|
||||||
import org.http4s.implicits._
|
import org.http4s.implicits._
|
||||||
|
import wow.doge.http4sdemo.MonixBioSuite
|
||||||
import wow.doge.http4sdemo.dto.Book
|
import wow.doge.http4sdemo.dto.Book
|
||||||
import wow.doge.http4sdemo.dto.BookSearchMode
|
import wow.doge.http4sdemo.dto.BookSearchMode
|
||||||
import wow.doge.http4sdemo.dto.BookUpdate
|
import wow.doge.http4sdemo.dto.BookUpdate
|
||||||
|
Loading…
Reference in New Issue
Block a user