From e9b2f238b9c05edfb8f198c4d3e0ef966e2db50c Mon Sep 17 00:00:00 2001 From: Gallay Lajos Date: Wed, 18 Feb 2026 12:42:09 +0100 Subject: [PATCH 01/26] feat: MVP 1. --- common/package.json | 1 + common/schemas/dependencies-api.json | 960 ++++++++ common/schemas/entities.json | 270 ++- common/schemas/github-repositories-api.json | 959 ++++++++ common/schemas/identity-api.json | 299 +++ common/schemas/install-api.json | 227 ++ common/schemas/services-api.json | 2133 +++++++++++++++++ common/schemas/stacks-api.json | 956 ++++++++ common/schemas/tokens-api.json | 651 +++++ common/src/apis/dependencies.ts | 40 + common/src/apis/github-repositories.ts | 40 + common/src/apis/identity.ts | 27 + common/src/apis/index.ts | 7 + common/src/apis/install.ts | 20 + common/src/apis/services.ts | 52 + common/src/apis/stacks.ts | 41 + common/src/apis/tokens.ts | 21 + common/src/bin/create-schemas.ts | 38 +- common/src/boilerplate-api.ts | 20 - common/src/index.ts | 3 +- common/src/models/api-token.ts | 8 + common/src/models/dependency.ts | 9 + common/src/models/github-repository.ts | 9 + common/src/models/index.ts | 5 + common/src/models/service.ts | 34 + common/src/models/stack.ts | 8 + common/src/stack-craft-api.ts | 27 +- common/src/websocket/index.ts | 27 + e2e/page.spec.d.ts | 1 + e2e/page.spec.js | 29 + e2e/page.spec.js.map | 1 + frontend/package.json | 1 + frontend/src/components/body.tsx | 8 +- .../entity-forms/dependency-form.tsx | 66 + .../entity-forms/github-repo-form.tsx | 64 + .../components/entity-forms/service-form.tsx | 110 + .../components/entity-forms/stack-form.tsx | 72 + frontend/src/components/header.tsx | 19 +- frontend/src/components/layout.tsx | 77 +- frontend/src/components/log-viewer.tsx | 100 + .../components/service-status-indicator.tsx | 46 + frontend/src/components/service-table.tsx | 189 ++ frontend/src/components/stack-selector.tsx | 39 + frontend/src/components/wizard-step.tsx | 48 + frontend/src/pages/dashboard/index.tsx | 125 + .../src/pages/import-export/export-stack.tsx | 73 + .../src/pages/import-export/import-stack.tsx | 69 + .../installer/check-prerequisites-step.tsx | 37 + .../src/pages/installer/create-admin-step.tsx | 57 + frontend/src/pages/installer/index.tsx | 32 + frontend/src/pages/installer/success-step.tsx | 19 + frontend/src/pages/installer/welcome-step.tsx | 19 + .../src/pages/services/service-detail.tsx | 113 + frontend/src/pages/services/service-logs.tsx | 32 + frontend/src/pages/settings/user-settings.tsx | 167 ++ .../api-clients/dependencies-api-client.ts | 15 + .../api-clients/github-repos-api-client.ts | 15 + .../identity-api-client.ts} | 8 +- frontend/src/services/api-clients/index.ts | 6 + .../api-clients/install-api-client.ts | 15 + .../api-clients/services-api-client.ts | 15 + .../services/api-clients/stacks-api-client.ts | 15 + .../services/api-clients/tokens-api-client.ts | 15 + frontend/src/services/install-service.ts | 22 + frontend/src/services/session.ts | 16 +- .../src/services/stack-craft-api-client.ts | 16 +- frontend/src/services/websocket-service.ts | 70 + frontend/vite.config.d.ts | 2 + frontend/vite.config.js | 20 + frontend/vite.config.js.map | 1 + playwright.config.d.ts | 3 + playwright.config.js | 37 + playwright.config.js.map | 1 + service/package.json | 2 + .../actions/check-dependency-action.ts | 35 + .../setup-dependencies-rest-api.ts | 58 + .../actions/validate-repo-action.ts | 34 + .../setup-github-repos-rest-api.ts | 58 + .../identity/actions/password-reset-action.ts | 44 + .../identity/setup-identity-rest-api.ts | 48 + .../install/actions/get-service-status.ts | 9 + .../install/actions/post-install-action.ts | 11 + .../app-models/install/service-installer.ts | 39 + .../install/setup-install-rest-api.ts | 28 + .../actions/service-lifecycle-action.ts | 54 + .../services/actions/service-logs-action.ts | 13 + .../services/setup-services-rest-api.ts | 61 + .../stacks/actions/export-stack-action.ts | 26 + .../stacks/actions/import-stack-action.ts | 44 + .../stacks/setup-stacks-rest-api.ts | 58 + .../tokens/setup-tokens-rest-api.ts | 102 + service/src/config.ts | 66 +- service/src/get-cors-options.ts | 8 + service/src/get-port.ts | 1 + service/src/mcp/mcp-server.ts | 218 ++ service/src/mcp/setup-mcp.ts | 34 + service/src/middleware/bearer-token-auth.ts | 41 + service/src/service.ts | 83 +- service/src/services/git-service.ts | 65 + service/src/services/git-watcher.ts | 120 + service/src/services/process-manager.ts | 252 ++ service/src/services/websocket-service.ts | 31 + vitest.config.d.mts | 2 + vitest.config.mjs | 31 + vitest.config.mjs.map | 1 + yarn.lock | 553 ++++- 106 files changed, 10849 insertions(+), 178 deletions(-) create mode 100644 common/schemas/dependencies-api.json create mode 100644 common/schemas/github-repositories-api.json create mode 100644 common/schemas/identity-api.json create mode 100644 common/schemas/install-api.json create mode 100644 common/schemas/services-api.json create mode 100644 common/schemas/stacks-api.json create mode 100644 common/schemas/tokens-api.json create mode 100644 common/src/apis/dependencies.ts create mode 100644 common/src/apis/github-repositories.ts create mode 100644 common/src/apis/identity.ts create mode 100644 common/src/apis/index.ts create mode 100644 common/src/apis/install.ts create mode 100644 common/src/apis/services.ts create mode 100644 common/src/apis/stacks.ts create mode 100644 common/src/apis/tokens.ts delete mode 100644 common/src/boilerplate-api.ts create mode 100644 common/src/models/api-token.ts create mode 100644 common/src/models/dependency.ts create mode 100644 common/src/models/github-repository.ts create mode 100644 common/src/models/service.ts create mode 100644 common/src/models/stack.ts create mode 100644 common/src/websocket/index.ts create mode 100644 e2e/page.spec.d.ts create mode 100644 e2e/page.spec.js create mode 100644 e2e/page.spec.js.map create mode 100644 frontend/src/components/entity-forms/dependency-form.tsx create mode 100644 frontend/src/components/entity-forms/github-repo-form.tsx create mode 100644 frontend/src/components/entity-forms/service-form.tsx create mode 100644 frontend/src/components/entity-forms/stack-form.tsx create mode 100644 frontend/src/components/log-viewer.tsx create mode 100644 frontend/src/components/service-status-indicator.tsx create mode 100644 frontend/src/components/service-table.tsx create mode 100644 frontend/src/components/stack-selector.tsx create mode 100644 frontend/src/components/wizard-step.tsx create mode 100644 frontend/src/pages/dashboard/index.tsx create mode 100644 frontend/src/pages/import-export/export-stack.tsx create mode 100644 frontend/src/pages/import-export/import-stack.tsx create mode 100644 frontend/src/pages/installer/check-prerequisites-step.tsx create mode 100644 frontend/src/pages/installer/create-admin-step.tsx create mode 100644 frontend/src/pages/installer/index.tsx create mode 100644 frontend/src/pages/installer/success-step.tsx create mode 100644 frontend/src/pages/installer/welcome-step.tsx create mode 100644 frontend/src/pages/services/service-detail.tsx create mode 100644 frontend/src/pages/services/service-logs.tsx create mode 100644 frontend/src/pages/settings/user-settings.tsx create mode 100644 frontend/src/services/api-clients/dependencies-api-client.ts create mode 100644 frontend/src/services/api-clients/github-repos-api-client.ts rename frontend/src/services/{boilerplate-api-client.ts => api-clients/identity-api-client.ts} (59%) create mode 100644 frontend/src/services/api-clients/index.ts create mode 100644 frontend/src/services/api-clients/install-api-client.ts create mode 100644 frontend/src/services/api-clients/services-api-client.ts create mode 100644 frontend/src/services/api-clients/stacks-api-client.ts create mode 100644 frontend/src/services/api-clients/tokens-api-client.ts create mode 100644 frontend/src/services/install-service.ts create mode 100644 frontend/src/services/websocket-service.ts create mode 100644 frontend/vite.config.d.ts create mode 100644 frontend/vite.config.js create mode 100644 frontend/vite.config.js.map create mode 100644 playwright.config.d.ts create mode 100644 playwright.config.js create mode 100644 playwright.config.js.map create mode 100644 service/src/app-models/dependencies/actions/check-dependency-action.ts create mode 100644 service/src/app-models/dependencies/setup-dependencies-rest-api.ts create mode 100644 service/src/app-models/github-repositories/actions/validate-repo-action.ts create mode 100644 service/src/app-models/github-repositories/setup-github-repos-rest-api.ts create mode 100644 service/src/app-models/identity/actions/password-reset-action.ts create mode 100644 service/src/app-models/identity/setup-identity-rest-api.ts create mode 100644 service/src/app-models/install/actions/get-service-status.ts create mode 100644 service/src/app-models/install/actions/post-install-action.ts create mode 100644 service/src/app-models/install/service-installer.ts create mode 100644 service/src/app-models/install/setup-install-rest-api.ts create mode 100644 service/src/app-models/services/actions/service-lifecycle-action.ts create mode 100644 service/src/app-models/services/actions/service-logs-action.ts create mode 100644 service/src/app-models/services/setup-services-rest-api.ts create mode 100644 service/src/app-models/stacks/actions/export-stack-action.ts create mode 100644 service/src/app-models/stacks/actions/import-stack-action.ts create mode 100644 service/src/app-models/stacks/setup-stacks-rest-api.ts create mode 100644 service/src/app-models/tokens/setup-tokens-rest-api.ts create mode 100644 service/src/get-cors-options.ts create mode 100644 service/src/get-port.ts create mode 100644 service/src/mcp/mcp-server.ts create mode 100644 service/src/mcp/setup-mcp.ts create mode 100644 service/src/middleware/bearer-token-auth.ts create mode 100644 service/src/services/git-service.ts create mode 100644 service/src/services/git-watcher.ts create mode 100644 service/src/services/process-manager.ts create mode 100644 service/src/services/websocket-service.ts create mode 100644 vitest.config.d.mts create mode 100644 vitest.config.mjs create mode 100644 vitest.config.mjs.map diff --git a/common/package.json b/common/package.json index 5532994..e8883d2 100644 --- a/common/package.json +++ b/common/package.json @@ -30,6 +30,7 @@ "vitest": "^4.0.18" }, "dependencies": { + "@furystack/core": "^15.0.36", "@furystack/rest": "^8.0.36" } } diff --git a/common/schemas/dependencies-api.json b/common/schemas/dependencies-api.json new file mode 100644 index 0000000..b516a7d --- /dev/null +++ b/common/schemas/dependencies-api.json @@ -0,0 +1,960 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "DependencyWritableFields": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "checkCommand": { + "type": "string" + }, + "installationHelp": { + "type": "string" + } + }, + "required": [ + "id", + "stackName", + "name", + "checkCommand", + "installationHelp" + ], + "additionalProperties": false + }, + "PostDependencyEndpoint": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/Dependency" + }, + "body": { + "$ref": "#/definitions/WithOptionalId%3CDependencyWritableFields%2C%22id%22%3E" + } + }, + "required": [ + "result", + "body" + ], + "additionalProperties": false + }, + "Dependency": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "checkCommand": { + "type": "string" + }, + "installationHelp": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "id", + "stackName", + "name", + "checkCommand", + "installationHelp", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + }, + "WithOptionalId": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "checkCommand": { + "type": "string" + }, + "installationHelp": { + "type": "string" + } + }, + "required": [ + "checkCommand", + "installationHelp", + "name", + "stackName" + ] + }, + "PatchDependencyEndpoint": { + "$ref": "#/definitions/PatchEndpoint%3CDependencyWritableFields%2C%22id%22%3E" + }, + "PatchEndpoint": { + "type": "object", + "properties": { + "body": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "checkCommand": { + "type": "string" + }, + "installationHelp": { + "type": "string" + } + }, + "additionalProperties": false + }, + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object" + } + }, + "required": [ + "body", + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for updating entities" + }, + "CheckDependencyEndpoint": { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object", + "properties": { + "satisfied": { + "type": "boolean" + }, + "output": { + "type": "string" + } + }, + "required": [ + "satisfied", + "output" + ], + "additionalProperties": false + } + }, + "required": [ + "url", + "result" + ], + "additionalProperties": false + }, + "DependenciesApi": { + "type": "object", + "properties": { + "GET": { + "type": "object", + "properties": { + "/dependencies": { + "$ref": "#/definitions/GetCollectionEndpoint%3CDependency%3E" + }, + "/dependencies/:id": { + "$ref": "#/definitions/GetEntityEndpoint%3CDependency%2C%22id%22%3E" + } + }, + "required": [ + "/dependencies", + "/dependencies/:id" + ], + "additionalProperties": false + }, + "POST": { + "type": "object", + "properties": { + "/dependencies": { + "$ref": "#/definitions/PostDependencyEndpoint" + }, + "/dependencies/:id/check": { + "$ref": "#/definitions/CheckDependencyEndpoint" + } + }, + "required": [ + "/dependencies", + "/dependencies/:id/check" + ], + "additionalProperties": false + }, + "PATCH": { + "type": "object", + "properties": { + "/dependencies/:id": { + "$ref": "#/definitions/PatchDependencyEndpoint" + } + }, + "required": [ + "/dependencies/:id" + ], + "additionalProperties": false + }, + "PUT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "DELETE": { + "type": "object", + "properties": { + "/dependencies/:id": { + "$ref": "#/definitions/DeleteEndpoint%3CDependency%2C%22id%22%3E" + } + }, + "required": [ + "/dependencies/:id" + ], + "additionalProperties": false + }, + "HEAD": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "CONNECT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "TRACE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "OPTIONS": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + } + }, + "required": [ + "GET", + "POST", + "PATCH", + "DELETE" + ], + "additionalProperties": false + }, + "GetCollectionEndpoint": { + "type": "object", + "properties": { + "query": { + "type": "object", + "properties": { + "findOptions": { + "$ref": "#/definitions/FindOptions%3CDependency%2C(%22id%22%7C%22stackName%22%7C%22name%22%7C%22checkCommand%22%7C%22installationHelp%22%7C%22createdAt%22%7C%22updatedAt%22)%5B%5D%3E" + } + }, + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/GetCollectionResult%3CDependency%3E" + } + }, + "required": [ + "query", + "result" + ], + "additionalProperties": false, + "description": "Rest endpoint model for getting / querying collections" + }, + "FindOptions": { + "type": "object", + "properties": { + "top": { + "type": "number", + "description": "Limits the hits" + }, + "skip": { + "type": "number", + "description": "Skips the first N hit" + }, + "order": { + "type": "object", + "properties": { + "id": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "stackName": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "name": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "checkCommand": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "installationHelp": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "createdAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "updatedAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + } + }, + "additionalProperties": false, + "description": "Sets up an order by a field and a direction" + }, + "select": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "id", + "stackName", + "name", + "checkCommand", + "installationHelp", + "createdAt", + "updatedAt" + ] + }, + "description": "The result set will be limited to these fields" + }, + "filter": { + "$ref": "#/definitions/FilterType%3CDependency%3E", + "description": "The fields should match this filter" + } + }, + "additionalProperties": false, + "description": "Type for default filtering model" + }, + "FilterType": { + "type": "object", + "additionalProperties": false, + "properties": { + "$and": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CDependency%3E" + } + }, + "$not": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CDependency%3E" + } + }, + "$nor": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CDependency%3E" + } + }, + "$or": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CDependency%3E" + } + }, + "id": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "stackName": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "name": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "checkCommand": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "installationHelp": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "createdAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "updatedAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "GetCollectionResult": { + "type": "object", + "properties": { + "count": { + "type": "number", + "description": "The Total count of entities" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/Dependency" + }, + "description": "List of the selected entities" + } + }, + "required": [ + "count", + "entries" + ], + "additionalProperties": false, + "description": "Response Model for GetCollection" + }, + "GetEntityEndpoint": { + "type": "object", + "properties": { + "query": { + "type": "object", + "properties": { + "select": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "id", + "stackName", + "name", + "checkCommand", + "installationHelp", + "createdAt", + "updatedAt" + ] + }, + "description": "The list of fields to select" + } + }, + "additionalProperties": false + }, + "url": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The entity's unique identifier" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/Dependency" + } + }, + "required": [ + "query", + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for getting a single entity" + }, + "DeleteEndpoint": { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object" + } + }, + "required": [ + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for deleting entities" + } + } +} \ No newline at end of file diff --git a/common/schemas/entities.json b/common/schemas/entities.json index ec29351..5e6a4f2 100644 --- a/common/schemas/entities.json +++ b/common/schemas/entities.json @@ -1,7 +1,109 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/User", "definitions": { + "ApiToken": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "name": { + "type": "string" + }, + "tokenHash": { + "type": "string" + }, + "lastUsedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "id", + "username", + "name", + "tokenHash", + "createdAt" + ], + "additionalProperties": false + }, + "Dependency": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "checkCommand": { + "type": "string" + }, + "installationHelp": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "id", + "stackName", + "name", + "checkCommand", + "installationHelp", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + }, + "GitHubRepository": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "url": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "id", + "stackName", + "url", + "displayName", + "description", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + }, "User": { "type": "object", "properties": { @@ -15,8 +117,170 @@ } } }, - "required": ["username", "roles"], + "required": [ + "username", + "roles" + ], + "additionalProperties": false + }, + "Stack": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mainDirectory": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "name", + "displayName", + "description", + "mainDirectory", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + }, + "InstallStatus": { + "type": "string", + "enum": [ + "not-installed", + "installing", + "installed", + "failed" + ] + }, + "BuildStatus": { + "type": "string", + "enum": [ + "not-built", + "building", + "built", + "failed" + ] + }, + "RunStatus": { + "type": "string", + "enum": [ + "stopped", + "starting", + "running", + "stopping", + "error" + ] + }, + "Service": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "workingDirectory": { + "type": "string" + }, + "repositoryId": { + "type": "string" + }, + "autoFetchEnabled": { + "type": "boolean" + }, + "autoFetchIntervalMinutes": { + "type": "number" + }, + "lastFetchedAt": { + "type": "string" + }, + "autoRestartOnFetch": { + "type": "boolean" + }, + "dependencyIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "prerequisiteServiceIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "installCommand": { + "type": "string" + }, + "buildCommand": { + "type": "string" + }, + "runCommand": { + "type": "string" + }, + "installStatus": { + "$ref": "#/definitions/InstallStatus" + }, + "buildStatus": { + "$ref": "#/definitions/BuildStatus" + }, + "runStatus": { + "$ref": "#/definitions/RunStatus" + }, + "lastInstalledAt": { + "type": "string" + }, + "lastBuiltAt": { + "type": "string" + }, + "lastStartedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "id", + "stackName", + "displayName", + "description", + "workingDirectory", + "autoFetchEnabled", + "autoFetchIntervalMinutes", + "autoRestartOnFetch", + "dependencyIds", + "prerequisiteServiceIds", + "runCommand", + "installStatus", + "buildStatus", + "runStatus", + "createdAt", + "updatedAt" + ], "additionalProperties": false } } -} +} \ No newline at end of file diff --git a/common/schemas/github-repositories-api.json b/common/schemas/github-repositories-api.json new file mode 100644 index 0000000..923b1d4 --- /dev/null +++ b/common/schemas/github-repositories-api.json @@ -0,0 +1,959 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "GitHubRepoWritableFields": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "url": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "id", + "stackName", + "url", + "displayName", + "description" + ], + "additionalProperties": false + }, + "PostGitHubRepoEndpoint": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/GitHubRepository" + }, + "body": { + "$ref": "#/definitions/WithOptionalId%3CGitHubRepoWritableFields%2C%22id%22%3E" + } + }, + "required": [ + "result", + "body" + ], + "additionalProperties": false + }, + "GitHubRepository": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "url": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "id", + "stackName", + "url", + "displayName", + "description", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + }, + "WithOptionalId": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "url": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "description", + "displayName", + "stackName", + "url" + ] + }, + "PatchGitHubRepoEndpoint": { + "$ref": "#/definitions/PatchEndpoint%3CGitHubRepoWritableFields%2C%22id%22%3E" + }, + "PatchEndpoint": { + "type": "object", + "properties": { + "body": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "url": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "additionalProperties": false + }, + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object" + } + }, + "required": [ + "body", + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for updating entities" + }, + "ValidateRepoEndpoint": { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object", + "properties": { + "accessible": { + "type": "boolean" + }, + "message": { + "type": "string" + } + }, + "required": [ + "accessible" + ], + "additionalProperties": false + } + }, + "required": [ + "url", + "result" + ], + "additionalProperties": false + }, + "GitHubRepositoriesApi": { + "type": "object", + "properties": { + "GET": { + "type": "object", + "properties": { + "/github-repositories": { + "$ref": "#/definitions/GetCollectionEndpoint%3CGitHubRepository%3E" + }, + "/github-repositories/:id": { + "$ref": "#/definitions/GetEntityEndpoint%3CGitHubRepository%2C%22id%22%3E" + } + }, + "required": [ + "/github-repositories", + "/github-repositories/:id" + ], + "additionalProperties": false + }, + "POST": { + "type": "object", + "properties": { + "/github-repositories": { + "$ref": "#/definitions/PostGitHubRepoEndpoint" + }, + "/github-repositories/:id/validate": { + "$ref": "#/definitions/ValidateRepoEndpoint" + } + }, + "required": [ + "/github-repositories", + "/github-repositories/:id/validate" + ], + "additionalProperties": false + }, + "PATCH": { + "type": "object", + "properties": { + "/github-repositories/:id": { + "$ref": "#/definitions/PatchGitHubRepoEndpoint" + } + }, + "required": [ + "/github-repositories/:id" + ], + "additionalProperties": false + }, + "PUT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "DELETE": { + "type": "object", + "properties": { + "/github-repositories/:id": { + "$ref": "#/definitions/DeleteEndpoint%3CGitHubRepository%2C%22id%22%3E" + } + }, + "required": [ + "/github-repositories/:id" + ], + "additionalProperties": false + }, + "HEAD": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "CONNECT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "TRACE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "OPTIONS": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + } + }, + "required": [ + "GET", + "POST", + "PATCH", + "DELETE" + ], + "additionalProperties": false + }, + "GetCollectionEndpoint": { + "type": "object", + "properties": { + "query": { + "type": "object", + "properties": { + "findOptions": { + "$ref": "#/definitions/FindOptions%3CGitHubRepository%2C(%22id%22%7C%22stackName%22%7C%22url%22%7C%22displayName%22%7C%22description%22%7C%22createdAt%22%7C%22updatedAt%22)%5B%5D%3E" + } + }, + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/GetCollectionResult%3CGitHubRepository%3E" + } + }, + "required": [ + "query", + "result" + ], + "additionalProperties": false, + "description": "Rest endpoint model for getting / querying collections" + }, + "FindOptions": { + "type": "object", + "properties": { + "top": { + "type": "number", + "description": "Limits the hits" + }, + "skip": { + "type": "number", + "description": "Skips the first N hit" + }, + "order": { + "type": "object", + "properties": { + "id": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "stackName": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "url": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "displayName": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "description": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "createdAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "updatedAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + } + }, + "additionalProperties": false, + "description": "Sets up an order by a field and a direction" + }, + "select": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "id", + "stackName", + "url", + "displayName", + "description", + "createdAt", + "updatedAt" + ] + }, + "description": "The result set will be limited to these fields" + }, + "filter": { + "$ref": "#/definitions/FilterType%3CGitHubRepository%3E", + "description": "The fields should match this filter" + } + }, + "additionalProperties": false, + "description": "Type for default filtering model" + }, + "FilterType": { + "type": "object", + "additionalProperties": false, + "properties": { + "$and": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CGitHubRepository%3E" + } + }, + "$not": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CGitHubRepository%3E" + } + }, + "$nor": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CGitHubRepository%3E" + } + }, + "$or": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CGitHubRepository%3E" + } + }, + "id": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "stackName": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "url": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "displayName": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "description": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "createdAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "updatedAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "GetCollectionResult": { + "type": "object", + "properties": { + "count": { + "type": "number", + "description": "The Total count of entities" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/GitHubRepository" + }, + "description": "List of the selected entities" + } + }, + "required": [ + "count", + "entries" + ], + "additionalProperties": false, + "description": "Response Model for GetCollection" + }, + "GetEntityEndpoint": { + "type": "object", + "properties": { + "query": { + "type": "object", + "properties": { + "select": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "id", + "stackName", + "url", + "displayName", + "description", + "createdAt", + "updatedAt" + ] + }, + "description": "The list of fields to select" + } + }, + "additionalProperties": false + }, + "url": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The entity's unique identifier" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/GitHubRepository" + } + }, + "required": [ + "query", + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for getting a single entity" + }, + "DeleteEndpoint": { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object" + } + }, + "required": [ + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for deleting entities" + } + } +} \ No newline at end of file diff --git a/common/schemas/identity-api.json b/common/schemas/identity-api.json new file mode 100644 index 0000000..b6c365f --- /dev/null +++ b/common/schemas/identity-api.json @@ -0,0 +1,299 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "IsAuthenticatedAction": { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "isAuthenticated": { + "type": "boolean" + } + }, + "required": [ + "isAuthenticated" + ], + "additionalProperties": false + } + }, + "required": [ + "result" + ], + "additionalProperties": false + }, + "GetCurrentUserAction": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/User" + } + }, + "required": [ + "result" + ], + "additionalProperties": false + }, + "User": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "username", + "roles" + ], + "additionalProperties": false + }, + "LoginAction": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/User" + }, + "body": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ], + "additionalProperties": false + } + }, + "required": [ + "result", + "body" + ], + "additionalProperties": false + }, + "LogoutAction": { + "type": "object", + "properties": { + "result": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + }, + "PasswordResetAction": { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ], + "additionalProperties": false + }, + "body": { + "type": "object", + "properties": { + "currentPassword": { + "type": "string" + }, + "newPassword": { + "type": "string" + } + }, + "required": [ + "currentPassword", + "newPassword" + ], + "additionalProperties": false + } + }, + "required": [ + "result", + "body" + ], + "additionalProperties": false + }, + "IdentityApi": { + "type": "object", + "properties": { + "GET": { + "type": "object", + "properties": { + "/isAuthenticated": { + "$ref": "#/definitions/IsAuthenticatedAction" + }, + "/currentUser": { + "$ref": "#/definitions/GetCurrentUserAction" + } + }, + "required": [ + "/isAuthenticated", + "/currentUser" + ], + "additionalProperties": false + }, + "POST": { + "type": "object", + "properties": { + "/login": { + "$ref": "#/definitions/LoginAction" + }, + "/logout": { + "$ref": "#/definitions/LogoutAction" + }, + "/password-reset": { + "$ref": "#/definitions/PasswordResetAction" + } + }, + "required": [ + "/login", + "/logout", + "/password-reset" + ], + "additionalProperties": false + }, + "PATCH": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "PUT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "DELETE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "HEAD": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "CONNECT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "TRACE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "OPTIONS": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + } + }, + "required": [ + "GET", + "POST" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/common/schemas/install-api.json b/common/schemas/install-api.json new file mode 100644 index 0000000..ebc3586 --- /dev/null +++ b/common/schemas/install-api.json @@ -0,0 +1,227 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ServiceStatus": { + "type": "string", + "enum": [ + "needsInstall", + "installed" + ] + }, + "ServiceStatusResponse": { + "type": "object", + "properties": { + "state": { + "$ref": "#/definitions/ServiceStatus" + } + }, + "required": [ + "state" + ], + "additionalProperties": false + }, + "GetServiceStatusAction": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/ServiceStatusResponse" + } + }, + "required": [ + "result" + ], + "additionalProperties": false + }, + "InstallAction": { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ], + "additionalProperties": false + }, + "body": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "password" + ], + "additionalProperties": false + } + }, + "required": [ + "result", + "body" + ], + "additionalProperties": false + }, + "InstallApi": { + "type": "object", + "properties": { + "GET": { + "type": "object", + "properties": { + "/serviceStatus": { + "$ref": "#/definitions/GetServiceStatusAction" + } + }, + "required": [ + "/serviceStatus" + ], + "additionalProperties": false + }, + "POST": { + "type": "object", + "properties": { + "/install": { + "$ref": "#/definitions/InstallAction" + } + }, + "required": [ + "/install" + ], + "additionalProperties": false + }, + "PATCH": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "PUT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "DELETE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "HEAD": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "CONNECT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "TRACE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "OPTIONS": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + } + }, + "required": [ + "GET", + "POST" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/common/schemas/services-api.json b/common/schemas/services-api.json new file mode 100644 index 0000000..fa3db06 --- /dev/null +++ b/common/schemas/services-api.json @@ -0,0 +1,2133 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ServiceWritableFields": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "workingDirectory": { + "type": "string" + }, + "repositoryId": { + "type": "string" + }, + "autoFetchEnabled": { + "type": "boolean" + }, + "autoFetchIntervalMinutes": { + "type": "number" + }, + "autoRestartOnFetch": { + "type": "boolean" + }, + "dependencyIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "prerequisiteServiceIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "installCommand": { + "type": "string" + }, + "buildCommand": { + "type": "string" + }, + "runCommand": { + "type": "string" + } + }, + "required": [ + "id", + "stackName", + "displayName", + "description", + "workingDirectory", + "autoFetchEnabled", + "autoFetchIntervalMinutes", + "autoRestartOnFetch", + "dependencyIds", + "prerequisiteServiceIds", + "runCommand" + ], + "additionalProperties": false + }, + "PostServiceEndpoint": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/Service" + }, + "body": { + "$ref": "#/definitions/WithOptionalId%3CServiceWritableFields%2C%22id%22%3E" + } + }, + "required": [ + "result", + "body" + ], + "additionalProperties": false + }, + "Service": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "workingDirectory": { + "type": "string" + }, + "repositoryId": { + "type": "string" + }, + "autoFetchEnabled": { + "type": "boolean" + }, + "autoFetchIntervalMinutes": { + "type": "number" + }, + "lastFetchedAt": { + "type": "string" + }, + "autoRestartOnFetch": { + "type": "boolean" + }, + "dependencyIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "prerequisiteServiceIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "installCommand": { + "type": "string" + }, + "buildCommand": { + "type": "string" + }, + "runCommand": { + "type": "string" + }, + "installStatus": { + "$ref": "#/definitions/InstallStatus" + }, + "buildStatus": { + "$ref": "#/definitions/BuildStatus" + }, + "runStatus": { + "$ref": "#/definitions/RunStatus" + }, + "lastInstalledAt": { + "type": "string" + }, + "lastBuiltAt": { + "type": "string" + }, + "lastStartedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "id", + "stackName", + "displayName", + "description", + "workingDirectory", + "autoFetchEnabled", + "autoFetchIntervalMinutes", + "autoRestartOnFetch", + "dependencyIds", + "prerequisiteServiceIds", + "runCommand", + "installStatus", + "buildStatus", + "runStatus", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + }, + "InstallStatus": { + "type": "string", + "enum": [ + "not-installed", + "installing", + "installed", + "failed" + ] + }, + "BuildStatus": { + "type": "string", + "enum": [ + "not-built", + "building", + "built", + "failed" + ] + }, + "RunStatus": { + "type": "string", + "enum": [ + "stopped", + "starting", + "running", + "stopping", + "error" + ] + }, + "WithOptionalId": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "workingDirectory": { + "type": "string" + }, + "repositoryId": { + "type": "string" + }, + "autoFetchEnabled": { + "type": "boolean" + }, + "autoFetchIntervalMinutes": { + "type": "number" + }, + "autoRestartOnFetch": { + "type": "boolean" + }, + "dependencyIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "prerequisiteServiceIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "installCommand": { + "type": "string" + }, + "buildCommand": { + "type": "string" + }, + "runCommand": { + "type": "string" + } + }, + "required": [ + "autoFetchEnabled", + "autoFetchIntervalMinutes", + "autoRestartOnFetch", + "dependencyIds", + "description", + "displayName", + "prerequisiteServiceIds", + "runCommand", + "stackName", + "workingDirectory" + ] + }, + "PatchServiceEndpoint": { + "$ref": "#/definitions/PatchEndpoint%3CServiceWritableFields%2C%22id%22%3E" + }, + "PatchEndpoint": { + "type": "object", + "properties": { + "body": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stackName": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "workingDirectory": { + "type": "string" + }, + "repositoryId": { + "type": "string" + }, + "autoFetchEnabled": { + "type": "boolean" + }, + "autoFetchIntervalMinutes": { + "type": "number" + }, + "autoRestartOnFetch": { + "type": "boolean" + }, + "dependencyIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "prerequisiteServiceIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "installCommand": { + "type": "string" + }, + "buildCommand": { + "type": "string" + }, + "runCommand": { + "type": "string" + } + }, + "additionalProperties": false + }, + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object" + } + }, + "required": [ + "body", + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for updating entities" + }, + "ServiceActionEndpoint": { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "serviceId": { + "type": "string" + } + }, + "required": [ + "success", + "serviceId" + ], + "additionalProperties": false + } + }, + "required": [ + "url", + "result" + ], + "additionalProperties": false + }, + "ServiceLogsEndpoint": { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "query": { + "type": "object", + "properties": { + "lines": { + "type": "number" + } + }, + "additionalProperties": false + }, + "result": { + "type": "object", + "properties": { + "lines": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "lines" + ], + "additionalProperties": false + } + }, + "required": [ + "url", + "query", + "result" + ], + "additionalProperties": false + }, + "ServicesApi": { + "type": "object", + "properties": { + "GET": { + "type": "object", + "properties": { + "/services": { + "$ref": "#/definitions/GetCollectionEndpoint%3CService%3E" + }, + "/services/:id": { + "$ref": "#/definitions/GetEntityEndpoint%3CService%2C%22id%22%3E" + }, + "/services/:id/logs": { + "$ref": "#/definitions/ServiceLogsEndpoint" + } + }, + "required": [ + "/services", + "/services/:id", + "/services/:id/logs" + ], + "additionalProperties": false + }, + "POST": { + "type": "object", + "properties": { + "/services": { + "$ref": "#/definitions/PostServiceEndpoint" + }, + "/services/:id/start": { + "$ref": "#/definitions/ServiceActionEndpoint" + }, + "/services/:id/stop": { + "$ref": "#/definitions/ServiceActionEndpoint" + }, + "/services/:id/restart": { + "$ref": "#/definitions/ServiceActionEndpoint" + }, + "/services/:id/install": { + "$ref": "#/definitions/ServiceActionEndpoint" + }, + "/services/:id/build": { + "$ref": "#/definitions/ServiceActionEndpoint" + }, + "/services/:id/pull": { + "$ref": "#/definitions/ServiceActionEndpoint" + } + }, + "required": [ + "/services", + "/services/:id/start", + "/services/:id/stop", + "/services/:id/restart", + "/services/:id/install", + "/services/:id/build", + "/services/:id/pull" + ], + "additionalProperties": false + }, + "PATCH": { + "type": "object", + "properties": { + "/services/:id": { + "$ref": "#/definitions/PatchServiceEndpoint" + } + }, + "required": [ + "/services/:id" + ], + "additionalProperties": false + }, + "PUT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "DELETE": { + "type": "object", + "properties": { + "/services/:id": { + "$ref": "#/definitions/DeleteEndpoint%3CService%2C%22id%22%3E" + } + }, + "required": [ + "/services/:id" + ], + "additionalProperties": false + }, + "HEAD": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "CONNECT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "TRACE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "OPTIONS": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + } + }, + "required": [ + "GET", + "POST", + "PATCH", + "DELETE" + ], + "additionalProperties": false + }, + "GetCollectionEndpoint": { + "type": "object", + "properties": { + "query": { + "type": "object", + "properties": { + "findOptions": { + "$ref": "#/definitions/FindOptions%3CService%2C(%22id%22%7C%22stackName%22%7C%22displayName%22%7C%22description%22%7C%22workingDirectory%22%7C%22repositoryId%22%7C%22autoFetchEnabled%22%7C%22autoFetchIntervalMinutes%22%7C%22lastFetchedAt%22%7C%22autoRestartOnFetch%22%7C%22dependencyIds%22%7C%22prerequisiteServiceIds%22%7C%22installCommand%22%7C%22buildCommand%22%7C%22runCommand%22%7C%22installStatus%22%7C%22buildStatus%22%7C%22runStatus%22%7C%22lastInstalledAt%22%7C%22lastBuiltAt%22%7C%22lastStartedAt%22%7C%22createdAt%22%7C%22updatedAt%22)%5B%5D%3E" + } + }, + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/GetCollectionResult%3CService%3E" + } + }, + "required": [ + "query", + "result" + ], + "additionalProperties": false, + "description": "Rest endpoint model for getting / querying collections" + }, + "FindOptions": { + "type": "object", + "properties": { + "top": { + "type": "number", + "description": "Limits the hits" + }, + "skip": { + "type": "number", + "description": "Skips the first N hit" + }, + "order": { + "type": "object", + "properties": { + "id": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "stackName": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "displayName": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "description": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "workingDirectory": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "repositoryId": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "autoFetchEnabled": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "autoFetchIntervalMinutes": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "lastFetchedAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "autoRestartOnFetch": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "dependencyIds": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "prerequisiteServiceIds": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "installCommand": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "buildCommand": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "runCommand": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "installStatus": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "buildStatus": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "runStatus": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "lastInstalledAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "lastBuiltAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "lastStartedAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "createdAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "updatedAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + } + }, + "additionalProperties": false, + "description": "Sets up an order by a field and a direction" + }, + "select": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "id", + "stackName", + "displayName", + "description", + "workingDirectory", + "repositoryId", + "autoFetchEnabled", + "autoFetchIntervalMinutes", + "lastFetchedAt", + "autoRestartOnFetch", + "dependencyIds", + "prerequisiteServiceIds", + "installCommand", + "buildCommand", + "runCommand", + "installStatus", + "buildStatus", + "runStatus", + "lastInstalledAt", + "lastBuiltAt", + "lastStartedAt", + "createdAt", + "updatedAt" + ] + }, + "description": "The result set will be limited to these fields" + }, + "filter": { + "$ref": "#/definitions/FilterType%3CService%3E", + "description": "The fields should match this filter" + } + }, + "additionalProperties": false, + "description": "Type for default filtering model" + }, + "FilterType": { + "type": "object", + "additionalProperties": false, + "properties": { + "$and": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CService%3E" + } + }, + "$not": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CService%3E" + } + }, + "$nor": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CService%3E" + } + }, + "$or": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CService%3E" + } + }, + "id": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "stackName": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "displayName": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "description": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "workingDirectory": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "repositoryId": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + }, + "$nin": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + } + }, + "additionalProperties": false + } + ] + }, + "autoFetchEnabled": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "boolean" + }, + "$ne": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "boolean" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "boolean" + } + } + }, + "additionalProperties": false + } + ] + }, + "autoFetchIntervalMinutes": { + "anyOf": [ + { + "type": "object", + "properties": { + "$gt": { + "type": "number" + }, + "$gte": { + "type": "number" + }, + "$lt": { + "type": "number" + }, + "$lte": { + "type": "number" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "number" + }, + "$ne": { + "type": "number" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "number" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "number" + } + } + }, + "additionalProperties": false + } + ] + }, + "lastFetchedAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + }, + "$nin": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + } + }, + "additionalProperties": false + } + ] + }, + "autoRestartOnFetch": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "boolean" + }, + "$ne": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "boolean" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "boolean" + } + } + }, + "additionalProperties": false + } + ] + }, + "dependencyIds": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "array", + "items": { + "type": "string" + } + }, + "$ne": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "$nin": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "prerequisiteServiceIds": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "array", + "items": { + "type": "string" + } + }, + "$ne": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "$nin": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "installCommand": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + }, + "$nin": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + } + }, + "additionalProperties": false + } + ] + }, + "buildCommand": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + }, + "$nin": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + } + }, + "additionalProperties": false + } + ] + }, + "runCommand": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "installStatus": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "$ref": "#/definitions/InstallStatus" + }, + "$endsWith": { + "$ref": "#/definitions/InstallStatus" + }, + "$like": { + "$ref": "#/definitions/InstallStatus" + }, + "$regex": { + "$ref": "#/definitions/InstallStatus" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "$ref": "#/definitions/InstallStatus" + }, + "$ne": { + "$ref": "#/definitions/InstallStatus" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "$ref": "#/definitions/InstallStatus" + } + }, + "$nin": { + "type": "array", + "items": { + "$ref": "#/definitions/InstallStatus" + } + } + }, + "additionalProperties": false + } + ] + }, + "buildStatus": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "$ref": "#/definitions/BuildStatus" + }, + "$endsWith": { + "$ref": "#/definitions/BuildStatus" + }, + "$like": { + "$ref": "#/definitions/BuildStatus" + }, + "$regex": { + "$ref": "#/definitions/BuildStatus" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "$ref": "#/definitions/BuildStatus" + }, + "$ne": { + "$ref": "#/definitions/BuildStatus" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "$ref": "#/definitions/BuildStatus" + } + }, + "$nin": { + "type": "array", + "items": { + "$ref": "#/definitions/BuildStatus" + } + } + }, + "additionalProperties": false + } + ] + }, + "runStatus": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "$ref": "#/definitions/RunStatus" + }, + "$endsWith": { + "$ref": "#/definitions/RunStatus" + }, + "$like": { + "$ref": "#/definitions/RunStatus" + }, + "$regex": { + "$ref": "#/definitions/RunStatus" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "$ref": "#/definitions/RunStatus" + }, + "$ne": { + "$ref": "#/definitions/RunStatus" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "$ref": "#/definitions/RunStatus" + } + }, + "$nin": { + "type": "array", + "items": { + "$ref": "#/definitions/RunStatus" + } + } + }, + "additionalProperties": false + } + ] + }, + "lastInstalledAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + }, + "$nin": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + } + }, + "additionalProperties": false + } + ] + }, + "lastBuiltAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + }, + "$nin": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + } + }, + "additionalProperties": false + } + ] + }, + "lastStartedAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + }, + "$nin": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + } + }, + "additionalProperties": false + } + ] + }, + "createdAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "updatedAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "GetCollectionResult": { + "type": "object", + "properties": { + "count": { + "type": "number", + "description": "The Total count of entities" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/Service" + }, + "description": "List of the selected entities" + } + }, + "required": [ + "count", + "entries" + ], + "additionalProperties": false, + "description": "Response Model for GetCollection" + }, + "GetEntityEndpoint": { + "type": "object", + "properties": { + "query": { + "type": "object", + "properties": { + "select": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "id", + "stackName", + "displayName", + "description", + "workingDirectory", + "repositoryId", + "autoFetchEnabled", + "autoFetchIntervalMinutes", + "lastFetchedAt", + "autoRestartOnFetch", + "dependencyIds", + "prerequisiteServiceIds", + "installCommand", + "buildCommand", + "runCommand", + "installStatus", + "buildStatus", + "runStatus", + "lastInstalledAt", + "lastBuiltAt", + "lastStartedAt", + "createdAt", + "updatedAt" + ] + }, + "description": "The list of fields to select" + } + }, + "additionalProperties": false + }, + "url": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The entity's unique identifier" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/Service" + } + }, + "required": [ + "query", + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for getting a single entity" + }, + "DeleteEndpoint": { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object" + } + }, + "required": [ + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for deleting entities" + } + } +} \ No newline at end of file diff --git a/common/schemas/stacks-api.json b/common/schemas/stacks-api.json new file mode 100644 index 0000000..158f4f6 --- /dev/null +++ b/common/schemas/stacks-api.json @@ -0,0 +1,956 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "StackWritableFields": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mainDirectory": { + "type": "string" + } + }, + "required": [ + "name", + "displayName", + "description", + "mainDirectory" + ], + "additionalProperties": false + }, + "PostStackEndpoint": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/Stack" + }, + "body": { + "$ref": "#/definitions/WithOptionalId%3CStack%2C%22name%22%3E" + } + }, + "required": [ + "result", + "body" + ], + "additionalProperties": false + }, + "Stack": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mainDirectory": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "name", + "displayName", + "description", + "mainDirectory", + "createdAt", + "updatedAt" + ], + "additionalProperties": false + }, + "WithOptionalId": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mainDirectory": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "required": [ + "createdAt", + "description", + "displayName", + "mainDirectory", + "updatedAt" + ] + }, + "PatchStackEndpoint": { + "$ref": "#/definitions/PatchEndpoint%3CStackWritableFields%2C%22name%22%3E" + }, + "PatchEndpoint": { + "type": "object", + "properties": { + "body": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mainDirectory": { + "type": "string" + } + }, + "additionalProperties": false + }, + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object" + } + }, + "required": [ + "body", + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for updating entities" + }, + "ExportStackEndpoint": { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object", + "properties": { + "stack": { + "$ref": "#/definitions/Stack" + }, + "services": { + "type": "array", + "items": {} + }, + "repositories": { + "type": "array", + "items": {} + }, + "dependencies": { + "type": "array", + "items": {} + } + }, + "required": [ + "stack", + "services", + "repositories", + "dependencies" + ], + "additionalProperties": false + } + }, + "required": [ + "url", + "result" + ], + "additionalProperties": false + }, + "ImportStackEndpoint": { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + }, + "required": [ + "success" + ], + "additionalProperties": false + }, + "body": { + "type": "object", + "properties": { + "stack": { + "$ref": "#/definitions/Stack" + }, + "services": { + "type": "array", + "items": {} + }, + "repositories": { + "type": "array", + "items": {} + }, + "dependencies": { + "type": "array", + "items": {} + } + }, + "required": [ + "stack", + "services", + "repositories", + "dependencies" + ], + "additionalProperties": false + } + }, + "required": [ + "result", + "body" + ], + "additionalProperties": false + }, + "StacksApi": { + "type": "object", + "properties": { + "GET": { + "type": "object", + "properties": { + "/stacks": { + "$ref": "#/definitions/GetCollectionEndpoint%3CStack%3E" + }, + "/stacks/:id": { + "$ref": "#/definitions/GetEntityEndpoint%3CStack%2C%22name%22%3E" + }, + "/stacks/:id/export": { + "$ref": "#/definitions/ExportStackEndpoint" + } + }, + "required": [ + "/stacks", + "/stacks/:id", + "/stacks/:id/export" + ], + "additionalProperties": false + }, + "POST": { + "type": "object", + "properties": { + "/stacks": { + "$ref": "#/definitions/PostStackEndpoint" + }, + "/stacks/import": { + "$ref": "#/definitions/ImportStackEndpoint" + } + }, + "required": [ + "/stacks", + "/stacks/import" + ], + "additionalProperties": false + }, + "PATCH": { + "type": "object", + "properties": { + "/stacks/:id": { + "$ref": "#/definitions/PatchStackEndpoint" + } + }, + "required": [ + "/stacks/:id" + ], + "additionalProperties": false + }, + "PUT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "DELETE": { + "type": "object", + "properties": { + "/stacks/:id": { + "$ref": "#/definitions/DeleteEndpoint%3CStack%2C%22name%22%3E" + } + }, + "required": [ + "/stacks/:id" + ], + "additionalProperties": false + }, + "HEAD": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "CONNECT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "TRACE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "OPTIONS": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + } + }, + "required": [ + "GET", + "POST", + "PATCH", + "DELETE" + ], + "additionalProperties": false + }, + "GetCollectionEndpoint": { + "type": "object", + "properties": { + "query": { + "type": "object", + "properties": { + "findOptions": { + "$ref": "#/definitions/FindOptions%3CStack%2C(%22name%22%7C%22displayName%22%7C%22description%22%7C%22mainDirectory%22%7C%22createdAt%22%7C%22updatedAt%22)%5B%5D%3E" + } + }, + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/GetCollectionResult%3CStack%3E" + } + }, + "required": [ + "query", + "result" + ], + "additionalProperties": false, + "description": "Rest endpoint model for getting / querying collections" + }, + "FindOptions": { + "type": "object", + "properties": { + "top": { + "type": "number", + "description": "Limits the hits" + }, + "skip": { + "type": "number", + "description": "Skips the first N hit" + }, + "order": { + "type": "object", + "properties": { + "name": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "displayName": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "description": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "mainDirectory": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "createdAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "updatedAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + } + }, + "additionalProperties": false, + "description": "Sets up an order by a field and a direction" + }, + "select": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "displayName", + "description", + "mainDirectory", + "createdAt", + "updatedAt" + ] + }, + "description": "The result set will be limited to these fields" + }, + "filter": { + "$ref": "#/definitions/FilterType%3CStack%3E", + "description": "The fields should match this filter" + } + }, + "additionalProperties": false, + "description": "Type for default filtering model" + }, + "FilterType": { + "type": "object", + "additionalProperties": false, + "properties": { + "$and": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CStack%3E" + } + }, + "$not": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CStack%3E" + } + }, + "$nor": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CStack%3E" + } + }, + "$or": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CStack%3E" + } + }, + "name": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "displayName": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "description": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "mainDirectory": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "createdAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "updatedAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "GetCollectionResult": { + "type": "object", + "properties": { + "count": { + "type": "number", + "description": "The Total count of entities" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/Stack" + }, + "description": "List of the selected entities" + } + }, + "required": [ + "count", + "entries" + ], + "additionalProperties": false, + "description": "Response Model for GetCollection" + }, + "GetEntityEndpoint": { + "type": "object", + "properties": { + "query": { + "type": "object", + "properties": { + "select": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "displayName", + "description", + "mainDirectory", + "createdAt", + "updatedAt" + ] + }, + "description": "The list of fields to select" + } + }, + "additionalProperties": false + }, + "url": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The entity's unique identifier" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/Stack" + } + }, + "required": [ + "query", + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for getting a single entity" + }, + "DeleteEndpoint": { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object" + } + }, + "required": [ + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for deleting entities" + } + } +} \ No newline at end of file diff --git a/common/schemas/tokens-api.json b/common/schemas/tokens-api.json new file mode 100644 index 0000000..bf7b1db --- /dev/null +++ b/common/schemas/tokens-api.json @@ -0,0 +1,651 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "PublicApiToken": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "name": { + "type": "string" + }, + "lastUsedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "id", + "username", + "name", + "createdAt" + ], + "additionalProperties": false + }, + "CreateTokenEndpoint": { + "type": "object", + "properties": { + "result": { + "type": "object", + "properties": { + "token": { + "$ref": "#/definitions/PublicApiToken" + }, + "plainTextToken": { + "type": "string" + } + }, + "required": [ + "token", + "plainTextToken" + ], + "additionalProperties": false + }, + "body": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "required": [ + "result", + "body" + ], + "additionalProperties": false + }, + "TokensApi": { + "type": "object", + "properties": { + "GET": { + "type": "object", + "properties": { + "/tokens": { + "$ref": "#/definitions/GetCollectionEndpoint%3CPublicApiToken%3E" + } + }, + "required": [ + "/tokens" + ], + "additionalProperties": false + }, + "POST": { + "type": "object", + "properties": { + "/tokens": { + "$ref": "#/definitions/CreateTokenEndpoint" + } + }, + "required": [ + "/tokens" + ], + "additionalProperties": false + }, + "PATCH": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "PUT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "DELETE": { + "type": "object", + "properties": { + "/tokens/:id": { + "$ref": "#/definitions/DeleteEndpoint%3CApiToken%2C%22id%22%3E" + } + }, + "required": [ + "/tokens/:id" + ], + "additionalProperties": false + }, + "HEAD": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "CONNECT": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "TRACE": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + }, + "OPTIONS": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "result": {}, + "url": {}, + "query": {}, + "body": {}, + "headers": {} + }, + "required": [ + "result" + ], + "additionalProperties": false + } + } + }, + "required": [ + "GET", + "POST", + "DELETE" + ], + "additionalProperties": false + }, + "GetCollectionEndpoint": { + "type": "object", + "properties": { + "query": { + "type": "object", + "properties": { + "findOptions": { + "$ref": "#/definitions/FindOptions%3CPublicApiToken%2C(%22id%22%7C%22username%22%7C%22name%22%7C%22lastUsedAt%22%7C%22createdAt%22)%5B%5D%3E" + } + }, + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/GetCollectionResult%3CPublicApiToken%3E" + } + }, + "required": [ + "query", + "result" + ], + "additionalProperties": false, + "description": "Rest endpoint model for getting / querying collections" + }, + "FindOptions": { + "type": "object", + "properties": { + "top": { + "type": "number", + "description": "Limits the hits" + }, + "skip": { + "type": "number", + "description": "Skips the first N hit" + }, + "order": { + "type": "object", + "properties": { + "id": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "username": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "name": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "lastUsedAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + }, + "createdAt": { + "type": "string", + "enum": [ + "ASC", + "DESC" + ] + } + }, + "additionalProperties": false, + "description": "Sets up an order by a field and a direction" + }, + "select": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "id", + "username", + "name", + "lastUsedAt", + "createdAt" + ] + }, + "description": "The result set will be limited to these fields" + }, + "filter": { + "$ref": "#/definitions/FilterType%3CPublicApiToken%3E", + "description": "The fields should match this filter" + } + }, + "additionalProperties": false, + "description": "Type for default filtering model" + }, + "FilterType": { + "type": "object", + "additionalProperties": false, + "properties": { + "$and": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CPublicApiToken%3E" + } + }, + "$not": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CPublicApiToken%3E" + } + }, + "$nor": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CPublicApiToken%3E" + } + }, + "$or": { + "type": "array", + "items": { + "$ref": "#/definitions/FilterType%3CPublicApiToken%3E" + } + }, + "id": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "username": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "name": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, + "lastUsedAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + }, + "$nin": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "not": {} + } + ] + } + } + }, + "additionalProperties": false + } + ] + }, + "createdAt": { + "anyOf": [ + { + "type": "object", + "properties": { + "$startsWith": { + "type": "string" + }, + "$endsWith": { + "type": "string" + }, + "$like": { + "type": "string" + }, + "$regex": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$eq": { + "type": "string" + }, + "$ne": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "$in": { + "type": "array", + "items": { + "type": "string" + } + }, + "$nin": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "GetCollectionResult": { + "type": "object", + "properties": { + "count": { + "type": "number", + "description": "The Total count of entities" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/PublicApiToken" + }, + "description": "List of the selected entities" + } + }, + "required": [ + "count", + "entries" + ], + "additionalProperties": false, + "description": "Response Model for GetCollection" + }, + "DeleteEndpoint": { + "type": "object", + "properties": { + "url": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "result": { + "type": "object" + } + }, + "required": [ + "url", + "result" + ], + "additionalProperties": false, + "description": "Endpoint model for deleting entities" + } + } +} \ No newline at end of file diff --git a/common/src/apis/dependencies.ts b/common/src/apis/dependencies.ts new file mode 100644 index 0000000..6c89a28 --- /dev/null +++ b/common/src/apis/dependencies.ts @@ -0,0 +1,40 @@ +import type { WithOptionalId } from '@furystack/core' +import type { + DeleteEndpoint, + GetCollectionEndpoint, + GetEntityEndpoint, + PatchEndpoint, + RestApi, +} from '@furystack/rest' +import type { Dependency } from '../models/dependency.js' + +export type DependencyWritableFields = Omit + +export type PostDependencyEndpoint = { + result: Dependency + body: WithOptionalId +} + +export type PatchDependencyEndpoint = PatchEndpoint + +export type CheckDependencyEndpoint = { + url: { id: string } + result: { satisfied: boolean; output: string } +} + +export interface DependenciesApi extends RestApi { + GET: { + '/dependencies': GetCollectionEndpoint + '/dependencies/:id': GetEntityEndpoint + } + POST: { + '/dependencies': PostDependencyEndpoint + '/dependencies/:id/check': CheckDependencyEndpoint + } + PATCH: { + '/dependencies/:id': PatchDependencyEndpoint + } + DELETE: { + '/dependencies/:id': DeleteEndpoint + } +} diff --git a/common/src/apis/github-repositories.ts b/common/src/apis/github-repositories.ts new file mode 100644 index 0000000..eafd5c4 --- /dev/null +++ b/common/src/apis/github-repositories.ts @@ -0,0 +1,40 @@ +import type { WithOptionalId } from '@furystack/core' +import type { + DeleteEndpoint, + GetCollectionEndpoint, + GetEntityEndpoint, + PatchEndpoint, + RestApi, +} from '@furystack/rest' +import type { GitHubRepository } from '../models/github-repository.js' + +export type GitHubRepoWritableFields = Omit + +export type PostGitHubRepoEndpoint = { + result: GitHubRepository + body: WithOptionalId +} + +export type PatchGitHubRepoEndpoint = PatchEndpoint + +export type ValidateRepoEndpoint = { + url: { id: string } + result: { accessible: boolean; message?: string } +} + +export interface GitHubRepositoriesApi extends RestApi { + GET: { + '/github-repositories': GetCollectionEndpoint + '/github-repositories/:id': GetEntityEndpoint + } + POST: { + '/github-repositories': PostGitHubRepoEndpoint + '/github-repositories/:id/validate': ValidateRepoEndpoint + } + PATCH: { + '/github-repositories/:id': PatchGitHubRepoEndpoint + } + DELETE: { + '/github-repositories/:id': DeleteEndpoint + } +} diff --git a/common/src/apis/identity.ts b/common/src/apis/identity.ts new file mode 100644 index 0000000..8e1039a --- /dev/null +++ b/common/src/apis/identity.ts @@ -0,0 +1,27 @@ +import type { RestApi } from '@furystack/rest' +import type { User } from '../models/user.js' + +export type IsAuthenticatedAction = { result: { isAuthenticated: boolean } } +export type GetCurrentUserAction = { result: User } +export type LoginAction = { result: User; body: { username: string; password: string } } +export type LogoutAction = { result: unknown } + +export type PasswordResetAction = { + result: { success: boolean } + body: { + currentPassword: string + newPassword: string + } +} + +export interface IdentityApi extends RestApi { + GET: { + '/isAuthenticated': IsAuthenticatedAction + '/currentUser': GetCurrentUserAction + } + POST: { + '/login': LoginAction + '/logout': LogoutAction + '/password-reset': PasswordResetAction + } +} diff --git a/common/src/apis/index.ts b/common/src/apis/index.ts new file mode 100644 index 0000000..b94ca86 --- /dev/null +++ b/common/src/apis/index.ts @@ -0,0 +1,7 @@ +export * from './install.js' +export * from './identity.js' +export * from './stacks.js' +export * from './services.js' +export * from './github-repositories.js' +export * from './dependencies.js' +export * from './tokens.js' diff --git a/common/src/apis/install.ts b/common/src/apis/install.ts new file mode 100644 index 0000000..58bcf48 --- /dev/null +++ b/common/src/apis/install.ts @@ -0,0 +1,20 @@ +import type { RestApi } from '@furystack/rest' + +export type ServiceStatus = 'needsInstall' | 'installed' + +export type ServiceStatusResponse = { + state: ServiceStatus +} + +export type GetServiceStatusAction = { result: ServiceStatusResponse } + +export type InstallAction = { result: { success: boolean }; body: { username: string; password: string } } + +export interface InstallApi extends RestApi { + GET: { + '/serviceStatus': GetServiceStatusAction + } + POST: { + '/install': InstallAction + } +} diff --git a/common/src/apis/services.ts b/common/src/apis/services.ts new file mode 100644 index 0000000..6e4fa88 --- /dev/null +++ b/common/src/apis/services.ts @@ -0,0 +1,52 @@ +import type { WithOptionalId } from '@furystack/core' +import type { + DeleteEndpoint, + GetCollectionEndpoint, + GetEntityEndpoint, + PatchEndpoint, + RestApi, +} from '@furystack/rest' +import type { Service } from '../models/service.js' + +export type ServiceWritableFields = Omit< + Service, + 'installStatus' | 'buildStatus' | 'runStatus' | 'lastInstalledAt' | 'lastBuiltAt' | 'lastStartedAt' | 'lastFetchedAt' | 'createdAt' | 'updatedAt' +> + +export type PostServiceEndpoint = { + result: Service + body: WithOptionalId +} + +export type PatchServiceEndpoint = PatchEndpoint + +export type ServiceActionEndpoint = { url: { id: string }; result: { success: boolean; serviceId: string } } + +export type ServiceLogsEndpoint = { + url: { id: string } + query: { lines?: number } + result: { lines: string[] } +} + +export interface ServicesApi extends RestApi { + GET: { + '/services': GetCollectionEndpoint + '/services/:id': GetEntityEndpoint + '/services/:id/logs': ServiceLogsEndpoint + } + POST: { + '/services': PostServiceEndpoint + '/services/:id/start': ServiceActionEndpoint + '/services/:id/stop': ServiceActionEndpoint + '/services/:id/restart': ServiceActionEndpoint + '/services/:id/install': ServiceActionEndpoint + '/services/:id/build': ServiceActionEndpoint + '/services/:id/pull': ServiceActionEndpoint + } + PATCH: { + '/services/:id': PatchServiceEndpoint + } + DELETE: { + '/services/:id': DeleteEndpoint + } +} diff --git a/common/src/apis/stacks.ts b/common/src/apis/stacks.ts new file mode 100644 index 0000000..5003c30 --- /dev/null +++ b/common/src/apis/stacks.ts @@ -0,0 +1,41 @@ +import type { WithOptionalId } from '@furystack/core' +import type { + DeleteEndpoint, + GetCollectionEndpoint, + GetEntityEndpoint, + PatchEndpoint, + RestApi, +} from '@furystack/rest' +import type { Stack } from '../models/stack.js' + +export type StackWritableFields = Omit +export type PostStackEndpoint = { result: Stack; body: WithOptionalId } +export type PatchStackEndpoint = PatchEndpoint + +export type ExportStackEndpoint = { + url: { id: string } + result: { stack: Stack; services: unknown[]; repositories: unknown[]; dependencies: unknown[] } +} + +export type ImportStackEndpoint = { + result: { success: boolean } + body: { stack: Stack; services: unknown[]; repositories: unknown[]; dependencies: unknown[] } +} + +export interface StacksApi extends RestApi { + GET: { + '/stacks': GetCollectionEndpoint + '/stacks/:id': GetEntityEndpoint + '/stacks/:id/export': ExportStackEndpoint + } + POST: { + '/stacks': PostStackEndpoint + '/stacks/import': ImportStackEndpoint + } + PATCH: { + '/stacks/:id': PatchStackEndpoint + } + DELETE: { + '/stacks/:id': DeleteEndpoint + } +} diff --git a/common/src/apis/tokens.ts b/common/src/apis/tokens.ts new file mode 100644 index 0000000..5dbaf85 --- /dev/null +++ b/common/src/apis/tokens.ts @@ -0,0 +1,21 @@ +import type { DeleteEndpoint, GetCollectionEndpoint, RestApi } from '@furystack/rest' +import type { ApiToken } from '../models/api-token.js' + +export type PublicApiToken = Omit + +export type CreateTokenEndpoint = { + result: { token: PublicApiToken; plainTextToken: string } + body: { name: string } +} + +export interface TokensApi extends RestApi { + GET: { + '/tokens': GetCollectionEndpoint + } + POST: { + '/tokens': CreateTokenEndpoint + } + DELETE: { + '/tokens/:id': DeleteEndpoint + } +} diff --git a/common/src/bin/create-schemas.ts b/common/src/bin/create-schemas.ts index 940c7aa..1d58f62 100644 --- a/common/src/bin/create-schemas.ts +++ b/common/src/bin/create-schemas.ts @@ -8,9 +8,6 @@ export interface SchemaGenerationSetting { type: string } -/** - * Entity schemas, e.g. User, Session, etc... - */ export const entityValues: SchemaGenerationSetting[] = [ { inputFile: './src/models/*.ts', @@ -21,8 +18,38 @@ export const entityValues: SchemaGenerationSetting[] = [ export const apiValues: SchemaGenerationSetting[] = [ { - inputFile: './src/stack-craft-api.ts', - outputFile: './schemas/stack-craft-api.json', + inputFile: './src/apis/identity.ts', + outputFile: './schemas/identity-api.json', + type: '*', + }, + { + inputFile: './src/apis/install.ts', + outputFile: './schemas/install-api.json', + type: '*', + }, + { + inputFile: './src/apis/stacks.ts', + outputFile: './schemas/stacks-api.json', + type: '*', + }, + { + inputFile: './src/apis/services.ts', + outputFile: './schemas/services-api.json', + type: '*', + }, + { + inputFile: './src/apis/github-repositories.ts', + outputFile: './schemas/github-repositories-api.json', + type: '*', + }, + { + inputFile: './src/apis/dependencies.ts', + outputFile: './schemas/dependencies-api.json', + type: '*', + }, + { + inputFile: './src/apis/tokens.ts', + outputFile: './schemas/tokens-api.json', type: '*', }, ] @@ -35,7 +62,6 @@ export const exec = async (): Promise => { path: join(process.cwd(), schemaValue.inputFile), tsconfig: join(process.cwd(), './tsconfig.json'), skipTypeCheck: true, - // expose: 'all', }).createSchema(schemaValue.type) await promises.writeFile(join(process.cwd(), schemaValue.outputFile), JSON.stringify(schema, null, 2)) } catch (error) { diff --git a/common/src/boilerplate-api.ts b/common/src/boilerplate-api.ts deleted file mode 100644 index 39380ad..0000000 --- a/common/src/boilerplate-api.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { RestApi } from '@furystack/rest' -import type { User } from './models/index.js' - -export type TestQueryEndpoint = { query: { param1: string }; result: { param1Value: string } } -export type TestUrlParamsEndpoint = { url: { urlParam: string }; result: { urlParamValue: string } } -export type TestPostBodyEndpoint = { body: { value: string }; result: { bodyValue: string } } - -export interface StackCraftApi extends RestApi { - GET: { - '/isAuthenticated': { result: { isAuthenticated: boolean } } - '/currentUser': { result: User } - '/testQuery': TestQueryEndpoint - '/testUrlParams/:urlParam': TestUrlParamsEndpoint - } - POST: { - '/login': { result: User; body: { username: string; password: string } } - '/logout': { result: unknown } - '/testPostBody': TestPostBodyEndpoint - } -} diff --git a/common/src/index.ts b/common/src/index.ts index 227ee11..336b128 100644 --- a/common/src/index.ts +++ b/common/src/index.ts @@ -1,2 +1,3 @@ export * from './models/index.js' -export * from './stack-craft-api.js' +export * from './apis/index.js' +export * from './websocket/index.js' diff --git a/common/src/models/api-token.ts b/common/src/models/api-token.ts new file mode 100644 index 0000000..514f67e --- /dev/null +++ b/common/src/models/api-token.ts @@ -0,0 +1,8 @@ +export class ApiToken { + id!: string + username!: string + name!: string + tokenHash!: string + lastUsedAt?: string + createdAt!: string +} diff --git a/common/src/models/dependency.ts b/common/src/models/dependency.ts new file mode 100644 index 0000000..b0405fc --- /dev/null +++ b/common/src/models/dependency.ts @@ -0,0 +1,9 @@ +export class Dependency { + id!: string + stackName!: string + name!: string + checkCommand!: string + installationHelp: string = '' + createdAt!: string + updatedAt!: string +} diff --git a/common/src/models/github-repository.ts b/common/src/models/github-repository.ts new file mode 100644 index 0000000..c1ce9af --- /dev/null +++ b/common/src/models/github-repository.ts @@ -0,0 +1,9 @@ +export class GitHubRepository { + id!: string + stackName!: string + url!: string + displayName!: string + description: string = '' + createdAt!: string + updatedAt!: string +} diff --git a/common/src/models/index.ts b/common/src/models/index.ts index 72e50f8..d1d4ab1 100644 --- a/common/src/models/index.ts +++ b/common/src/models/index.ts @@ -1 +1,6 @@ export * from './user.js' +export * from './stack.js' +export * from './github-repository.js' +export * from './dependency.js' +export * from './service.js' +export * from './api-token.js' diff --git a/common/src/models/service.ts b/common/src/models/service.ts new file mode 100644 index 0000000..ebdd2c3 --- /dev/null +++ b/common/src/models/service.ts @@ -0,0 +1,34 @@ +export type InstallStatus = 'not-installed' | 'installing' | 'installed' | 'failed' +export type BuildStatus = 'not-built' | 'building' | 'built' | 'failed' +export type RunStatus = 'stopped' | 'starting' | 'running' | 'stopping' | 'error' + +export class Service { + id!: string + stackName!: string + displayName!: string + description: string = '' + workingDirectory!: string + repositoryId?: string + + autoFetchEnabled: boolean = false + autoFetchIntervalMinutes: number = 60 + lastFetchedAt?: string + autoRestartOnFetch: boolean = false + + dependencyIds: string[] = [] + prerequisiteServiceIds: string[] = [] + + installCommand?: string + buildCommand?: string + runCommand!: string + + installStatus: InstallStatus = 'not-installed' + buildStatus: BuildStatus = 'not-built' + runStatus: RunStatus = 'stopped' + + lastInstalledAt?: string + lastBuiltAt?: string + lastStartedAt?: string + createdAt!: string + updatedAt!: string +} diff --git a/common/src/models/stack.ts b/common/src/models/stack.ts new file mode 100644 index 0000000..f3d2f92 --- /dev/null +++ b/common/src/models/stack.ts @@ -0,0 +1,8 @@ +export class Stack { + name!: string + displayName!: string + description: string = '' + mainDirectory!: string + createdAt!: string + updatedAt!: string +} diff --git a/common/src/stack-craft-api.ts b/common/src/stack-craft-api.ts index 39380ad..fc919c8 100644 --- a/common/src/stack-craft-api.ts +++ b/common/src/stack-craft-api.ts @@ -1,20 +1,7 @@ -import type { RestApi } from '@furystack/rest' -import type { User } from './models/index.js' - -export type TestQueryEndpoint = { query: { param1: string }; result: { param1Value: string } } -export type TestUrlParamsEndpoint = { url: { urlParam: string }; result: { urlParamValue: string } } -export type TestPostBodyEndpoint = { body: { value: string }; result: { bodyValue: string } } - -export interface StackCraftApi extends RestApi { - GET: { - '/isAuthenticated': { result: { isAuthenticated: boolean } } - '/currentUser': { result: User } - '/testQuery': TestQueryEndpoint - '/testUrlParams/:urlParam': TestUrlParamsEndpoint - } - POST: { - '/login': { result: User; body: { username: string; password: string } } - '/logout': { result: unknown } - '/testPostBody': TestPostBodyEndpoint - } -} +export type { IdentityApi } from './apis/identity.js' +export type { InstallApi } from './apis/install.js' +export type { StacksApi } from './apis/stacks.js' +export type { ServicesApi } from './apis/services.js' +export type { GitHubRepositoriesApi } from './apis/github-repositories.js' +export type { DependenciesApi } from './apis/dependencies.js' +export type { TokensApi } from './apis/tokens.js' diff --git a/common/src/websocket/index.ts b/common/src/websocket/index.ts new file mode 100644 index 0000000..72094f6 --- /dev/null +++ b/common/src/websocket/index.ts @@ -0,0 +1,27 @@ +import type { InstallStatus, BuildStatus, RunStatus } from '../models/service.js' + +export type WebsocketMessage = + | { + type: 'service-status-changed' + serviceId: string + installStatus: InstallStatus + buildStatus: BuildStatus + runStatus: RunStatus + } + | { + type: 'service-log' + serviceId: string + stream: 'stdout' | 'stderr' + line: string + } + | { + type: 'git-branches-changed' + serviceId: string + newBranches: string[] + } + | { + type: 'dependency-check-result' + dependencyId: string + satisfied: boolean + output: string + } diff --git a/e2e/page.spec.d.ts b/e2e/page.spec.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/e2e/page.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/e2e/page.spec.js b/e2e/page.spec.js new file mode 100644 index 0000000..ed4c911 --- /dev/null +++ b/e2e/page.spec.js @@ -0,0 +1,29 @@ +import { expect, test } from '@playwright/test'; +test.describe('Example Application', () => { + test('Login and logout roundtrip', async ({ page }) => { + await page.goto('/'); + const loginForm = page.locator('shade-login form'); + await expect(loginForm).toBeVisible(); + const usernameInput = loginForm.locator('input[name="userName"]'); + await expect(usernameInput).toBeVisible(); + const passwordInput = loginForm.locator('input[name="password"]'); + await expect(passwordInput).toBeVisible(); + await usernameInput.type('testuser'); + await passwordInput.type('password'); + const submitButton = page.locator('button', { hasText: 'Login' }); + await expect(submitButton).toBeVisible(); + await expect(submitButton).toBeEnabled(); + await submitButton.click(); + const welcomeTitle = page.locator('hello-world div h2'); + await expect(welcomeTitle).toBeVisible(); + await expect(welcomeTitle).toHaveText('Hello, testuser !'); + const logoutButton = page.locator('shade-app-bar button >> text="Log Out"'); + await expect(logoutButton).toBeVisible(); + await expect(logoutButton).toBeEnabled(); + await expect(logoutButton).toHaveText('Log Out'); + await logoutButton.click(); + const loggedOutLoginForm = page.locator('shade-login form'); + await expect(loggedOutLoginForm).toBeVisible(); + }); +}); +//# sourceMappingURL=page.spec.js.map \ No newline at end of file diff --git a/e2e/page.spec.js.map b/e2e/page.spec.js.map new file mode 100644 index 0000000..941cf67 --- /dev/null +++ b/e2e/page.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"page.spec.js","sourceRoot":"","sources":["page.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAE/C,IAAI,CAAC,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACxC,IAAI,CAAC,4BAA4B,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACpD,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAEpB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;QAClD,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAA;QAErC,MAAM,aAAa,GAAG,SAAS,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAA;QACjE,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC,WAAW,EAAE,CAAA;QAEzC,MAAM,aAAa,GAAG,SAAS,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAA;QACjE,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC,WAAW,EAAE,CAAA;QAEzC,MAAM,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACpC,MAAM,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAEpC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAA;QACjE,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAA;QACxC,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAA;QAExC,MAAM,YAAY,CAAC,KAAK,EAAE,CAAA;QAE1B,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAA;QACvD,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAA;QACxC,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAA;QAE1D,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAA;QAC3E,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAA;QACxC,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAA;QACxC,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;QAChD,MAAM,YAAY,CAAC,KAAK,EAAE,CAAA;QAE1B,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;QAC3D,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,WAAW,EAAE,CAAA;IAChD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 74d4989..e553d15 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "vitest": "^4.0.18" }, "dependencies": { + "@furystack/cache": "^6.0.0", "@furystack/core": "^15.0.36", "@furystack/inject": "^12.0.30", "@furystack/logging": "^8.0.30", diff --git a/frontend/src/components/body.tsx b/frontend/src/components/body.tsx index 74cefe8..fb5a97e 100644 --- a/frontend/src/components/body.tsx +++ b/frontend/src/components/body.tsx @@ -1,6 +1,7 @@ import { createComponent, Router, Shade } from '@furystack/shades' -import { ButtonsDemo, HelloWorld, Init, Login, Offline } from '../pages/index.js' +import { Init, Login, Offline } from '../pages/index.js' import { SessionService } from '../services/session.js' +import { Dashboard } from '../pages/dashboard/index.js' export const Body = Shade<{ style?: Partial }>({ shadowDomName: 'shade-app-body', @@ -15,10 +16,9 @@ export const Body = Shade<{ style?: Partial }>({ return ( }, - { url: '/', routingOptions: { end: false }, component: () => }, + { url: '/', routingOptions: { end: false }, component: () => }, ]} - > + /> ) case 'offline': return diff --git a/frontend/src/components/entity-forms/dependency-form.tsx b/frontend/src/components/entity-forms/dependency-form.tsx new file mode 100644 index 0000000..7718951 --- /dev/null +++ b/frontend/src/components/entity-forms/dependency-form.tsx @@ -0,0 +1,66 @@ +import { createComponent, Shade } from '@furystack/shades' +import { Button, Input } from '@furystack/shades-common-components' +import type { Dependency } from 'common' + +type DependencyFormProps = { + initial?: Partial + stackName: string + onSubmit: (data: Partial) => void | Promise + onCancel: () => void + mode: 'create' | 'edit' +} + +export const DependencyForm = Shade({ + shadowDomName: 'shade-dependency-form', + render: ({ props }) => { + return ( +
{ + ev.preventDefault() + const formData = new FormData(ev.target as HTMLFormElement) + const data = Object.fromEntries(formData.entries()) as Record + await props.onSubmit({ + stackName: props.stackName, + name: data.name, + checkCommand: data.checkCommand, + installationHelp: data.installationHelp, + }) + }} + > +

