From da62b87b8ddc5666e3e4da2d9750abfee2e6b776 Mon Sep 17 00:00:00 2001 From: zwiterrion Date: Tue, 3 Dec 2024 15:57:36 +0100 Subject: [PATCH] refacto documentation --- cli/README.md | 589 +---------- cli/src/commands/commands.md | 54 - daikoku/app/controllers/HomeController.scala | 74 +- .../app/controllers/admin-api-openapi.json | 5 - daikoku/app/domain/json.scala | 4 +- daikoku/app/domain/tenantEntities.scala | 1 - .../tenants/forms/CustomizationForm.tsx | 11 +- daikoku/javascript/src/types/tenant.ts | 1 - .../public/swaggers/admin-api-openapi.json | 10 - .../public/swaggers/admin-api-openapi.yaml | 8 - manual/docs/04-cli/041-informations/index.mdx | 958 +++++++++--------- 11 files changed, 464 insertions(+), 1251 deletions(-) delete mode 100644 cli/src/commands/commands.md diff --git a/cli/README.md b/cli/README.md index 44921afe7..87a1dc997 100644 --- a/cli/README.md +++ b/cli/README.md @@ -4,591 +4,6 @@ ![CLI architecture](architecture.png "Architecture") +Reference Documentation -# Installation - -This project can be installed and compiled from source with this Cargo command: - -``` -$ cargo install daikoku -or -$ brew tap maif/daikoku -$ brew install daikoku -``` - -Additionally there are [precompiled artifacts built on CI][artifacts] which are -available for download as well. - -[artifacts]: https://github.com/MAIF/daikoku/releases - -Installation can be confirmed with: - -``` -$ daikoku version -``` - -Subcommands can be explored with: - -``` -$ daikoku help -``` - -# Core commands - -Daikokucli uses your home folder to store the list of projects inside a `.daikoku` file. Each project created with the CLI should contain a `src` folder and a `.daikoku/.environments`. This file will contain - -You can start a new project from scratch - -```sh -daikoku cms init --name= --path= -``` - -or import an existing one - -```sh -daikoku cms migrate --name= --path= --server= --apikey= -``` - -then add a default Daikoku environment - -```sh -daikoku environments add --name= --server= --apikey= -``` - -> The Daikoku server has to be reachable and will be checked before saving the configuration - - -you can sync the new project with your Daikoku instance and fetch mails and apis - -```sh -daikoku pull apis -daikoku pull mails -``` - -you can start to develop and watch file changes - -```sh -daikoku watch -``` - -Common practices involve utilizing the directives within the Daikoku CMS to access private entities based on the connected user's permissions. - -```sh -daikoku login -``` - -You can start to follow your changes using - -```sh -daikoku watch --environment= -``` - -or permanently by changing the default project or environment - -```sh -daikoku environments switch --name= -daikoku cms switch --name= -``` - -you can view the currently used project and the others -```sh -daikoku cms list -``` - -At anytime, you can track an existing CMS folder or update its information -```sh -daikoku cms add --name= --path= --overwrite= -``` - -Once ready, you can push your sources with the Daikoku environment -```sh -daikoku push -``` - -## Start a new project by importing an existing one - -If you already have a legacy CMS on your Daikoku, you can start by importing it -```sh -daikoku projects migrate --name= \ - --path= \ - --server= \ - --apikey= -``` - -# CMS Structure - -The CMS projects adhere to the following strict file structure: - -- `.daikoku`: This hidden folder is used exclusively by Daikoku to store environments, secrets, and credentials. The only file you can edit here is the .daikokuignore, which allows you to exclude specific files from being pushed. - -- `assets`: Files placed in this folder can be uploaded to the Daikoku S3 Bucket associated with your project. They can then be accessed using a generated slug. - -- `src`: This folder contains all other source files, organized into the following subdirectories: - - `apis`: Lists all APIs available in your Daikoku. Each API has its own subfolder containing a header and description folder. - - `data`: Contains external data files such as JSON, YAML, CSV, and others. - - `pages`: Stores all source files that are not categorized under apis, data, scripts, mails, or styles. - - `scripts`: Contains JavaScript (JS) files. - - `styles`: Contains CSS files. - - `documentations` : Contains files that can be used as documentation page of APIs - -# Dynamic routes - -The CLI uses file-system routing where folders are used to create nested routes. Each folder represents a route segment that maps to a URL segment. - -You can create separate UIs for each route using page.html files. `page.html` is a special CLI file that contains html content. - -To create a nested route, you can nest folders inside each other and add page.html files inside them. For example: - -```sh -src/pages/page.html -> mysite.com/ -src/pages/invoices/page.html -> mysite.com/invoices -src/pages/offres.html -> mysite.com/offres -src/pages/apis/api/[apiId] -> mysite.com/apis/api/any-kind-of-api (the apiId value can be use in the page as mustache variable using {{apiId}}) -``` - -# Manage your assets - -You can manage your images, diagrams, or any type of files directly by creating a `/assets` folder inside your CMS project. - -Each asset is save in the S3 of your Daikoku using the following command -```sh -daikoku assets push --filename= \ - --path= \ - --desc= \ - --title= - --slug= -``` - -If you require a particular `slug` for your asset, you have the option to replace the automatically generated one by specifying the `slug` field. Additionally, you can exclude the `path` field, which is only necessary when creating an asset from a subfolder within the `assets` directory. - -To delete your asset you have to give the `filename` and the `slug` iif it differs - -```sh -daikoku assets remove --slug= --filename= -``` - -As others commands, you can display all registered assets -```sh -daikoku assets list -``` - -If you prefer to synchronize all assets with a single command, it offers speed advantages over doing so individually, albeit with reduced configurability. -```sh -daikoku assets sync -``` - -# Manage documentation pages - -You already have many choices in Daikoku to create the APIs's documentation. But, with the release of the CMS, you can now write your documentation with it. The documentations pages have to be written in the `src/documentations` folder and can be named as you wish. - -The recommended usage to create a new documentation page is to use the CLI as following : - -```sh -daikoku generate documentation --filename=my-new-documentation-page \ - --title="Title of the page" \ - --desc="The description of this page" -``` - -# CMS Directives - -## daikoku-user - -`parameters`: -- string user id -```html -{{#daikoku-user "{{userId}}"}} -
- {{user.name}} - -
-{{/daikoku-user}} -``` - -## daikoku-owned-apis - -`parameters` -- visibility: can be Private | Public | All -```html -{{#daikoku-owned-apis visibility="Private"}} - Mon api : {{api.name}} -{{/daikoku-owned-apis}} -``` - -## daikoku-owned-api -`parameters`: -- String API id -- The API version is optional, but it defaults to 1.0.0 when not specified. - -```html -{{#daikoku-owned-api "{{apiId}}" version="1.0.0"}} - Mon api : {{api.name}} -{{/daikoku-owned-api}} -``` - -## daikoku-json-owned-apis -`parameters`: -- Visibility : Private, Public or All -```html -{{#daikoku-json-owned-apis visibility="Private"}} - -{{/daikoku-json-owned-apis}} -``` - -## daikoku-json-owned-api -`parameters`: -- The API id, string value expected -- The API version is optional, but it defaults to 1.0.0 when not specified. - -```html -{{#daikoku-json-owned-api "{{apiId}}" version="1.0.0"}} -{{/daikoku-json-owned-api}} -``` - -## daikoku-owned-teams - -```html -{{#daikoku-owned-teams}} - Ma team : {{team.name}} -{{/daikoku-owned-teams}} -``` - -## daikoku-owned-team -`parameters`: -- The team ID, string value expected" -```html -{{#daikoku-owned-team "{{teamId}}"}} - Mon team : {{team.name}} -{{/daikoku-owned-team}} -``` - -## daikoku-json-owned-teams - -```html -{{daikoku-json-owned-teams}} -``` - -## daikoku-json-owned-team -`parameters`: -- The Team ID, String value expected - -```html -{{#daikoku-json-owned-team "{{teamId}}"}} - -{{/daikoku-json-owned-team}} -``` - -## tenant - -```html -{{tenant.name}} - {{tenant.style.description}} -``` - -## is_admin - -```html -{{is_admin}} -``` - -## connected - -```html -{{connected}} -``` - -## user - -When you have an user returned from directive, you can use the following fields - - - `name` - - `email` - - `_id` - - `_humandReadableId` - - `picture` - - `isDaikokuAdmin` - - `starredApis` - -```html -
- {{user.name}} - {{user.email}} -
-``` - -## request -```html -
- {{request.path}} - {{request.method}} - {{request.headers}} -
-``` - -## daikoku-css -```html -
- {{daikoku-css}} -
-``` - -## for -`parameters`: -- the fieldname used in the helper content - -``` -{{#for '{{team.users}}' field='myuser' }} - {{myuser.userId}} -{{/for}} -``` - -## size - -```html -{{size '{{team.users}}'}} -``` - -## ifeq - -```html -{{#ifeq "{{plan.type}}" "FreeWithoutQuotas"}} - You'll pay nothing and do whatever you want -{{/ifeq}} -``` - -## ifnoteq - -```html -{{#ifnoteq "{{plan.type}}" "FreeWithoutQuotas"}} - You'll pay nothing and do whatever you want -{{/ifnoteq}} -``` - -## getOrElse - -```html -{{getOrElse "{{plan.customName}}" "Un plan"}} -``` - -## translate - -```html -{{translate 'Logout'}} -``` - -## daikoku-query-param -`parameters`: -- the name of the query param - -```html -{{daikoku-query-param 'my-query-param'}} -``` - - -## daikoku-template-wrapper -`parameters`: -- Block path -- List of key=value usable in content - -```html -{{#daikoku-template-wrapper '' ="" }} - -{{/daikoku-template-wrapper}}" -``` - - -## daikoku-apis - -```html -{{#daikoku-apis}} - Api : {{api.name}} -{{/daikoku-apis}} -``` - -## daikoku-api - -`parameters`: -- API id, String value expected - -```html -{{#daikoku-api "{{apiId}}" version="1.0.0"}} - Mon api : {{api.name}} -{{/daikoku-api}}" -``` - -## daikoku-json-apis - -```html -{{daikoku-json-apis}} -``` - -## daikoku-json-api - -`parameters`: -- API Id, String value expected - -```html -{{#daikoku-json-api "{{apiId}}" version="1.0.0"}} - -{{/daikoku-json-api}} -``` - -## daikoku-teams - -```html -{{#daikoku-teams}} - Team : {{team.name}} -{{/daikoku-teams}} -``` - -## daikoku-team - -`parameters`: -- Team Id, String value expected - -```html -{{#daikoku-team "{{}}"}} - My team : {{team.name}} -{{/daikoku-team}} -``` - - -## daikoku-json-teams - -```html -{{daikoku-json-teams}} -``` - -## daikoku-json-team - -`parameters`: -- Team Id, String value expected - -```html -{{#daikoku-json-team "{{}}"}} - -{{/daikoku-json-team}} -``` - -## daikoku-documentations - -`parameters`: -- API id, String value expected - -```html -{{#daikoku-documentations "{{}}"}} - {{documentation.title}} -{{/daikoku-documentations}} -``` - -## daikoku-documentations-page - -`parameters`: -- API ID, String value expected -- Page ID as String value - -```html -{{#daikoku-documentations-page "" page=""}} - {{documentation.content}} -{{/daikoku-documentations-page}} -``` - -## daikoku-documentations-page-id - -`parameters`: -- Team ID, String value expected -- The named page parameter corresponding to the id of the expected page - -```html -{{#daikoku-documentations-page-id "" page=""}} - {{content}} -{{/daikoku-documentations-page-id}}" -``` - -## daikoku-plans - -`parameters`: -- API ID - -```html -{{#daikoku-plans ""}} - {{plan.type}} -{{/daikoku-plans}} -``` - -# License - -This project is licensed under the Apache 2.0 license with the LLVM exception. - - -# Commands - -The following commands must be run, replacing `` with `--parameter=value`. - -# PROJECT commands -```sh -daikoku cms init -daikoku cms migrate - -daikoku cms list -daikoku cms add -daikoku cms switch -daikoku cms remove -daikoku cms clear -``` - -# PUSH commands -```sh -daikoku push -``` - -# ASSETS commands -```sh -daikoku assets push <DESC> <PATH> <SLUG> -daikoku assets remove <FILENAME> <PATH> <SLUG> -daikoku assets list -daikoku assets sync -``` - -# ENVIRONMENTS commands -```sh -daikoku environments clear <FORCE> -daikoku environments add <NAME> <SERVER> <OVERWRITE> -daikoku environments switch <NAME> -daikoku environments remove <NAME> -daikoku environments info <NAME> <FULL> -daikoku environments list -daikoku environments config <APIKEY> -``` - -# GENERATE commands -```sh -daikoku generate documentation <FILENAME> <TITLE> <DESC> -``` - -# LOGIN -```sh -daikoku login -``` - -# PULL commands -```sh -daikoku pull apis -daikoku pull mails -``` - -# VERSION commands -```sh -daikoku version -``` - -# WATCH commands -```sh -daikoku watch -``` - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in this project by you, as defined in the Apache-2.0 license, -shall be licensed as above, without any additional terms or conditions. - -#### Run tests -``` -cargo test --test <filename> -- --nocapture --test-threads 1 -``` +The reference documentation is available at https://maif.github.io/daikoku/docs/cli \ No newline at end of file diff --git a/cli/src/commands/commands.md b/cli/src/commands/commands.md deleted file mode 100644 index 39f27d76a..000000000 --- a/cli/src/commands/commands.md +++ /dev/null @@ -1,54 +0,0 @@ -# CMS -daikoku cms init <NAME> <PATH> -daikoku cms migrate <NAME> <PATH> <SERVER> <APIKEY> -crash si le fichier .daikoku_metadata est present - -daikoku cms list -daikoku cms add <NAME> <PATH> <OVERWRITE> -daikoku cms switch <NAME> // change default en switch -daikoku cms remove <NAME> <REMOVE_FILES> -daikoku cms clear <FORCE> -avec confirmation pour faire plaisir - -# PUSH -daikoku push <DRY_RUN> <FILEPATH> -qui peut être un dossier - -# ASSETS -daikoku assets push <FILENAME> <TITLE> <DESC> <PATH> <SLUG> -daikoku assets remove <FILENAME> <PATH> <SLUG> -daikoku assets list -daikoku assets sync - -# ENVIRONMENTS -daikoku environments clear <FORCE> -daikoku environments add <NAME> <SERVER> <OVERWRITE> -daikoku environments switch <NAME> // rename du default -daikoku environments remove <NAME> -daikoku environments info <NAME> <FULL> // rename from env -daikoku environments list -daikoku environments config <APIKEY> - -# GENERATE -daikoku generate documentation <FILENAME> <TITLE> <DESC> - -# LOGIN (plus de liste de cms autorisés) -daikoku login - -# PULL -daikoku pull apis -daikoku pull mails - -# VERSION -daikoku version - -# WATCH -daikoku watch - -# CMS API -à créer - -# documentations folder sorti des apis - -# créer un fichier .secrets -avec les apikeys et les cookies \ No newline at end of file diff --git a/daikoku/app/controllers/HomeController.scala b/daikoku/app/controllers/HomeController.scala index 1b8f77cbd..6178d11e6 100644 --- a/daikoku/app/controllers/HomeController.scala +++ b/daikoku/app/controllers/HomeController.scala @@ -1,30 +1,21 @@ package fr.maif.otoroshi.daikoku.ctrls -import com.github.blemale.scaffeine.{Cache, Scaffeine} -import com.nimbusds.jose.util.StandardCharset import controllers.Assets import daikoku.BuildInfo import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionMaybeWithGuest, DaikokuActionMaybeWithoutUser, DaikokuActionMaybeWithoutUserContext} import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent -import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.{DaikokuAdminOrSelf, TenantAdminOnly} -import fr.maif.otoroshi.daikoku.ctrls.authorizations.sync.TeamMemberOnly +import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.TenantAdminOnly import fr.maif.otoroshi.daikoku.domain._ -import fr.maif.otoroshi.daikoku.domain.json.{CmsFileFormat, CmsPageFormat, CmsRequestRenderingFormat} +import fr.maif.otoroshi.daikoku.domain.json.CmsRequestRenderingFormat import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.utils.Errors import org.apache.pekko.http.scaladsl.util.FastFuture -import org.joda.time.DateTime import play.api.i18n.{I18nSupport, MessagesApi} import play.api.libs.json._ import play.api.mvc._ -import java.io.{ByteArrayOutputStream, File, FileInputStream, FileOutputStream} -import java.util -import java.util.concurrent.TimeUnit -import java.util.zip.{ZipEntry, ZipInputStream, ZipOutputStream} import scala.collection.mutable -import scala.concurrent.duration.DurationInt import scala.concurrent.{ExecutionContext, Future} import scala.util.matching.Regex @@ -42,13 +33,6 @@ class HomeController( implicit val e: Env = env implicit val m: MessagesApi = messagesApi - case class CmsPageCache(contentType: String, content: String) - - private val cache: Cache[String, CmsPageCache] = Scaffeine() - .expireAfterWrite(60.seconds) - .maximumSize(100) - .build[String, CmsPageCache]() - private def manageCmsHome[A]( ctx: DaikokuActionMaybeWithoutUserContext[A], redirectTo: Result @@ -234,7 +218,7 @@ class HomeController( )) => redirectToLoginPage(ctx) case Some(r) if !r.visible() => cmsPageNotFound(ctx) - case Some(page) => render(ctx, page.toCmsPage(ctx.tenant.id), Some(req), skipCache = true, req.fields) + case Some(page) => render(ctx, page.toCmsPage(ctx.tenant.id), Some(req), req.fields) case None => cmsPageNotFound(ctx) } } @@ -309,7 +293,7 @@ class HomeController( case None => render(ctx, page) case Some(layout) => val fields = Map("email" -> JsString(page.body)) - render(ctx, layout, skipCache = true, fields = fields) + render(ctx, layout, fields = fields) } } else { render(ctx, page) @@ -364,55 +348,17 @@ class HomeController( ctx: DaikokuActionMaybeWithoutUserContext[A], r: CmsPage, req: Option[CmsRequestRendering] = None, - skipCache: Boolean = false, fields: Map[String, JsValue] = Map.empty[String, JsValue] ) = { - val forceReloading: Boolean = - ctx.request - .getQueryString("force_reloading") - .contains("true") || skipCache - - val cacheId = - s"${ctx.user.map(_.id.value).getOrElse("")}-${r.path.getOrElse("")}" - - cache - .policy() - .expireAfterWrite() - .ifPresent(eviction => { - val ttl: Long = ctx.tenant.style - .map(_.cacheTTL) - .getOrElse(60000) - .asInstanceOf[Number] - .longValue - if (eviction.getExpiresAfter(TimeUnit.MILLISECONDS) != ttl) { - cache.invalidateAll() - eviction.setExpiresAfter(ttl, TimeUnit.MILLISECONDS) - } + r.render(ctx, None, req = req, jsonToCombine = fields) + .map(res => { + Ok(res._1).as(res._2) }) - - if (forceReloading) { - r.render(ctx, None, req = req, jsonToCombine = fields) - .map(res => Ok(res._1).as(res._2)) - } else - cache.getIfPresent(cacheId) match { - case Some(value) => - FastFuture.successful(Ok(value.content).as(value.contentType)) - case _ => - r.render(ctx, None, req = req, jsonToCombine = fields) - .map(res => { - cache.put( - cacheId, - CmsPageCache(content = res._1, contentType = res._2) - ) - Ok(res._1).as(res._2) - }) - } } private def cmsPageByIdWithoutAction[A]( ctx: DaikokuActionMaybeWithoutUserContext[A], id: String, - skipCache: Boolean = false, fields: Map[String, JsValue] = Map.empty ) = { env.dataStore.cmsRepo.forTenant(ctx.tenant).findByIdNotDeleted(id).flatMap { @@ -424,14 +370,13 @@ class HomeController( s"/auth/${ctx.tenant.authProvider.name}/login?redirect=${ctx.request.path}" ) ) - case Some(page) => - render(ctx, page, skipCache = skipCache, fields = fields) + case Some(page) => render(ctx, page, fields = fields) } } def cmsPageById(id: String) = DaikokuActionMaybeWithoutUser.async { ctx => - cmsPageByIdWithoutAction(ctx, id, skipCache = true) + cmsPageByIdWithoutAction(ctx, id) } def advancedRenderCmsPageById(id: String) = @@ -439,7 +384,6 @@ class HomeController( cmsPageByIdWithoutAction( ctx, id, - skipCache = true, fields = ctx.request.body .asOpt[JsObject] .flatMap(body => (body \ "fields").asOpt[Map[String, JsValue]]) diff --git a/daikoku/app/controllers/admin-api-openapi.json b/daikoku/app/controllers/admin-api-openapi.json index bcf5ff602..474dbb0b3 100644 --- a/daikoku/app/controllers/admin-api-openapi.json +++ b/daikoku/app/controllers/admin-api-openapi.json @@ -290,11 +290,6 @@ "type": "string", "nullable": true }, - "cacheTTL": { - "type": "integer", - "format": "int32", - "nullable": true - }, "logo": { "type": "string", "nullable": true diff --git a/daikoku/app/domain/json.scala b/daikoku/app/domain/json.scala index c8912dc69..70aec487f 100644 --- a/daikoku/app/domain/json.scala +++ b/daikoku/app/domain/json.scala @@ -2083,8 +2083,7 @@ object json { .asOpt[String] .getOrElse("/assets/images/daikoku.svg"), footer = (json \ "footer") - .asOpt[String], - cacheTTL = (json \ "cacheTTL").asOpt[Int].getOrElse(60000) + .asOpt[String] ) ) } recover { @@ -2121,7 +2120,6 @@ object json { .map(JsString.apply) .getOrElse(JsNull) .as[JsValue], - "cacheTTL" -> o.cacheTTL, "homePageVisible" -> o.homePageVisible, "logo" -> o.logo, "footer" -> o.footer diff --git a/daikoku/app/domain/tenantEntities.scala b/daikoku/app/domain/tenantEntities.scala index a7bcb9e90..9568a5b58 100644 --- a/daikoku/app/domain/tenantEntities.scala +++ b/daikoku/app/domain/tenantEntities.scala @@ -177,7 +177,6 @@ case class DaikokuStyle( homeCmsPage: Option[String] = None, notFoundCmsPage: Option[String] = None, authenticatedCmsPage: Option[String] = None, - cacheTTL: Int = 60000, logo: String = "/assets/images/daikoku.svg", footer: Option[String] = None ) extends CanJson[DaikokuStyle] { diff --git a/daikoku/javascript/src/components/adminbackoffice/tenants/forms/CustomizationForm.tsx b/daikoku/javascript/src/components/adminbackoffice/tenants/forms/CustomizationForm.tsx index 4fa8ffdf7..4aa1703b4 100644 --- a/daikoku/javascript/src/components/adminbackoffice/tenants/forms/CustomizationForm.tsx +++ b/daikoku/javascript/src/components/adminbackoffice/tenants/forms/CustomizationForm.tsx @@ -104,15 +104,6 @@ export const CustomizationForm = ({ tenant, updateTenant }: { tenant?: ITenantFu options: queryCMSPages.data?.map((t) => ({ label: `${t.path}`, value: t.id })), }, - cacheTTL: { - type: 'number', - visible: tenant?.style?.homePageVisible, - props: { - label: translate('tenant_edit.cache'), - help: translate('tenant_edit.cache_help'), - disabled: !tenant?.style?.homePageVisible, - }, - }, logo: urlWithAssetButton(translate('Logo'), translate({ key: 'set.from.assets', replacements: [translate('set.logo')] }), MimeTypeFilter.image), cssUrl: urlWithAssetButton(translate('CSS URL'), translate({ key: 'set.from.assets', replacements: [translate('set.css')] }), MimeTypeFilter.css), css: { @@ -164,7 +155,7 @@ export const CustomizationForm = ({ tenant, updateTenant }: { tenant?: ITenantFu }, { label: translate('Pages'), - flow: ['homePageVisible', 'homeCmsPage', 'notFoundCmsPage', 'authenticatedCmsPage', 'cacheTTL', 'footer'], + flow: ['homePageVisible', 'homeCmsPage', 'notFoundCmsPage', 'authenticatedCmsPage', 'footer'], collapsed: true } ] diff --git a/daikoku/javascript/src/types/tenant.ts b/daikoku/javascript/src/types/tenant.ts index d17fe2b2c..bdcef84a5 100644 --- a/daikoku/javascript/src/types/tenant.ts +++ b/daikoku/javascript/src/types/tenant.ts @@ -153,7 +153,6 @@ interface ITenantStyle { homeCmsPage?: string; notFoundCmsPage?: string; authenticatedCmsPage?: string; - cacheTTL: number; logo: string; footer?: string; } diff --git a/daikoku/public/swaggers/admin-api-openapi.json b/daikoku/public/swaggers/admin-api-openapi.json index 7cd23beae..f4772cb58 100644 --- a/daikoku/public/swaggers/admin-api-openapi.json +++ b/daikoku/public/swaggers/admin-api-openapi.json @@ -305,16 +305,6 @@ "type": "string", "nullable": true }, - "cacheTTL": { - "type": "integer", - "format": "int32", - "nullable": true - }, - "cmsHistoryLength": { - "type": "integer", - "format": "int32", - "nullable": true - }, "logo": { "type": "string", "nullable": true diff --git a/daikoku/public/swaggers/admin-api-openapi.yaml b/daikoku/public/swaggers/admin-api-openapi.yaml index b31d5e3df..c90a534bf 100644 --- a/daikoku/public/swaggers/admin-api-openapi.yaml +++ b/daikoku/public/swaggers/admin-api-openapi.yaml @@ -214,14 +214,6 @@ components: authenticatedCmsPage: type: string nullable: true - cacheTTL: - type: integer - format: int32 - nullable: true - cmsHistoryLength: - type: integer - format: int32 - nullable: true logo: type: string nullable: true diff --git a/manual/docs/04-cli/041-informations/index.mdx b/manual/docs/04-cli/041-informations/index.mdx index db6277933..679b8009f 100644 --- a/manual/docs/04-cli/041-informations/index.mdx +++ b/manual/docs/04-cli/041-informations/index.mdx @@ -11,17 +11,15 @@ import ArchiImageUrl from '@site/static/img/cli-architecture.png'; /> </div> - - # Installation This project can be installed and compiled from source with this Cargo command: ``` -$ cargo install daikokucli +$ cargo install daikoku or -$ brew tap maif/daikokucli -$ brew install daikokucli +$ brew tap maif/daikoku +$ brew install daikoku ``` Additionally there are [precompiled artifacts built on CI][artifacts] which are @@ -32,81 +30,126 @@ available for download as well. Installation can be confirmed with: ``` -$ daikokucli version +$ daikoku version ``` Subcommands can be explored with: ``` -$ daikokucli help +$ daikoku help ``` # Core commands Daikokucli uses your home folder to store the list of projects inside a `.daikoku` file. Each project created with the CLI should contain a `src` folder and a `.daikoku/.environments`. This file will contain -You can start a new project +You can start a new project from scratch + +```sh +daikoku cms init --name=<PROJECT_NAME> --path=<PROJECT_PATH_OR_CURRENT_FOLDER> +``` + +or import an existing one ```sh -daikokucli create --name=<PROJECT_NAME> --path=<PROJECT_PATH_OR_CURRENT_FOLDER> +daikoku cms migrate --name=<PROJECT_NAME> --path=<PROJECT_PATH_OR_CURRENT_FOLDER> --server=<DAIKOKU_SERVER> --apikey=<CMS_APIKEY> ``` then add a default Daikoku environment ```sh -daikokucli environments add --name=<ENVIRONMENT_NAME> --server=<ENVIROMNENT_SERVER> +daikoku environments add --name=<ENVIRONMENT_NAME> --server=<ENVIROMNENT_SERVER> --apikey=<CMS_APIKEY> ``` > The Daikoku server has to be reachable and will be checked before saving the configuration + +you can sync the new project with your Daikoku instance and fetch mails and apis + +```sh +daikoku pull apis +daikoku pull mails +``` + you can start to develop and watch file changes ```sh -daikokucli watch +daikoku watch ``` -Common practices involve utilizing the directives within the Daikoku CMS to access private entities based on the connected user's permissions. You have the option to configure the token for accessing your CMS with an authenticated user by pasting the token from your Daikoku profile page. +Common practices involve utilizing the directives within the Daikoku CMS to access private entities based on the connected user's permissions. ```sh -daikokucli login --token=<YOUR_TOKEN> +daikoku login ``` -If you have many environments you can switch between us simply using +You can start to follow your changes using ```sh -daikokucli watch --environment=<NAME_OF_YOUR_ENVIRONMENT> +daikoku watch --environment=<NAME_OF_YOUR_ENVIRONMENT> ``` or permanently by changing the default project or environment ```sh -daikokucli environments default --name=<NAME_OF_YOUR_ENVIRONMENT> -daikokucli projects default --name=<NAME_OF_YOUR_PROJECT> +daikoku environments switch --name=<NAME_OF_YOUR_ENVIRONMENT> +daikoku cms switch --name=<NAME_OF_YOUR_PROJECT> ``` you can view the currently used project and the others ```sh -daikokucli projects list +daikoku cms list ``` At anytime, you can track an existing CMS folder or update its information ```sh -daikokucli projects add --name=<NAME_OF_YOUR_PROJECT> --path=<PATH_TO_YOUR_PROJECT> --overwrite=<true|false> +daikoku cms add --name=<NAME_OF_YOUR_PROJECT> --path=<PATH_TO_YOUR_PROJECT> --overwrite=<true|false> ``` -Once ready, you can synchronize your sources with the Daikoku environment +Once ready, you can push your sources with the Daikoku environment ```sh -daikokucli sync +daikoku push ``` ## Start a new project by importing an existing one If you already have a legacy CMS on your Daikoku, you can start by importing it ```sh -daikokucli projects import --name=<NEW_NAME_OF_YOUR_PROJECT> \ +daikoku projects migrate --name=<NEW_NAME_OF_YOUR_PROJECT> \ --path=<PATH_TO_THE_NEW_PROJECT> \ --server=<DAIKOKU_SERVER_TO_PULL> \ - --token=<AUTHENTICATION_TOKEN> + --apikey=<CMS_APIKEY> +``` + +# CMS Structure + +The CMS projects adhere to the following strict file structure: + +- `.daikoku`: This hidden folder is used exclusively by Daikoku to store environments, secrets, and credentials. The only file you can edit here is the .daikokuignore, which allows you to exclude specific files from being pushed. + +- `assets`: Files placed in this folder can be uploaded to the Daikoku S3 Bucket associated with your project. They can then be accessed using a generated slug. + +- `src`: This folder contains all other source files, organized into the following subdirectories: + - `apis`: Lists all APIs available in your Daikoku. Each API has its own subfolder containing a header and description folder. + - `data`: Contains external data files such as JSON, YAML, CSV, and others. + - `pages`: Stores all source files that are not categorized under apis, data, scripts, mails, or styles. + - `scripts`: Contains JavaScript (JS) files. + - `styles`: Contains CSS files. + - `documentations` : Contains files that can be used as documentation page of APIs + +# Dynamic routes + +The CLI uses file-system routing where folders are used to create nested routes. Each folder represents a route segment that maps to a URL segment. + +You can create separate UIs for each route using page.html files. `page.html` is a special CLI file that contains html content. + +To create a nested route, you can nest folders inside each other and add page.html files inside them. For example: + +```sh +src/pages/page.html -> mysite.com/ +src/pages/invoices/page.html -> mysite.com/invoices +src/pages/offres.html -> mysite.com/offres +src/pages/apis/api/[apiId] -> mysite.com/apis/api/any-kind-of-api (the apiId value can be use in the page as mustache variable using {{apiId}}) ``` # Manage your assets @@ -115,7 +158,7 @@ You can manage your images, diagrams, or any type of files directly by creating Each asset is save in the S3 of your Daikoku using the following command ```sh -daikokucli assets add --filename=<ASSET_FILENAME> \ +daikoku assets push --filename=<ASSET_FILENAME> \ --path=<ONLY_NESTED_FOLDER_BEHIND_ASSETS_FOLDER> \ --desc=<ASSET_DESCRIPTION> \ --title=<ASSET_TITLE> @@ -127,530 +170,431 @@ If you require a particular `slug` for your asset, you have the option to replac To delete your asset you have to give the `filename` and the `slug` iif it differs ```sh -daikokucli assets remove --slug=<CUSTOM_SLUG> --filename=<ASSET_FILENAME> +daikoku assets remove --slug=<CUSTOM_SLUG> --filename=<ASSET_FILENAME> ``` As others commands, you can display all registered assets ```sh -daikokucli assets list +daikoku assets list ``` If you prefer to synchronize all assets with a single command, it offers speed advantages over doing so individually, albeit with reduced configurability. ```sh -daikokucli assets sync +daikoku assets sync ``` -# Authorized applications +# Manage documentation pages + +You already have many choices in Daikoku to create the APIs's documentation. But, with the release of the CMS, you can now write your documentation with it. The documentations pages have to be written in the `src/documentations` folder and can be named as you wish. -Just before running the `daikoku login` command, you have to configure your tenant by adding the CLI server. By default, the server is set to `http://localhost:3334` but you can overwrite it using the `WATCHING_PORT` environment variable. +The recommended usage to create a new documentation page is to use the CLI as following : ```sh -daikokucli login +daikoku generate documentation --filename=my-new-documentation-page \ + --title="Title of the page" \ + --desc="The description of this page" ``` # CMS Directives -<details> - <summary> - daikoku-user - </summary> - Parameters - <ul> - <li>string user id</li> - </ul> - - ```html - {{#daikoku-user "{{userId}}"}} - <div> - <span>{{user.name}}</span> - <img src="{{user.picture}}" /> - </div> - {{/daikoku-user}} - ``` -</details> - - -<details> - <summary> - daikoku-owned-apis - </summary> - Parameters - <ul> - <li>visibility: can be Private | Public | All</li> - </ul> - - ```html - {{#daikoku-owned-apis visibility="Private"}} - <span>Mon api : {{api.name}}</span> - {{/daikoku-owned-apis}} - ``` -</details> - -<details> - <summary> - daikoku-owned-api - </summary> - Parameters - <ul> - <li>String API id</li> - <li>The API version is optional, but it defaults to 1.0.0 when not specified.</li> - </ul> +## daikoku-user + +`parameters`: +- string user id +```html +{{#daikoku-user "{{userId}}"}} + <div> + <span>{{user.name}}</span> + <img src="{{user.picture}}" /> + </div> +{{/daikoku-user}} +``` + +## daikoku-owned-apis + +`parameters` +- visibility: can be Private | Public | All +```html +{{#daikoku-owned-apis visibility="Private"}} + <span>Mon api : {{api.name}}</span> +{{/daikoku-owned-apis}} +``` + +## daikoku-owned-api +`parameters`: +- String API id +- The API version is optional, but it defaults to 1.0.0 when not specified. - ```html - {{#daikoku-owned-api "{{apiId}}" version="1.0.0"}} - <span>Mon api : {{api.name}}</span> - {{/daikoku-owned-api}} - ``` -</details> - -<details> - <summary> - daikoku-json-owned-apis - </summary> - Parameters - <ul> - <li>Visibility : Private, Public or All</li> - </ul> - - ```html - {{#daikoku-json-owned-apis visibility="Private"}} - - {{/daikoku-json-owned-apis}} - ``` -</details> - -<details> - <summary> - daikoku-json-owned-api - </summary> - Parameters - <ul> - <li>The API id, string value expected</li> - <li>The API version is optional, but it defaults to 1.0.0 when not specified.</li> - </ul> - - ```html - {{#daikoku-json-owned-api "{{apiId}}" version="1.0.0"}} - {{/daikoku-json-owned-api}} - ``` -</details> - -<details> - <summary> - daikoku-owned-teams - </summary> - - ```html - {{#daikoku-owned-teams}} - <span>Ma team : {{team.name}} - {{/daikoku-owned-teams}} - ``` -</details> - -<details> - <summary> - daikoku-owned-team - </summary> - Parameters - <ul> - <li>The team ID, string value expected </li> - </ul> +```html +{{#daikoku-owned-api "{{apiId}}" version="1.0.0"}} + <span>Mon api : {{api.name}}</span> +{{/daikoku-owned-api}} +``` + +## daikoku-json-owned-apis +`parameters`: +- Visibility : Private, Public or All +```html +{{#daikoku-json-owned-apis visibility="Private"}} + +{{/daikoku-json-owned-apis}} +``` - ```html - {{#daikoku-owned-team "{{teamId}}"}} - <span>Mon team : {{team.name}}</span> - {{/daikoku-owned-team}} - ``` -</details> - - -<details> - <summary> - daikoku-json-owned-teams - </summary> - - ```html - {{daikoku-json-owned-teams}} - ``` -</details> - -<details> - <summary> - daikoku-json-owned-team - </summary> - Parameters - <ul> - <li>The Team ID, String value expected</li> - </ul> - - ```html - {{#daikoku-json-owned-team "{{teamId}}"}} - - {{/daikoku-json-owned-team}} - ``` -</details> - -<details> - <summary> - tenant - </summary> - - ```html - {{tenant.name}} <li>{{tenant.style.description}} - ``` -</details> - -<details> - <summary> - is_admin - </summary> +## daikoku-json-owned-api +`parameters`: +- The API id, string value expected +- The API version is optional, but it defaults to 1.0.0 when not specified. + +```html +{{#daikoku-json-owned-api "{{apiId}}" version="1.0.0"}} +{{/daikoku-json-owned-api}} +``` + +## daikoku-owned-teams + +```html +{{#daikoku-owned-teams}} + <span>Ma team : {{team.name}} +{{/daikoku-owned-teams}} +``` + +## daikoku-owned-team +`parameters`: +- The team ID, string value expected" +```html +{{#daikoku-owned-team "{{teamId}}"}} + <span>Mon team : {{team.name}}</span> +{{/daikoku-owned-team}} +``` + +## daikoku-json-owned-teams + +```html +{{daikoku-json-owned-teams}} +``` + +## daikoku-json-owned-team +`parameters`: +- The Team ID, String value expected + +```html +{{#daikoku-json-owned-team "{{teamId}}"}} + +{{/daikoku-json-owned-team}} +``` + +## tenant + +```html +{{tenant.name}} - {{tenant.style.description}} +``` + +## is_admin - ```html - {{is_admin}} - ``` -</details> +```html +{{is_admin}} +``` -<details> - <summary> - connected - </summary> +## connected - ```html - {{connected}} - ``` -</details> +```html +{{connected}} +``` -<details> - <summary> - user - </summary> +## user - When you have an user returned from directive, you can use the following fields +When you have an user returned from directive, you can use the following fields - - `name` - - `email` - - `_id` - - `_humandReadableId` - - `picture` - - `isDaikokuAdmin` - - `starredApis` - - ```html - <div> - {{user.name}} - {{user.email}} - </div> - ``` -</details> + - `name` + - `email` + - `_id` + - `_humandReadableId` + - `picture` + - `isDaikokuAdmin` + - `starredApis` + +```html +<div> + {{user.name}} - {{user.email}} +</div> +``` -<details> - <summary> - request - </summary> - - ```html - <div> - {{request.path}} - {{request.method}} - {{request.headers}} - </div> - ``` -</details> +## request +```html +<div> + {{request.path}} - {{request.method}} - {{request.headers}} +</div> +``` -<details> - <summary> - daikoku-css - </summary> - - ```html - <div> - {{daikoku-css}} - </div> - ``` -</details> - -<details> - <summary> - for - </summary> - Parameters - <ul> - <li>the fieldname used in the helper content</li> - </ul> +## daikoku-css +```html +<div> + {{daikoku-css}} +</div> +``` + +## for +`parameters`: +- the fieldname used in the helper content - ``` - {{#for '{{team.users}}' field='myuser' }} - {{myuser.userId}} - {{/for}} - ``` -</details> - -<details> - <summary> - size - </summary> +``` +{{#for '{{team.users}}' field='myuser' }} + {{myuser.userId}} +{{/for}} +``` + +## size - ```html - {{size '{{team.users}}'}} - ``` -</details> - -<details> - <summary> - ifeq - </summary> - - ```html - {{#ifeq "{{plan.type}}" "FreeWithoutQuotas"}} - You'll pay nothing and do whatever you want - {{/ifeq}} - ``` -</details> - -<details> - <summary> - ifnoteq - </summary> +```html +{{size '{{team.users}}'}} +``` + +## ifeq + +```html +{{#ifeq "{{plan.type}}" "FreeWithoutQuotas"}} + You'll pay nothing and do whatever you want +{{/ifeq}} +``` + +## ifnoteq - ```html - {{#ifnoteq "{{plan.type}}" "FreeWithoutQuotas"}} - You'll pay nothing and do whatever you want - {{/ifnoteq}} - ``` -</details> - -<details> - <summary> - getOrElse - </summary> +```html +{{#ifnoteq "{{plan.type}}" "FreeWithoutQuotas"}} + You'll pay nothing and do whatever you want +{{/ifnoteq}} +``` + +## getOrElse - ```html - {{getOrElse "{{plan.customName}}" "Un plan"}} - ``` -</details> - -<details> - <summary> - translate - </summary> - - ```html - {{translate 'Logout'}} - ``` -</details> - -<details> - <summary> - daikoku-query-param - </summary> - Parameters - <ul> - <li>the name of the query param</li> - </ul> - - ```html - {{daikoku-query-param 'my-query-param'}} - ``` -</details> - - -<details> - <summary> - daikoku-template-wrapper - </summary> - Parameters - <ul> - <li>Block path</li> - <li>List of key=value usable in content</li> - </ul> - - ```html - {{#daikoku-template-wrapper '<wrapper-id>' <named-parameter>="<value>" }} - - {{/daikoku-template-wrapper}}" - ``` -</details> - - -<details> - <summary> - daikoku-apis - </summary> - - ```html - {{#daikoku-apis}} - <span>Api : {{api.name}}</span> - {{/daikoku-apis}} - ``` -</details> - -<details> - <summary> - daikoku-api - </summary> - Parameters - <ul> - <li>API id, String value expected</li> - </ul> - - ```html - {{#daikoku-api "{{apiId}}" version="1.0.0"}} - <span>Mon api : {{api.name}}</span> - {{/daikoku-api}}" - ``` -</details> - -<details> - <summary> - daikoku-json-apis - </summary> - - ```html - {{daikoku-json-apis}} - ``` -</details> - -<details> - <summary> - daikoku-json-api - </summary> - Parameters - <ul> - <li>API Id, String value expected</li> - </ul> - - ```html - {{#daikoku-json-api "{{apiId}}" version="1.0.0"}} - - {{/daikoku-json-api}} - ``` -</details> - -<details> - <summary> - daikoku-teams - </summary> - - ```html - {{#daikoku-teams}} - <span>Team : {{team.name}}</span> - {{/daikoku-teams}} - ``` -</details> - -<details> - <summary> - daikoku-team - </summary> - Parameters - <ul> - <li>Team Id, String value expected</li> - </ul> +```html +{{getOrElse "{{plan.customName}}" "Un plan"}} +``` + +## translate + +```html +{{translate 'Logout'}} +``` + +## daikoku-query-param +`parameters`: +- the name of the query param + +```html +{{daikoku-query-param 'my-query-param'}} +``` + + +## daikoku-template-wrapper +`parameters`: +- Block path +- List of key=value usable in content + +```html +{{#daikoku-template-wrapper '<wrapper-id>' <named-parameter>="<value>" }} + +{{/daikoku-template-wrapper}}" +``` + + +## daikoku-apis + +```html +{{#daikoku-apis}} + <span>Api : {{api.name}}</span> +{{/daikoku-apis}} +``` + +## daikoku-api + +`parameters`: +- API id, String value expected + +```html +{{#daikoku-api "{{apiId}}" version="1.0.0"}} + <span>Mon api : {{api.name}}</span> +{{/daikoku-api}}" +``` + +## daikoku-json-apis + +```html +{{daikoku-json-apis}} +``` + +## daikoku-json-api + +`parameters`: +- API Id, String value expected + +```html +{{#daikoku-json-api "{{apiId}}" version="1.0.0"}} + +{{/daikoku-json-api}} +``` + +## daikoku-teams + +```html +{{#daikoku-teams}} + <span>Team : {{team.name}}</span> +{{/daikoku-teams}} +``` + +## daikoku-team + +`parameters`: +- Team Id, String value expected + +```html +{{#daikoku-team "{{<teamId>}}"}} + <span>My team : {{team.name}}</span> +{{/daikoku-team}} +``` + + +## daikoku-json-teams + +```html +{{daikoku-json-teams}} +``` + +## daikoku-json-team + +`parameters`: +- Team Id, String value expected + +```html +{{#daikoku-json-team "{{<teamId>}}"}} + +{{/daikoku-json-team}} +``` + +## daikoku-documentations + +`parameters`: +- API id, String value expected - ```html - {{#daikoku-team "{{<teamId>}}"}} - <span>My team : {{team.name}}</span> - {{/daikoku-team}} - ``` -</details> - - -<details> - <summary> - daikoku-json-teams - </summary> - - ```html - {{daikoku-json-teams}} - ``` -</details> - -<details> - <summary> - daikoku-json-team - </summary> - Parameters - <ul> - <li>Team Id, String value expected</li> - </ul> +```html +{{#daikoku-documentations "{{<apiId>}}"}} + <span>{{documentation.title}}</span> +{{/daikoku-documentations}} +``` + +## daikoku-documentations-page - ```html - {{#daikoku-json-team "{{<teamId>}}"}} - - {{/daikoku-json-team}} - ``` - -</details> - -<details> - <summary> - daikoku-documentations - </summary> - Parameters - <ul> - <li>API id, String value expected</li> - </ul> +`parameters`: +- API ID, String value expected +- Page ID as String value - ```html - {{#daikoku-documentations "{{<apiId>}}"}} - <span>{{documentation.title}}</span> - {{/daikoku-documentations}} - ``` -</details> - -<details> - <summary> - daikoku-documentations-page - </summary> - Parameters - <ul> - <li>API ID, String value expected</li> - <li>Page ID as String value</li> - </ul> +```html +{{#daikoku-documentations-page "<apiId>" page="<pageId>"}} + {{documentation.content}} +{{/daikoku-documentations-page}} +``` + +## daikoku-documentations-page-id + +`parameters`: +- Team ID, String value expected +- The named page parameter corresponding to the id of the expected page + +```html +{{#daikoku-documentations-page-id "<apiId>" page="<pageId>"}} + {{content}} +{{/daikoku-documentations-page-id}}" +``` + +## daikoku-plans - ```html - {{#daikoku-documentations-page "<apiId>" page="<pageId>"}} - {{documentation.content}} - {{/daikoku-documentations-page}} - ``` -</details> - -<details> - <summary> - daikoku-documentations-page-id - </summary> - Parameters - <ul> - <li>Team ID, String value expected</li> - <li>The named page parameter corresponding to the id of the expected page</li> - </ul> - - ```html - {{#daikoku-documentations-page-id "<apiId>" page="<pageId>"}} - {{content}} - {{/daikoku-documentations-page-id}}" - ``` -</details> - -<details> - <summary> - daikoku-plans - </summary> - Parameters - <ul> - <li>API ID</li> - </ul> - - ```html - {{#daikoku-plans "<apiId>"}} - <span>{{plan.type}}</span> - {{/daikoku-plans}} - ``` -</details> +`parameters`: +- API ID + +```html +{{#daikoku-plans "<apiId>"}} + <span>{{plan.type}}</span> +{{/daikoku-plans}} +``` # License This project is licensed under the Apache 2.0 license with the LLVM exception. + +# Commands + +The following commands must be run, replacing `<parameter>` with `--parameter=value`. + +# PROJECT commands +```sh +daikoku cms init <NAME> <PATH> +daikoku cms migrate <NAME> <PATH> <SERVER> <APIKEY> + +daikoku cms list +daikoku cms add <NAME> <PATH> <OVERWRITE> +daikoku cms switch <NAME> +daikoku cms remove <NAME> <REMOVE_FILES> +daikoku cms clear <FORCE> +``` + +# PUSH commands +```sh +daikoku push <DRY_RUN> <FILEPATH> +``` + +# ASSETS commands +```sh +daikoku assets push <FILENAME> <TITLE> <DESC> <PATH> <SLUG> +daikoku assets remove <FILENAME> <PATH> <SLUG> +daikoku assets list +daikoku assets sync +``` + +# ENVIRONMENTS commands +```sh +daikoku environments clear <FORCE> +daikoku environments add <NAME> <SERVER> <OVERWRITE> +daikoku environments switch <NAME> +daikoku environments remove <NAME> +daikoku environments info <NAME> <FULL> +daikoku environments list +daikoku environments config <APIKEY> +``` + +# GENERATE commands +```sh +daikoku generate documentation <FILENAME> <TITLE> <DESC> +``` + +# LOGIN +```sh +daikoku login +``` + +# PULL commands +```sh +daikoku pull apis +daikoku pull mails +``` + +# VERSION commands +```sh +daikoku version +``` + +# WATCH commands +```sh +daikoku watch +``` + ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, -shall be licensed as above, without any additional terms or conditions. \ No newline at end of file +shall be licensed as above, without any additional terms or conditions. + +#### Run tests +``` +cargo test --test <filename> -- --nocapture --test-threads 1 +```