{props.mode === 'create' ? 'Add Dependency' : 'Edit Dependency'}

+ 'e.g., Node.js, Python, Docker'} + /> + 'Command that returns exit code 0 if installed (e.g., node --version)'} + /> + 'Instructions for installing this dependency'} + /> +
+ + +
+ + ) + }, +}) diff --git a/frontend/src/components/entity-forms/github-repo-form.tsx b/frontend/src/components/entity-forms/github-repo-form.tsx new file mode 100644 index 0000000..6a77731 --- /dev/null +++ b/frontend/src/components/entity-forms/github-repo-form.tsx @@ -0,0 +1,64 @@ +import { createComponent, Shade } from '@furystack/shades' +import { Button, Input } from '@furystack/shades-common-components' +import type { GitHubRepository } from 'common' + +type GitHubRepoFormProps = { + initial?: Partial + stackName: string + onSubmit: (data: Partial) => void | Promise + onCancel: () => void + mode: 'create' | 'edit' +} + +export const GitHubRepoForm = Shade({ + shadowDomName: 'shade-github-repo-form', + render: ({ props }) => { + return ( +
{ + ev.preventDefault() + const formData = new FormData(ev.target as HTMLFormElement) + const data = Object.fromEntries(formData.entries()) as Record + await props.onSubmit({ + stackName: props.stackName, + url: data.url, + displayName: data.displayName, + description: data.description, + }) + }} + > +

{props.mode === 'create' ? 'Add GitHub Repository' : 'Edit Repository'}

+ 'Full GitHub URL, e.g. https://github.com/org/repo'} + /> + + +
+ + +
+ + ) + }, +}) diff --git a/frontend/src/components/entity-forms/service-form.tsx b/frontend/src/components/entity-forms/service-form.tsx new file mode 100644 index 0000000..d9f0415 --- /dev/null +++ b/frontend/src/components/entity-forms/service-form.tsx @@ -0,0 +1,110 @@ +import { createComponent, Shade } from '@furystack/shades' +import { Button, Input } from '@furystack/shades-common-components' +import type { Service } from 'common' + +type ServiceFormProps = { + initial?: Partial + stackName: string + onSubmit: (data: Partial) => void | Promise + onCancel: () => void + mode: 'create' | 'edit' +} + +export const ServiceForm = Shade({ + shadowDomName: 'shade-service-form', + render: ({ props }) => { + return ( +
{ + ev.preventDefault() + const formData = new FormData(ev.target as HTMLFormElement) + const data = Object.fromEntries(formData.entries()) as Record + await props.onSubmit({ + stackName: props.stackName, + displayName: data.displayName, + description: data.description, + workingDirectory: data.workingDirectory, + runCommand: data.runCommand, + installCommand: data.installCommand || undefined, + buildCommand: data.buildCommand || undefined, + autoFetchEnabled: data.autoFetchEnabled === 'on', + autoFetchIntervalMinutes: parseInt(data.autoFetchIntervalMinutes, 10) || 60, + autoRestartOnFetch: data.autoRestartOnFetch === 'on', + }) + }} + > +

{props.mode === 'create' ? 'Create Service' : 'Edit Service'}

+ + + 'Absolute path where commands will be executed'} + /> + 'e.g., npm start, yarn dev, dotnet run'} + /> + 'e.g., npm install, yarn, dotnet restore'} + /> + 'e.g., npm run build, yarn build, dotnet build'} + /> +
+ + + +
+
+ + +
+ + ) + }, +}) diff --git a/frontend/src/components/entity-forms/stack-form.tsx b/frontend/src/components/entity-forms/stack-form.tsx new file mode 100644 index 0000000..d90ebb8 --- /dev/null +++ b/frontend/src/components/entity-forms/stack-form.tsx @@ -0,0 +1,72 @@ +import { createComponent, Shade } from '@furystack/shades' +import { Button, Input } from '@furystack/shades-common-components' +import type { Stack } from 'common' + +type StackFormProps = { + initial?: Partial + onSubmit: (data: Partial) => void | Promise + onCancel: () => void + mode: 'create' | 'edit' +} + +export const StackForm = Shade({ + shadowDomName: 'shade-stack-form', + render: ({ props }) => { + return ( +
{ + ev.preventDefault() + const formData = new FormData(ev.target as HTMLFormElement) + const data = Object.fromEntries(formData.entries()) as Record + await props.onSubmit({ + name: data.name, + displayName: data.displayName, + description: data.description, + mainDirectory: data.mainDirectory, + }) + }} + > +

{props.mode === 'create' ? 'Create Stack' : 'Edit Stack'}

+ 'Unique kebab-case identifier'} + /> + + + 'Absolute path to the root directory for this stack'} + /> +
+ + +
+ + ) + }, +}) diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx index a0faf4b..fca72b3 100644 --- a/frontend/src/components/header.tsx +++ b/frontend/src/components/header.tsx @@ -1,11 +1,9 @@ import { createComponent, RouteLink, Shade } from '@furystack/shades' import { AppBar, Button } from '@furystack/shades-common-components' -import { environmentOptions } from '../environment-options.js' import { SessionService } from '../services/session.js' -import { GithubLogo } from './github-logo/index.js' import { ThemeSwitch } from './theme-switch/index.js' -export interface HeaderProps { +export type HeaderProps = { title: string links: Array<{ name: string; url: string }> } @@ -25,15 +23,14 @@ export const Header = Shade({ '& route-link:hover': { color: '#fff', }, - '& .nav-link': { - padding: '0 8px', - }, '& .spacer': { flex: '1', }, '& .actions': { display: 'flex', placeContent: 'center', + alignItems: 'center', + gap: '8px', marginRight: '24px', }, }, @@ -47,19 +44,9 @@ export const Header = Shade({ {props.title} - {props.links.map((link) => ( - - {link.name || ''} - - ))}
- - - {sessionState === 'authenticated' ? ( + ) : null} + {hasRunning ? ( + + ) : null} + + +
+ ) : null} + + + + + + + + + + + {props.services.length === 0 ? ( + + + + ) : null} + {props.services.map((svc) => ( + + + + + + + ))} + +
+ 0} + onchange={toggleAll} + /> + ServiceStatusActions
+ No services in this stack yet. +
+ toggleSelect(svc.id)} /> + + {svc.displayName} + {svc.description ? ( +
{svc.description}
+ ) : null} +
+ + +
+ {svc.runStatus !== 'running' ? ( + + ) : ( + + )} + +
+
+
+ ) + }, +}) diff --git a/frontend/src/components/stack-selector.tsx b/frontend/src/components/stack-selector.tsx new file mode 100644 index 0000000..faf2744 --- /dev/null +++ b/frontend/src/components/stack-selector.tsx @@ -0,0 +1,39 @@ +import { createComponent, Shade } from '@furystack/shades' +import type { Stack } from 'common' + +type StackSelectorProps = { + stacks: Stack[] + selectedStack: string | null + onSelect: (stackName: string) => void +} + +export const StackSelector = Shade({ + shadowDomName: 'shade-stack-selector', + render: ({ props }) => { + if (props.stacks.length <= 1) return null + + return ( + + ) + }, +}) diff --git a/frontend/src/components/wizard-step.tsx b/frontend/src/components/wizard-step.tsx new file mode 100644 index 0000000..8374363 --- /dev/null +++ b/frontend/src/components/wizard-step.tsx @@ -0,0 +1,48 @@ +import { createComponent, Shade } from '@furystack/shades' +import type { WizardStepProps } from '@furystack/shades-common-components' +import { Button } from '@furystack/shades-common-components' + +export const WizardStep = Shade< + { title: string; onSubmit?: (ev: SubmitEvent) => void | Promise } & WizardStepProps +>({ + shadowDomName: 'shade-wizard-step', + render: ({ props, children }) => { + return ( +
{ + ev.preventDefault() + if (props.onSubmit) { + await props.onSubmit(ev) + } else { + props.onNext?.() + } + }} + > +

{props.title}

+
{children}
+
+ + +
+
+ ) + }, +}) diff --git a/frontend/src/pages/dashboard/index.tsx b/frontend/src/pages/dashboard/index.tsx new file mode 100644 index 0000000..a293518 --- /dev/null +++ b/frontend/src/pages/dashboard/index.tsx @@ -0,0 +1,125 @@ +import { createComponent, Shade } from '@furystack/shades' +import { Button } from '@furystack/shades-common-components' +import { ObservableValue } from '@furystack/utils' +import type { Service, Stack } from 'common' +import { StacksApiClient } from '../../services/api-clients/stacks-api-client.js' +import { ServicesApiClient } from '../../services/api-clients/services-api-client.js' +import { ServiceTable } from '../../components/service-table.js' +import { StackSelector } from '../../components/stack-selector.js' + +export const Dashboard = Shade({ + shadowDomName: 'shade-dashboard', + render: ({ injector, useState, useDisposable }) => { + const [stacks, setStacks] = useState('stacks', []) + const [selectedStackName, setSelectedStackName] = useState('selectedStack', null) + const [services, setServices] = useState('services', []) + const [isLoading, setIsLoading] = useState('isLoading', true) + const refreshTrigger = useDisposable('refreshTrigger', () => new ObservableValue(0)) + + const stacksApi = injector.getInstance(StacksApiClient) + const servicesApi = injector.getInstance(ServicesApiClient) + + const loadData = async () => { + setIsLoading(true) + try { + const { result: stackResult } = await stacksApi.call({ + method: 'GET', + action: '/stacks', + query: { findOptions: {} }, + }) + setStacks(stackResult.entries) + + const activeStack = selectedStackName ?? stackResult.entries[0]?.name ?? null + if (activeStack && activeStack !== selectedStackName) { + setSelectedStackName(activeStack) + } + + if (activeStack) { + const { result: serviceResult } = await servicesApi.call({ + method: 'GET', + action: '/services', + query: { findOptions: { filter: { stackName: { $eq: activeStack } } } }, + }) + setServices(serviceResult.entries) + } else { + setServices([]) + } + } catch { + // Handle error silently + } + setIsLoading(false) + } + + if (isLoading && refreshTrigger.getValue() === 0) { + void loadData() + refreshTrigger.setValue(1) + } + + const refresh = () => { + void loadData() + } + + if (stacks.length === 0 && !isLoading) { + return ( +
+

No stacks yet

+

Create a stack to start managing your services.

+
+ + +
+
+ ) + } + + const currentStack = stacks.find((s) => s.name === selectedStackName) + + return ( +
+
+

{currentStack?.displayName ?? 'Dashboard'}

+ { + setSelectedStackName(name) + void loadData() + }} + /> +
+ +
+ history.pushState(null, '', `/services/${serviceId}/logs`)} + onEdit={(serviceId) => history.pushState(null, '', `/services/${serviceId}`)} + /> +
+ ) + }, +}) diff --git a/frontend/src/pages/import-export/export-stack.tsx b/frontend/src/pages/import-export/export-stack.tsx new file mode 100644 index 0000000..5964896 --- /dev/null +++ b/frontend/src/pages/import-export/export-stack.tsx @@ -0,0 +1,73 @@ +import { createComponent, Shade } from '@furystack/shades' +import { Button, NotyService } from '@furystack/shades-common-components' +import { StacksApiClient } from '../../services/api-clients/stacks-api-client.js' + +type ExportStackProps = { + stackName: string +} + +export const ExportStack = Shade({ + shadowDomName: 'shade-export-stack', + render: ({ props, injector, useState }) => { + const [jsonOutput, setJsonOutput] = useState('json', '') + const [isLoading, setIsLoading] = useState('isLoading', true) + + if (isLoading && !jsonOutput) { + injector + .getInstance(StacksApiClient) + .call({ + method: 'GET', + action: '/stacks/:id/export', + url: { id: props.stackName }, + }) + .then(({ result }) => { + setJsonOutput(JSON.stringify(result, null, 2)) + setIsLoading(false) + }) + .catch(() => setIsLoading(false)) + } + + return ( +
+

Export Stack

+

+ Copy the JSON below and share it with other developers to quickly set up the same stack. +

+