From c0b493046489b2385cb87e2b5069ff4d30d37aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tayfun=20Y=C4=B1lmaz?= Date: Thu, 4 Dec 2025 01:56:52 +0300 Subject: [PATCH 1/2] Replace cursorrules and ignore files, update build and docs Added .cursor/rules/cursorrules.mdc and .cursorignore for improved Cursor AI rules and ignore patterns. Removed legacy .cursorrules. Updated README.md with new architecture, build, and validation instructions. Added build.js for domain packaging. Updated and added supporting scripts and configuration files. Various updates to core schemas, tasks, workflows, and mocks for vNext domain structure and testing. --- .cursor/rules/cursorrules.mdc | 623 +++++++ .cursorignore | 29 + .cursorrules | 101 -- README.md | 276 +-- build.js | 429 +++++ .../account-opening/account-confirmation.json | 6 +- .../account-details-input.json | 6 +- .../account-type-selection.json | 6 +- .../account-opening/create-bank-account.json | 2 +- .../validate-account-policies.json | 2 +- .../oauth/check-device-registration.json | 2 +- core/Tasks/oauth/check-push-response.json | 2 +- core/Tasks/oauth/generate-tokens.json | 2 +- core/Tasks/oauth/register-device.json | 2 +- core/Tasks/oauth/send-otp-notification.json | 2 +- core/Tasks/oauth/send-push-notification.json | 2 +- .../oauth/validate-authorization-code.json | 2 +- core/Tasks/oauth/validate-client.json | 2 +- .../oauth/validate-user-credentials.json | 2 +- core/Tasks/oauth/verify-otp-code.json | 2 +- .../payments/activate-payment-schedule.json | 2 +- .../payments/archive-payment-record.json | 2 +- .../payments/deactivate-payment-schedule.json | 2 +- core/Tasks/payments/get-user-info.json | 2 +- .../payments/increment-retry-counter.json | 2 +- core/Tasks/payments/process-payment.json | 2 +- .../payments/save-payment-configuration.json | 3 +- .../send-payment-notification-sms.json | 2 +- .../send-payment-push-notification.json | 2 +- .../account-opening-workflow.diagram.json | 36 - .../account-opening-workflow.json | 31 +- .../src/AccountCreationFailedRule.csx | 8 +- .../src/InitialTransMapping.csx | 1 + .../src/PoliciesFailedRule.csx | 5 +- ...oauth-authentication-workflow.diagram.json | 52 - .../oauth/.meta/password-subflow.diagram.json | 20 + .../oauth/authorization-code-subflow.json | 3 + .../oauth/oauth-authentication-workflow.json | 11 + core/Workflows/oauth/otp-mfa-subflow.json | 6 + core/Workflows/oauth/password-subflow.json | 3 + .../oauth/push-notification-mfa-subflow.json | 5 + .../payment-notification-subflow.json | 3 + core/Workflows/payments/payment-process.json | 6 + .../payments/scheduled-payments-workflow.json | 6 + index.js | 97 +- mockoon/banking-api-mocks.json | 271 --- mockoon/migration-api.json | 1551 +++++++++++++++++ mockoon/upload-to-mockoon.sh | 69 + package-lock.json | 27 +- package.json | 28 +- ...xt Example Runtime.postman_collection.json | 2 +- setup.js | 243 +++ sync-schema-version.js | 58 + test.js | 2 +- validate.js | 706 +++++++- vnext.config.json | 4 +- 56 files changed, 4078 insertions(+), 695 deletions(-) create mode 100644 .cursor/rules/cursorrules.mdc create mode 100644 .cursorignore delete mode 100644 .cursorrules create mode 100644 build.js delete mode 100644 core/Workflows/account-opening/.meta/account-opening-workflow.diagram.json delete mode 100644 core/Workflows/oauth/.meta/oauth-authentication-workflow.diagram.json create mode 100644 core/Workflows/oauth/.meta/password-subflow.diagram.json delete mode 100644 mockoon/banking-api-mocks.json create mode 100644 mockoon/migration-api.json create mode 100755 mockoon/upload-to-mockoon.sh create mode 100644 setup.js create mode 100644 sync-schema-version.js diff --git a/.cursor/rules/cursorrules.mdc b/.cursor/rules/cursorrules.mdc new file mode 100644 index 0000000..d4b1bba --- /dev/null +++ b/.cursor/rules/cursorrules.mdc @@ -0,0 +1,623 @@ +--- +alwaysApply: true +--- + +# vNext Core Domain - Cursor AI Rules + +You are an expert vNext workflow developer working on a domain-driven workflow automation system. Follow these rules strictly when working on this project. + +## 📚 Knowledge Sources + +### vNext Runtime Documentation +- **Primary Reference**: Use Context7 MCP server with `vnext-runtime` to get up-to-date documentation +- **Component Schemas**: Query vnext-runtime for component structure examples and patterns +- **Workflow Concepts**: Reference vnext-runtime for state lifecycle, transitions, views, and task types +- **Best Practices**: Always verify against official vNext runtime documentation + +### Component Structure +All vNext components follow a standard structure: + +```json +{ + "key": "component-name", // Component unique identifier (kebab-case) + "version": "1.0.0", // Semantic version + "domain": "core", // Domain from vnext.config.json + "flow": "sys-workflows", // Component type (sys-workflows, sys-tasks, sys-schemas, sys-views, sys-functions, sys-extensions) + "flowVersion": "1.0.0", // Component type version + "tags": ["tag1", "tag2"], // Searchable tags + "attributes": { + // Component-specific attributes + // Refer to vnext-runtime for each component type's schema examples + } +} +``` + +### Component Flow Types +- `sys-workflows` - Workflow definitions +- `sys-tasks` - Task definitions +- `sys-schemas` - JSON Schema definitions +- `sys-views` - View component definitions +- `sys-functions` - Function definitions +- `sys-extensions` - Extension definitions + +### Project Configuration +- **Config File**: `vnext.config.json` - Contains domain configuration, paths, exports, and validation rules +- **Domain**: "core" +- **Runtime Version**: Check vnext.config.json for current version +- **Schema Version**: Check vnext.config.json for current schema version +- **Component Paths**: All paths are defined in vnext.config.json under "paths" section + +### Project Structure +- **Documentation**: `README.md` - Contains full project structure, architecture, and available scripts +- **Component Root**: `core/` directory +- **Component Types**: + - `core/Workflows/` - Workflow definitions with .json and src/ folders for .csx mappings + - `core/Tasks/` - Task definitions (.json files) + - `core/Views/` - View component definitions (.json files) + - `core/Schemas/` - JSON Schema definitions (.json files) + - `core/Functions/` - Function definitions (.json files) + - `core/Extensions/` - Extension definitions (.json files) + +### Schema Definitions +- **Schema Location**: `node_modules/@burgan-tech/vnext-schema/` +- **Always validate components** against the official schemas before saving +- **Current Schema Version**: 0.0.25 (check package.json for updates) +- Use `npm run validate` to validate all components after changes + +## 🎭 Mockoon API Mocking + +### Mock API Strategy +- **Never use real APIs** - Always use Mockoon for API mocking during development +- **Mock Location**: `mockoon/` directory +- **Current Mocks**: `mockoon/migration-api.json` +- **Mock Server**: Runs on `localhost:3001` + +### Creating New Mocks +When creating new workflows or processes: + +1. **Always create corresponding mocks** in Mockoon +2. **Create a folder with domain name first** - Before creating routes, create a folder in Mockoon with the domain name (e.g., "contract", "banking", "oauth"). All routes for that domain should be placed inside this folder. This groups routes by domain for better organization. +3. **Use folder structure** to organize APIs by domain (see `mockoon/migration-api.json` for examples) +4. **Follow naming conventions**: + - Endpoint: `api/{domain}/{resource}/{action}` + - Example: `api/banking/user/validate-session` +5. **Include multiple response scenarios**: + - Success response (200-299 status codes) + - Error responses (400-499 for client errors, 500-599 for server errors) + - Use rules to conditionally return different responses +6. **Use Mockoon's Faker.js** for dynamic data generation +7. **Add latency** to simulate real API behavior (500-1000ms) +8. **Document each endpoint** with clear documentation field + +### Mock Structure Example +```json +{ + "uuid": "unique-id", + "type": "http", + "documentation": "Clear description of what this endpoint does", + "method": "post", + "endpoint": "api/{domain}/{resource}/{action}", + "responses": [ + { + "uuid": "response-id", + "body": "{\n \"statusCode\": 200,\n \"data\": {...}\n}", + "statusCode": 200, + "label": "Success Scenario", + "latency": 500, + "rules": [...] + } + ] +} +``` + +## 📄 HTTP Request Files + +### vNext Runtime API Configuration +- **Base URL**: `http://localhost:4201` +- **API Version**: Check vnext.config.json for current runtime version +- **All requests**: Use vNext Runtime API endpoints (not Mockoon for workflow operations) + +### Create Test Files for Each Process +For every workflow or process, create an `.http` file to demonstrate instance progression: + +1. **File Location**: Create in project root or a `http-tests/` directory +2. **Naming Convention**: `{workflow-name}.http` or `{process-name}.http` +3. **Include**: + - All API calls needed to complete the workflow + - Example request bodies + - Expected response formats + - Comments explaining each step + - Authentication headers (if needed) + - Variables for reusable values + +### vNext Runtime API Endpoints + +#### Start Workflow Instance +```http +POST http://localhost:4201/api/v{apiVersion}/{domain}/workflows/{workflow}/instances/start +Content-Type: application/json + +{ + "key": "unique-instance-key", + "tags": ["tag1", "tag2"], + "attributes": { + // Initial workflow data + } +} +``` + +#### Execute Transition +```http +PATCH http://localhost:4201/api/v{apiVersion}/{domain}/workflows/{workflow}/instances/{instance}/transitions/{transitionKey} +Content-Type: application/json + +{ + // Transition data +} +``` + +#### Get Instance State (Long Polling) +```http +GET http://localhost:4201/api/v{apiVersion}/{domain}/workflows/{workflow}/instances/{instance}/functions/state +``` + +#### Get Instance Data +```http +GET http://localhost:4201/api/v{apiVersion}/{domain}/workflows/{workflow}/instances/{instance}/functions/data +``` + +#### Execute Instance Function +```http +GET http://localhost:4201/api/v{apiVersion}/{domain}/workflows/{workflow}/instances/{instance}/functions/{function} +``` + +### HTTP File Template +```http +@baseUrl = http://localhost:4201 +@apiVersion = 1 +@domain = core +@workflow = account-opening-workflow +@instanceKey = test-instance-{{$timestamp}} + +### Step 1: Start Workflow Instance +POST {{baseUrl}}/api/v{{apiVersion}}/{{domain}}/workflows/{{workflow}}/instances/start +Content-Type: application/json + +{ + "key": "{{instanceKey}}", + "tags": ["test", "account-opening"], + "attributes": { + "userId": "user-123", + "sessionId": "session-456" + } +} + +### Step 2: Get Current State +GET {{baseUrl}}/api/v{{apiVersion}}/{{domain}}/workflows/{{workflow}}/instances/{{instanceKey}}/functions/state + +### Step 3: Execute Transition +PATCH {{baseUrl}}/api/v{{apiVersion}}/{{domain}}/workflows/{{workflow}}/instances/{{instanceKey}}/transitions/submit-account-type +Content-Type: application/json + +{ + "accountType": "demand-deposit", + "currency": "TRY" +} + +### Step 4: Get Instance Data +GET {{baseUrl}}/api/v{{apiVersion}}/{{domain}}/workflows/{{workflow}}/instances/{{instanceKey}}/functions/data +``` + +## 🔄 Workflow Core Concepts + +Reference vnext-runtime documentation for detailed understanding of these concepts: + +### State Lifecycle +- **States**: Define the workflow steps and their behavior +- **Initial State**: The starting point of the workflow - **there must be exactly ONE initial state per workflow** +- **Final States**: Terminal states where workflow completes (can have multiple final states) +- **State Types**: Active, Waiting, Completed, Failed +- **State Transitions**: Define how workflow moves between states + +### Transitions +- **Transition Types**: + - User transitions (triggered by user actions) + - Auto transitions (triggered automatically by conditions) + - System transitions (triggered by system events) +- **Transition Guards**: Conditions that must be met for transition +- **Transition Actions**: Operations performed during transition + +### Views +- **Purpose**: Define user interface presentation for workflow states +- **View Binding**: Link views to states for user interaction +- **Schema Integration**: Views reference schemas for data validation +- **Dynamic Rendering**: Views adapt based on workflow state and data + +### Tasks +- **Task Types**: + - **HTTP Task**: Make HTTP requests to external APIs + - **Sub-Process Task**: Call another workflow as subprocess + - **Script Task**: Execute inline scripts or mappings + - **Service Task**: Call internal services or functions +- **Task Execution**: Tasks execute within state lifecycle +- **Task Mappings**: Input/Output mappings transform data + +### Flow Types +- **Flow (Main Workflow)**: Primary workflow definition +- **SubFlow**: Reusable workflow segment called within main flow +- **SubProcess**: Independent workflow instance called as a task + +### Schema Integration +- **Input Schemas**: Validate incoming data for states/transitions +- **Output Schemas**: Define expected output structure +- **Data Validation**: Ensure data integrity throughout workflow +- **Schema References**: Link schemas to views, tasks, and states + +### Functions +- **Purpose**: Reusable business logic callable from workflows +- **Function Types**: Data transformation, validation, calculation +- **Integration**: Functions can be called from states or transitions + +### Extensions +- **Purpose**: Extend vNext runtime capabilities +- **Custom Behavior**: Add domain-specific functionality +- **Plugin System**: Integrate external systems or libraries + +## 🏗️ Component Creation Rules + +### Workflows +- **Location**: `core/Workflows/{domain-name}/` +- **Flow Type**: `sys-workflows` +- **Files**: + - `{workflow-name}.json` - Workflow definition + - `src/{MappingName}.csx` - C# mapping files for data transformations +- **Required Fields**: + - `key` (kebab-case): Unique workflow identifier + - `version` (semver): Component version + - `domain`: Domain from vnext.config.json + - `flow`: "sys-workflows" + - `flowVersion`: Flow type version + - `tags`: Array of searchable tags + - `attributes`: Workflow-specific attributes (states, transitions, initialState, etc.) +- **Attributes Structure**: Reference vnext-runtime for complete workflow schema +- **Mappings**: Must reference files in `src/` folder (base64 encoding is handled automatically by the vNext extension) +- **Always create corresponding mocks** for any HTTP tasks in workflows +- **Always create .http file** to demonstrate workflow execution + +### Tasks +- **Location**: `core/Tasks/{domain-name}/` +- **Flow Type**: `sys-tasks` +- **Required Fields**: + - `key` (kebab-case): Unique task identifier + - `version` (semver): Component version + - `domain`: Domain from vnext.config.json + - `flow`: "sys-tasks" + - `flowVersion`: Flow type version + - `tags`: Array of searchable tags + - `attributes`: Task-specific attributes (type, url, headers, etc.) +- **Task Types**: HTTP, SubProcess, Script, Service +- **HTTP Tasks**: Always have corresponding Mockoon mocks at `localhost:3001` +- **Attributes Structure**: Reference vnext-runtime for task type schemas +- **Schema References**: Link input/output schemas when applicable + +### Views +- **Location**: `core/Views/{domain-name}/` +- **Flow Type**: `sys-views` +- **Required Fields**: + - `key` (kebab-case): Unique view identifier + - `version` (semver): Component version + - `domain`: Domain from vnext.config.json + - `flow`: "sys-views" + - `flowVersion`: Flow type version + - `tags`: Array of searchable tags + - `attributes`: View-specific attributes (schema reference, ui definition, etc.) +- **Schema Binding**: Must reference existing schema for data validation +- **UI Definition**: Define components, layout, and behavior +- **Attributes Structure**: Reference vnext-runtime for view schema examples + +### Schemas +- **Location**: `core/Schemas/{domain-name}/` +- **Flow Type**: `sys-schemas` +- **Required Fields**: + - `key` (kebab-case): Unique schema identifier + - `version` (semver): Component version + - `domain`: Domain from vnext.config.json + - `flow`: "sys-schemas" + - `flowVersion`: Flow type version + - `tags`: Array of searchable tags + - `attributes`: JSON Schema definition (Draft 7 or compatible) +- **Attributes Must Include**: `$schema`, `type`, `properties` +- **Validation**: Run `npm run validate` after creating schemas +- **Reusability**: Design schemas for reuse across views and tasks + +### Functions +- **Location**: `core/Functions/{domain-name}/` +- **Flow Type**: `sys-functions` +- **Required Fields**: + - `key` (kebab-case): Unique function identifier + - `version` (semver): Component version + - `domain`: Domain from vnext.config.json + - `flow`: "sys-functions" + - `flowVersion`: Flow type version + - `tags`: Array of searchable tags + - `attributes`: Function-specific attributes (parameters, return type, etc.) +- **Implementation**: Code in `src/` folder if applicable +- **Attributes Structure**: Reference vnext-runtime for function schema + +### Extensions +- **Location**: `core/Extensions/{domain-name}/` +- **Flow Type**: `sys-extensions` +- **Required Fields**: + - `key` (kebab-case): Unique extension identifier + - `version` (semver): Component version + - `domain`: Domain from vnext.config.json + - `flow`: "sys-extensions" + - `flowVersion`: Flow type version + - `tags`: Array of searchable tags + - `attributes`: Extension-specific attributes +- **Purpose**: Extend vNext runtime capabilities +- **Attributes Structure**: Reference vnext-runtime for extension schema examples + +## 🔄 Workflow Development Process + +When creating a new workflow or process: + +1. **Research and plan**: + - Query vnext-runtime via Context7 for workflow patterns and examples + - Understand state lifecycle and transition types + - Identify all states, transitions, and their relationships + - List all external API calls needed (these will use Mockoon) + - Define data flow and schema requirements + - Determine if using flow, subflow, or subprocess + +2. **Create component structure**: + - Use proper flow types for each component + - Include all required fields: key, version, domain, flow, flowVersion, tags, attributes + - Follow kebab-case for keys + - Use semantic versioning + +3. **Create Mockoon mocks** (for external APIs): + - Add all external API endpoints to `mockoon/banking-api-mocks.json` + - Use folder structure to organize by domain + - Create success and error response scenarios + - Add realistic latency (500-1000ms) + - Use Faker.js for dynamic data + - Document each endpoint clearly + +4. **Create schemas**: + - Define in `core/Schemas/{domain}/` with flow type `sys-schemas` + - Follow JSON Schema Draft 7 or compatible + - Include complete component structure from vnext-runtime + - Design for reusability across views and tasks + - Validate using `npm run validate` + +5. **Create tasks**: + - Define in `core/Tasks/{domain}/` with flow type `sys-tasks` + - Reference vnext-runtime for task type schemas + - HTTP tasks point to Mockoon endpoints (`localhost:3001`) + - SubProcess tasks reference other workflows + - Link input/output schemas where applicable + - Create mappings in `src/` folder + +6. **Create views** (if needed): + - Define in `core/Views/{domain}/` with flow type `sys-views` + - Reference schemas for data binding + - Define UI components and layout + - Link to workflow states for rendering + +7. **Create workflow**: + - Define in `core/Workflows/{domain}/` with flow type `sys-workflows` + - Reference vnext-runtime for workflow attributes schema + - Define states with proper lifecycle + - Configure transitions (user, auto, system) + - Link views to states + - Create C# mapping files in `src/` folder (base64 encoding is handled automatically by extension) + - Reference tasks, schemas, and functions + +8. **Create .http test file**: + - Use vNext Runtime API (`localhost:4201`) + - Include all workflow endpoints: + - Start instance + - Execute transitions + - Query state (long polling) + - Get instance data + - Call functions + - Add variables for workflow, domain, version + - Document each step with comments + - Show complete workflow progression + +9. **Validate and test**: + - Run `npm run validate` to check all components + - Fix any schema validation errors + - Start vNext Runtime server on `localhost:4201` + - Start Mockoon server on `localhost:3001` + - Execute .http file step by step + - Verify workflow progresses correctly + - Test error scenarios + +10. **Document and commit**: + - Ensure all components have proper tags + - Verify naming conventions (kebab-case) + - Check that all references are valid + - Update vnext.config.json exports if needed + - Commit with descriptive message + +## 📋 Code Standards + +### JSON Files +- **Indentation**: 2 spaces +- **No trailing commas** +- **Double quotes** for strings +- **Valid JSON** - always validate before saving + +### C# Mapping Files (.csx) +- **Namespace**: Use appropriate BBT.Workflow namespaces +- **Error Handling**: Always include try-catch blocks +- **Comments**: Use XML documentation comments +- **Async/Await**: Use async patterns for I/O operations +- **Null Safety**: Always check for null values +- **⚠️ IMPORTANT: DO NOT manually convert .csx files to base64** - The vNext extension automatically handles base64 encoding when saving workflow JSON files. Just create/edit the .csx files directly in the `src/` folder. + +### Naming Conventions +- **Files**: kebab-case (e.g., `user-session.json`) +- **Keys**: kebab-case (e.g., `validate-user-credentials`) +- **Classes**: PascalCase (e.g., `ValidateUserCredentialsMapping`) +- **Variables**: camelCase (e.g., `userId`, `requestData`) + +## 🧪 Validation and Testing + +### Before Committing +1. Run `npm run validate` to validate all components +2. Check that all referenced schemas exist +3. Verify Mockoon mocks are complete +4. Test workflows using .http files +5. Ensure no schema validation errors + +### Build Commands +- `npm run build` - Build runtime package +- `npm run build:reference` - Build reference package (exports only) +- `npm run build:runtime` - Explicitly build runtime + +## 🚨 Common Mistakes to Avoid + +### Component Structure +1. **DO NOT** forget required fields: `key`, `version`, `domain`, `flow`, `flowVersion`, `tags`, `attributes` +2. **DO NOT** use wrong flow type - each component type has specific flow value +3. **DO NOT** create components without consulting vnext-runtime documentation first +4. **DO NOT** use inline attributes - always follow vnext-runtime schema structure + +### API & Testing +5. **DO NOT** use vNext Runtime API (`localhost:4201`) for external service calls +6. **DO NOT** use Mockoon API (`localhost:3001`) for workflow operations +7. **DO NOT** hardcode production API URLs - always use Mockoon for external APIs +8. **DO NOT** create workflows without corresponding .http test files +9. **DO NOT** create HTTP tasks without Mockoon mocks + +### Code & Validation +10. **DO NOT** create components without validating against schemas +11. **DO NOT** use inline code in JSON - always use src/ files for C# code +12. **DO NOT** manually encode C# (.csx) files to base64 - the vNext extension handles this automatically +13. **DO NOT** forget to update vnext.config.json exports when needed +14. **DO NOT** skip `npm run validate` before committing + +### Workflow Design +15. **DO NOT** confuse flow, subflow, and subprocess concepts +16. **DO NOT** create states without understanding state lifecycle +17. **DO NOT** forget to define transition types correctly +18. **DO NOT** create views without linking to schemas +19. **DO NOT** create a single auto transition (triggerType: 1) in a state - auto transitions work in pairs with mutually exclusive rules. If you have only one auto transition, its rule MUST always return true (unconditional). For proper flow control, always define complementary auto transitions (e.g., "has-more" and "no-more") with opposite conditions. +20. **DO NOT** create multiple initial states in a workflow - each workflow must have exactly ONE initial state (defined by startTransition.target) + +### Mockoon Mocking +21. **DO NOT** create routes without first creating a domain folder - always create a folder with the domain name in Mockoon and place all related routes inside that folder for proper organization + +## 🔍 Reference Resolution + +The project uses strict reference resolution: +- **Enabled**: true +- **Validate on Build**: true +- **Strict Mode**: true +- **Schema Validation**: true + +Always ensure: +- All references use correct format: `{domain}/{component-type}/{key}/{version}` +- All referenced components exist +- Versions match exactly + +## 💡 Best Practices + +1. **Domain Organization**: Group related components by business domain +2. **Reusability**: Create reusable schemas and tasks +3. **Documentation**: Document all components with clear descriptions +4. **Versioning**: Follow semantic versioning for components +5. **Testing**: Always create .http files for testing workflows +6. **Mocking**: Keep mocks realistic and comprehensive +7. **Validation**: Validate early and often + +## 🛠️ Development Workflow + +1. **Consult vnext-runtime documentation**: + - Use Context7 MCP server to query `vnext-runtime` for component schemas + - Understand state lifecycle, transition types, and task patterns + - Review flow types and component structure requirements + +2. **Review project configuration**: + - Check `vnext.config.json` for domain, versions, and paths + - Verify runtime version and schema version compatibility + - Understand reference resolution settings + +3. **Plan workflow architecture**: + - Define states and their lifecycle + - Identify transition types and guards + - Map out views, schemas, and tasks needed + - Determine flow vs subflow vs subprocess usage + +4. **Create Mockoon mocks** (for external APIs): + - Add API endpoints to `mockoon/banking-api-mocks.json` + - Use folder structure to organize by domain + - Create success and error response scenarios + - Test mocks with Mockoon server on `localhost:3001` + +5. **Create schemas**: + - Define JSON schemas in `core/Schemas/{domain}/` + - Follow component structure with flow type `sys-schemas` + - Include all required fields (key, version, domain, flow, flowVersion, tags, attributes) + - Validate using `npm run validate` + +6. **Create supporting components**: + - **Tasks**: Define in `core/Tasks/{domain}/` with flow type `sys-tasks` + - **Views**: Create in `core/Views/{domain}/` with flow type `sys-views` + - **Functions**: Add to `core/Functions/` with flow type `sys-functions` + - **Extensions**: Implement in `core/Extensions/{domain}/` with flow type `sys-extensions` + +7. **Create workflow**: + - Define in `core/Workflows/{domain}/` with flow type `sys-workflows` + - Reference vnext-runtime for workflow attribute schema + - Create C# mapping files in `src/` folder (base64 encoding is handled automatically by extension) + +8. **Create .http test file**: + - Use vNext Runtime API endpoints (`localhost:4201`) + - Include instance start, transitions, and state queries + - Document complete workflow progression + - Add variables for reusable values + +9. **Validate and test**: + - Run `npm run validate` to check all components + - Start vNext Runtime server on `localhost:4201` + - Start Mockoon server on `localhost:3001` (for external API mocks) + - Execute .http file to test workflow + +10. **Review and commit**: + - Verify all components follow naming conventions + - Check that all references are valid + - Ensure .http file demonstrates complete workflow + - Commit with descriptive message + +## 📚 Additional Resources + +### Documentation +- **vNext Runtime**: Query Context7 MCP server with `vnext-runtime` for latest docs +- **Project README**: `README.md` - Full project structure and scripts +- **Configuration**: `vnext.config.json` - Domain settings and paths + +### Schemas & Validation +- **Component Schemas**: `node_modules/@burgan-tech/vnext-schema/` +- **Validation Tool**: Run `npm run validate` after any changes + +### Testing & Mocking +- **Mock APIs**: `mockoon/banking-api-mocks.json` - External API mocks (port 3001) +- **HTTP Tests**: Create `.http` files for workflow testing (Runtime API port 4201) +- **Example Workflows**: `core/Workflows/` - Reference implementations + +### Component Examples +- **Workflows**: `core/Workflows/` - State machines and processes +- **Tasks**: `core/Tasks/` - HTTP, subprocess, and script tasks +- **Views**: `core/Views/` - UI component definitions +- **Schemas**: `core/Schemas/` - JSON Schema definitions +- **Functions**: `core/Functions/` - Reusable business logic +- **Extensions**: `core/Extensions/` - Runtime extensions + +--- + +**Remember**: Quality over speed. Always validate, document, and test your work. + diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..d22fca0 --- /dev/null +++ b/.cursorignore @@ -0,0 +1,29 @@ +# vNext Runtime - Cursor Ignore Rules + +# Docker and deployment files - we are focusing on development so ignore these +#vnext/ + +# Build and deployment files +dist/ +build/ + +# CI/CD pipeline files +.github/ + +# Binary and log files +*.log +*.tmp +*.cache + +# IDE files (VS Code workspace settings except cursor settings) +.vscode/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Temporary files +*.bak +*.orig diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index eae3c2d..0000000 --- a/.cursorrules +++ /dev/null @@ -1,101 +0,0 @@ -# core Domain Project Rules for Cursor AI - -## Project Structure Rules -- Every folder represents a workflow (Schema, View, Task, Function, etc.) -- Files inside folders are instances of that workflow - -## Schema Instance Pattern Rules - -### Platform Managed Properties (InstanceBase) -- Schema instances MUST include: `key`, `version`, `domain`, `flow`, `flowVersion` -- Schema instances MUST NOT include: `labels` (labels belong to business logic) -- Optional fields: `id`, `eTag` (added by platform in production) - -### Business Process Properties -- State, Transition, Workflow definitions inside attributes MUST include `labels` -- View instances MAY include `labels` in attributes -- Use label.1.0.0.json#/attributes reference for i18n support -- Payload example {"label": "Example", "language": "en-US"} - -## Reference Pattern Rules - -### Foreign Key Pattern -- References use: `domain` + `workflow` + (`id` OR `key`) + optional `version` -- NO `type` property in references - `workflow` field serves as type -- Examples: - - Schema → workflow: "Schema" - - View → workflow: "View" - - Task → workflow: "Task" - -### Reference Format -- Always use local file references: `reference.json#/attributes` -- Never use: `https://schemas.vnext.com/...` -- Use `#/attributes` to reference the schema inside attributes - -## JSON Schema Standards - -### Enum Pattern -Use oneOf + const + description instead of enum arrays: -```json -"oneOf": [ - {"const": "value1", "description": "Description 1"}, - {"const": "value2", "description": "Description 2"} -] -``` - -### Required Properties -- Always define required fields explicitly -- State schema requires: `key`, `labels`, `transitions`, `views`, etc. -- Transition schema requires: `key`, `labels`, `versionStrategy`, `onExecutionTask`, `type` - -## Workflow Lifecycle Pattern - -### Standard 3-State Pattern -All lifecycle workflows must have: -- `draft` (type: "start") - Initial state -- `active` (type: "normal") - Active state -- `passive` (type: "finish") - Final state - -### Standard Transitions -- `create-*` → Entry to draft -- `activate` → draft to active -- `delete` → Delete in draft -- `deactivate` → active to passive -- `update` → Update in active -- `reactivate` → passive to active -- `archive` → Archive in passive - -## Task Array Pattern -For onEntries, onExits: -```json -{ - "order": 1, - "task": { "$ref": "reference.1.0.0.json#/attributes" }, - "mapping": { "$ref": "mapping.1.0.0.json#/attributes" } -} -``` - -## Common Mistakes to Avoid - -### ❌ DON'T -- Add labels to schema instances -- Add type property to references -- Use https://schemas.vnext.com URLs -- Create files without version numbers -- Add non-existent properties (permissions, conditions, metadata to State) -- Use simple object definitions instead of schema references - -### ✅ DO -- Keep schema instances platform-managed only -- Use workflow field as type indicator in references -- Use local file references with #/attributes -- Follow semantic versioning for all files -- Reference code.json for mapping properties -- Read original files before making changes - -## Development Guidelines -- Schema-first approach -- Semantic versioning for backward compatibility -- JSON Schema Draft 2020-12 compliance -- Cross-domain ready architecture -- Instance wrapper pattern for schema definitions \ No newline at end of file diff --git a/README.md b/README.md index 4961e9b..e7c861c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ A structured template package for vNext workflow components with domain-based architecture. This package provides a foundation for building scalable workflow systems with schemas, tasks, views, functions, and extensions. -[![npm version](https://badge.fury.io/js/%40burgan-tech%2Fvnext-template.svg)](https://badge.fury.io/js/%40burgan-tech%2Fvnext-template) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ## 🚀 Features @@ -41,175 +40,184 @@ vnext-template/ └── package.json # Package metadata ``` -## 📚 Usage - -### Basic Usage +## 🏛️ Architecture Principles -```javascript -const vnextTemplate = require('@my-organization/core'); +### Component Types -// Get domain configuration -const config = vnextTemplate.getDomainConfig(); -console.log('Domain config:', config); +1. **Schemas**: JSON Schema definitions for data validation +2. **Workflows**: Business process definitions and state machines +3. **Tasks**: Individual task definitions and configurations +4. **Views**: User interface and presentation components +5. **Functions**: Reusable business logic functions +6. **Extensions**: Plugin and extension definitions -// Get all available component types -const availableTypes = vnextTemplate.getAvailableTypes(); -console.log('Available types:', availableTypes); -// Output: ['schemas', 'workflows', 'tasks', 'views', 'functions', 'extensions'] +## 🛠️ Development -// Get domain name -const domainName = vnextTemplate.getDomainName(); -console.log('Domain name:', domainName); +### Running Tests +```bash +npm test ``` -### Component Access - -```javascript -// Load specific component types -const schemas = vnextTemplate.getSchemas(); -const workflows = vnextTemplate.getWorkflows(); -const tasks = vnextTemplate.getTasks(); -const views = vnextTemplate.getViews(); -const functions = vnextTemplate.getFunctions(); -const extensions = vnextTemplate.getExtensions(); - -// Example: Working with schemas -Object.keys(schemas).forEach(schemaName => { - console.log(`Schema: ${schemaName}`, schemas[schemaName]); -}); - -// Example: Working with workflows -Object.keys(workflows).forEach(workflowName => { - console.log(`Workflow: ${workflowName}`, workflows[workflowName]); -}); +## ⚙️ Configuration + +The `vnext.config.json` file allows you to customize paths and exports: + +```json +{ + "domain": "my-domain", + "paths": { + "componentsRoot": "my-domain", + "schemas": "Schemas", + "workflows": "Workflows", + "tasks": "Tasks", + "views": "Views", + "functions": "Functions", + "extensions": "Extensions" + }, + "exports": { + "schemas": ["schema1.json", "schema2.json"], + "workflows": ["workflow1.json"], + "tasks": [], + "views": [], + "functions": [], + "extensions": [] + } +} ``` -### Integration Example +### Path Configuration -```javascript -const vnext = require('my-organization/core'); +You can customize component directory names: -class WorkflowManager { - constructor() { - this.config = vnext.getDomainConfig(); - this.schemas = vnext.getSchemas(); - this.workflows = vnext.getWorkflows(); - } - - validateWorkflow(workflowData) { - // Use loaded schemas for validation - // Implementation depends on your validation library - } - - executeWorkflow(workflowName) { - const workflow = this.workflows[workflowName]; - if (!workflow) { - throw new Error(`Workflow ${workflowName} not found`); - } - // Execute workflow logic +```json +{ + "paths": { + "componentsRoot": "src", + "workflows": "Flows", + "schemas": "Models" } } - -const manager = new WorkflowManager(); ``` -## 🔧 API Reference - -### `getDomainConfig()` -Returns the domain configuration from `vnext.config.json`. +## ✅ Validation -**Returns:** `Object | null` - The configuration object or null if not found +Validate your project structure and schemas: -### `getSchemas()` -Loads all JSON schema files from the Schemas directory. - -**Returns:** `Object` - Key-value pairs of schema names and definitions - -### `getWorkflows()` -Loads all workflow definitions from the Workflows directory. - -**Returns:** `Object` - Key-value pairs of workflow names and definitions - -### `getTasks()` -Loads all task definitions from the Tasks directory. +```bash +npm run validate +``` -**Returns:** `Object` - Key-value pairs of task names and definitions +This will check: +- Package.json structure and content +- Main entry point functionality +- vnext.config.json validation +- Domain directory structure +- JSON file syntax validation +- Schema validation using @burgan-tech/vnext-schema +- Module functionality +- Semantic versioning compliance -### `getViews()` -Loads all view definitions from the Views directory. +### Validation Output -**Returns:** `Object` - Key-value pairs of view names and definitions +The validation provides detailed output with: +- ✅ Passed validations +- ❌ Failed validations with file paths and line numbers +- 📊 Summary statistics +- 📋 Failed files summary for easy navigation -### `getFunctions()` -Loads all function definitions from the Functions directory. +## 🏗️ Build -**Returns:** `Object` - Key-value pairs of function names and definitions +Build your domain package for deployment or cross-domain usage: -### `getExtensions()` -Loads all extension definitions from the Extensions directory. +```bash +# Runtime build (default) - Complete domain structure +npm run build -**Returns:** `Object` - Key-value pairs of extension names and definitions +# Reference build - Only exported components +npm run build:reference -### `getAvailableTypes()` -Returns an array of available component types. +# Runtime build explicitly +npm run build:runtime +``` -**Returns:** `Array` - Available component types +### Build Options -### `getDomainName()` -Returns the name of the domain directory. +```bash +npm run build -- [options] -**Returns:** `string | null` - Domain directory name or null if not found +Options: + -o, --output Output directory (default: dist) + -t, --type Build type: reference or runtime (default: runtime) + --skip-validation Skip validation during build + -h, --help Show help message +``` -## 🏛️ Architecture Principles +### Build Types -### Domain-Driven Design -- Each domain is represented as a separate directory -- Components are organized by type within domains -- Clear separation between different workflow concerns +| Type | Description | Use Case | +|------|-------------|----------| +| `runtime` | Complete domain structure with all files | Engine deployment | +| `reference` | Only exported components from vnext.config.json | Cross-domain usage | -### Component Types +### Examples -1. **Schemas**: JSON Schema definitions for data validation -2. **Workflows**: Business process definitions and state machines -3. **Tasks**: Individual task definitions and configurations -4. **Views**: User interface and presentation components -5. **Functions**: Reusable business logic functions -6. **Extensions**: Plugin and extension definitions +```bash +# Build to custom directory +npm run build -- -o my-build +# Reference build to custom directory +npm run build -- -t reference -o packages/ref -Example: `user-registration.1.0.0.json` +# Skip validation for faster builds +npm run build -- --skip-validation +``` -## 🛠️ Development +### Build Output Structure -### Running Tests -```bash -npm test +**Runtime Build:** ``` - -### Validation -```bash -npm run validate +dist/ +├── / +│ ├── Extensions/ +│ ├── Functions/ +│ ├── Schemas/ +│ ├── Tasks/ +│ ├── Views/ +│ └── Workflows/ +├── vnext.config.json +├── package.json +├── README.md +└── LICENSE ``` -## 📋 Schema Validation Rules - -The template follows strict schema validation rules: +**Reference Build:** +``` +dist/ +├── / +│ ├── Extensions/ # Only exported files +│ ├── Functions/ # Only exported files +│ ├── Schemas/ # Only exported files +│ ├── Tasks/ # Only exported files +│ ├── Views/ # Only exported files +│ └── Workflows/ # Only exported files +├── vnext.config.json +├── package.json +├── README.md +└── LICENSE +``` -### Instance Base Properties -- Schema instances MUST include: `key`, `version`, `domain`, `flow`, `flowVersion` -- Schema instances MUST NOT include: `labels` (labels belong to business logic) -- Optional fields: `id`, `eTag` (added by platform in production) +## 📜 Available Scripts -### Reference Pattern -- References use: `domain` + `workflow` + (`id` OR `key`) + optional `version` -- NO `type` property in references - `workflow` field serves as type -- Always use local file references: `reference.json#/attributes` +| Script | Description | +|--------|-------------| +| `npm run validate` | Validate project structure and schemas | +| `npm run build` | Build runtime package to dist/ | +| `npm run build:runtime` | Build runtime package explicitly | +| `npm run build:reference` | Build reference package with exports only | +| `npm run setup ` | Setup domain with given name | +| `npm run sync-schema` | Sync schema version from dependencies | +| `npm test` | Run tests | -### Standard Lifecycle Pattern -All lifecycle workflows must have: -- `draft` (type: "start") - Initial state -- `active` (type: "normal") - Active state -- `passive` (type: "finish") - Final state ## 🤝 Contributing @@ -229,15 +237,15 @@ This package is maintained by the Burgan Tech team as part of our commitment to ## 🔗 Links -- [NPM Package](https://www.npmjs.com/package/@burgan-tech/vnext-template) -- [GitHub Repository](https://github.com/burgan-tech/vnext-template) -- [Issues](https://github.com/burgan-tech/vnext-template/issues) -- [Documentation](https://github.com/burgan-tech/vnext-template#readme) +- [NPM Package](https://www.npmjs.com/package/@burgan-tech/vnext-example) +- [GitHub Repository](https://github.com/burgan-tech/vnext-example) +- [Issues](https://github.com/burgan-tech/vnext-example/issues) +- [Documentation](https://github.com/burgan-tech/vnext-example#readme) ## 📞 Support For support and questions: -- Create an issue on [GitHub](https://github.com/burgan-tech/vnext-template/issues) +- Create an issue on [GitHub](https://github.com/burgan-tech/vnext-example/issues) - Contact the development team at dev@burgan-tech.com --- diff --git a/build.js b/build.js new file mode 100644 index 0000000..4a96168 --- /dev/null +++ b/build.js @@ -0,0 +1,429 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +// ANSI color codes for terminal output +const colors = { + reset: '\x1b[0m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + bright: '\x1b[1m', + dim: '\x1b[2m' +}; + +function colorize(text, color) { + return `${colors[color]}${text}${colors.reset}`; +} + +// Load vnext.config.json +function loadConfig() { + try { + return JSON.parse(fs.readFileSync('vnext.config.json', 'utf8')); + } catch (error) { + return null; + } +} + +// Get paths configuration with defaults +function getPathsConfig() { + const config = loadConfig(); + const defaults = { + componentsRoot: 'core', + schemas: 'Schemas', + workflows: 'Workflows', + tasks: 'Tasks', + views: 'Views', + functions: 'Functions', + extensions: 'Extensions' + }; + + if (config && config.paths) { + return { ...defaults, ...config.paths }; + } + return defaults; +} + +// Recursively find all JSON files in a directory +function findJsonFiles(dirPath, files = []) { + if (!fs.existsSync(dirPath)) { + return files; + } + + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + + for (const entry of entries) { + // Skip .meta directory + if (entry.name === '.meta') { + continue; + } + + const fullPath = path.join(dirPath, entry.name); + + if (entry.isDirectory()) { + findJsonFiles(fullPath, files); + } else if (entry.isFile() && entry.name.endsWith('.json')) { + files.push(fullPath); + } + } + + return files; +} + +// Recursively get all files in a directory +function getAllFiles(dirPath, files = []) { + if (!fs.existsSync(dirPath)) { + return files; + } + + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + + for (const entry of entries) { + // Skip .meta directory + if (entry.name === '.meta') { + continue; + } + + const fullPath = path.join(dirPath, entry.name); + + if (entry.isDirectory()) { + getAllFiles(fullPath, files); + } else { + files.push(fullPath); + } + } + + return files; +} + +// Recursively remove directory +function removeDir(dirPath) { + if (fs.existsSync(dirPath)) { + fs.readdirSync(dirPath).forEach(file => { + const curPath = path.join(dirPath, file); + if (fs.lstatSync(curPath).isDirectory()) { + removeDir(curPath); + } else { + fs.unlinkSync(curPath); + } + }); + fs.rmdirSync(dirPath); + } +} + +// Recursively create directory +function ensureDir(dirPath) { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +} + +// Copy file with directory creation +function copyFile(src, dest) { + ensureDir(path.dirname(dest)); + fs.copyFileSync(src, dest); +} + +// Write JSON file with directory creation +function writeJsonFile(filePath, data) { + ensureDir(path.dirname(filePath)); + fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8'); +} + +// Parse command line arguments +function parseArgs() { + const args = process.argv.slice(2); + const options = { + output: 'dist', + type: 'runtime', // default to runtime + skipValidation: false, + help: false + }; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg === '-o' || arg === '--output') { + options.output = args[++i]; + } else if (arg === '-t' || arg === '--type') { + options.type = args[++i]; + } else if (arg === '--skip-validation') { + options.skipValidation = true; + } else if (arg === '-h' || arg === '--help') { + options.help = true; + } + } + + return options; +} + +// Show help +function showHelp() { + console.log(` +${colorize('vNext Domain Build Tool', 'bright')} + +${colorize('Usage:', 'yellow')} + node build.js [options] + npm run build -- [options] + +${colorize('Options:', 'yellow')} + -o, --output Output directory (default: dist) + -t, --type Build type: reference or runtime (default: runtime) + --skip-validation Skip validation during build + -h, --help Show this help message + +${colorize('Build Types:', 'yellow')} + ${colorize('reference', 'cyan')} - Exports only (for cross-domain usage) + Only components listed in vnext.config.json exports are included + + ${colorize('runtime', 'cyan')} - Complete domain structure (for engine deployment) + All components and supporting files are included + +${colorize('Examples:', 'yellow')} + npm run build # Runtime build to dist/ + npm run build -- -t reference # Reference build to dist/ + npm run build -- -o build -t runtime # Runtime build to build/ + npm run build -- --skip-validation # Skip validation step +`); +} + +// Main build function +async function build() { + const options = parseArgs(); + + if (options.help) { + showHelp(); + process.exit(0); + } + + // Validate build type + if (!['reference', 'runtime'].includes(options.type)) { + console.log(colorize('❌ Invalid build type. Use "reference" or "runtime".', 'red')); + process.exit(1); + } + + console.log(colorize(`\n🏗️ Building ${options.type} package...`, 'blue')); + console.log('═'.repeat(60)); + + // Load configuration + const config = loadConfig(); + if (!config) { + console.log(colorize('❌ vnext.config.json not found.', 'red')); + process.exit(1); + } + + const pathsConfig = getPathsConfig(); + const domainPath = pathsConfig.componentsRoot; + const outputDir = options.output; + + console.log(` Domain: ${colorize(config.domain, 'cyan')}`); + console.log(` Build Type: ${colorize(options.type, 'magenta')}`); + console.log(` Output: ${colorize(outputDir, 'yellow')}`); + + // Step 1: Validation (unless skipped) + if (!options.skipValidation) { + console.log(colorize('\n📋 Step 1: Running full validation (validate.js)...', 'blue')); + console.log('─'.repeat(60)); + + try { + const { execSync } = require('child_process'); + execSync('node validate.js', { stdio: 'inherit' }); + console.log(colorize(' ✅ Validation completed successfully', 'green')); + } catch (error) { + console.log(colorize('\n❌ Build failed: Validation errors found', 'red')); + console.log(colorize(' Run "npm run validate" for detailed error information', 'dim')); + process.exit(1); + } + } else { + console.log(colorize('\n⚠️ Step 1: Validation skipped (--skip-validation)', 'yellow')); + } + + // Step 2: Clean and prepare output directory + console.log(colorize('\n📦 Step 2: Preparing build directory...', 'blue')); + console.log('─'.repeat(60)); + + removeDir(outputDir); + ensureDir(outputDir); + console.log(colorize(` ✅ Output directory prepared: ${outputDir}`, 'green')); + + // Step 3: Copy configuration files + console.log(colorize('\n📄 Step 3: Copying configuration files...', 'blue')); + console.log('─'.repeat(60)); + + // Copy vnext.config.json + writeJsonFile(path.join(outputDir, 'vnext.config.json'), config); + console.log(colorize(' ✅ vnext.config.json', 'green')); + + // Copy and modify package.json + if (fs.existsSync('package.json')) { + const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); + const originalPackageName = packageJson.name; + + // Modify package name based on build type + if (options.type === 'reference') { + packageJson.name = `${originalPackageName}-reference`; + packageJson.description = `${packageJson.description || ''} (Reference Package for Cross-Domain Usage)`.trim(); + } else if (options.type === 'runtime') { + packageJson.name = `${originalPackageName}-runtime`; + packageJson.description = `${packageJson.description || ''} (Runtime Package for Engine Deployment)`.trim(); + } + + // Add build type metadata + packageJson.vnext = { + ...packageJson.vnext, + buildType: options.type, + buildTimestamp: new Date().toISOString(), + originalPackage: originalPackageName + }; + + // Remove scripts that aren't needed in the built package + delete packageJson.scripts; + delete packageJson.devDependencies; + delete packageJson.bin; + + writeJsonFile(path.join(outputDir, 'package.json'), packageJson); + console.log(colorize(` ✅ package.json (${packageJson.name})`, 'green')); + } + + // Copy README.md if exists + if (fs.existsSync('README.md')) { + copyFile('README.md', path.join(outputDir, 'README.md')); + console.log(colorize(' ✅ README.md', 'green')); + } + + // Copy LICENSE if exists + if (fs.existsSync('LICENSE')) { + copyFile('LICENSE', path.join(outputDir, 'LICENSE')); + console.log(colorize(' ✅ LICENSE', 'green')); + } + + // Step 4: Process and copy components based on build type + let copiedFiles = 0; + let copiedSupportFiles = 0; + + if (options.type === 'reference') { + console.log(colorize('\n🔗 Step 4: Processing exported components...', 'blue')); + console.log('─'.repeat(60)); + + // Create domain root and component directories from paths config + const targetDomainPath = path.join(outputDir, config.domain); + ensureDir(targetDomainPath); + + const componentDirs = [ + pathsConfig.schemas, + pathsConfig.workflows, + pathsConfig.tasks, + pathsConfig.views, + pathsConfig.functions, + pathsConfig.extensions + ]; + + for (const dir of componentDirs) { + const targetDir = path.join(targetDomainPath, dir); + ensureDir(targetDir); + } + console.log(colorize(` ✅ Created domain structure: ${config.domain}/`, 'green')); + + // Check if there are any exports configured + const hasExports = config.exports && Object.entries(config.exports).some( + ([key, files]) => Array.isArray(files) && files.length > 0 + ); + + if (hasExports) { + for (const [category, files] of Object.entries(config.exports)) { + if (Array.isArray(files) && files.length > 0) { + const categoryPath = pathsConfig[category] || category; + + for (const filename of files) { + const sourcePath = path.join(domainPath, categoryPath, filename); + const targetPath = path.join(outputDir, config.domain, categoryPath, filename); + + if (fs.existsSync(sourcePath)) { + try { + const content = JSON.parse(fs.readFileSync(sourcePath, 'utf8')); + writeJsonFile(targetPath, content); + console.log(colorize(` ✅ ${categoryPath}/${filename}`, 'green')); + copiedFiles++; + } catch (error) { + console.log(colorize(` ❌ ${categoryPath}/${filename}: ${error.message}`, 'red')); + } + } else { + console.log(colorize(` ⚠️ Not found: ${categoryPath}/${filename}`, 'yellow')); + } + } + } + } + } else { + console.log(colorize(' ℹ️ No exports configured - empty domain structure created', 'dim')); + } + + } else if (options.type === 'runtime') { + console.log(colorize('\n📁 Step 4: Processing complete domain structure...', 'blue')); + console.log('─'.repeat(60)); + + if (fs.existsSync(domainPath)) { + const allFiles = getAllFiles(domainPath); + const targetDomainPath = path.join(outputDir, config.domain); + + for (const filePath of allFiles) { + const relativePath = path.relative(domainPath, filePath); + const targetPath = path.join(targetDomainPath, relativePath); + + try { + if (path.extname(filePath) === '.json') { + // Process JSON files + const content = JSON.parse(fs.readFileSync(filePath, 'utf8')); + writeJsonFile(targetPath, content); + copiedFiles++; + } else { + // Copy non-JSON files as-is + copyFile(filePath, targetPath); + copiedSupportFiles++; + } + } catch (error) { + console.log(colorize(` ⚠️ Error processing ${relativePath}: ${error.message}`, 'yellow')); + // Copy as-is if processing fails + copyFile(filePath, targetPath); + } + } + + console.log(colorize(` ✅ Processed ${copiedFiles} JSON component files`, 'green')); + console.log(colorize(` ✅ Copied ${copiedSupportFiles} supporting files`, 'green')); + } else { + console.log(colorize(` ❌ Domain directory not found: ${domainPath}`, 'red')); + process.exit(1); + } + } + + // Build Summary + console.log('\n' + '═'.repeat(60)); + console.log(colorize('📊 Build Summary:', 'bright')); + console.log('═'.repeat(60)); + console.log(` Build type: ${colorize(options.type, 'magenta')}`); + console.log(` JSON files processed: ${colorize(copiedFiles, 'cyan')}`); + if (options.type === 'runtime') { + console.log(` Supporting files copied: ${colorize(copiedSupportFiles, 'cyan')}`); + } + console.log(` Output directory: ${colorize(outputDir, 'yellow')}`); + + if (options.type === 'reference') { + console.log(colorize('\n Package contents: Exported components only (for cross-domain usage)', 'dim')); + } else { + console.log(colorize('\n Package contents: Complete domain structure (for runtime deployment)', 'dim')); + } + + console.log('═'.repeat(60)); + console.log(colorize(`\n🎉 ${options.type.charAt(0).toUpperCase() + options.type.slice(1)} package built successfully!`, 'green')); + console.log(colorize(` Output: ${path.resolve(outputDir)}\n`, 'dim')); +} + +// Run build +build().catch(error => { + console.error(colorize(`\n❌ Build error: ${error.message}`, 'red')); + process.exit(1); +}); + diff --git a/core/Schemas/account-opening/account-confirmation.json b/core/Schemas/account-opening/account-confirmation.json index f2c75c9..dc95e8c 100644 --- a/core/Schemas/account-opening/account-confirmation.json +++ b/core/Schemas/account-opening/account-confirmation.json @@ -11,11 +11,7 @@ "approval" ], "attributes": { - "type": "object", - "$id": "https://schemas.vnext.com/banking/account-type-selection.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Account Type Selection Schema", - "description": "Schema for account type selection input", + "type": "workflow", "schema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.vnext.com/banking/account-confirmation.json", diff --git a/core/Schemas/account-opening/account-details-input.json b/core/Schemas/account-opening/account-details-input.json index 4c4e2b1..62b50a3 100644 --- a/core/Schemas/account-opening/account-details-input.json +++ b/core/Schemas/account-opening/account-details-input.json @@ -11,11 +11,7 @@ "demand-deposit" ], "attributes": { - "type": "object", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.vnext.com/banking/account-details-input.json", - "title": "Account Details Input Schema", - "description": "Schema for account details input during account opening", + "type": "workflow", "schema": { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.vnext.com/banking/account-details-input.json", diff --git a/core/Schemas/account-opening/account-type-selection.json b/core/Schemas/account-opening/account-type-selection.json index 28d4496..28a4951 100644 --- a/core/Schemas/account-opening/account-type-selection.json +++ b/core/Schemas/account-opening/account-type-selection.json @@ -11,11 +11,7 @@ "input-schema" ], "attributes": { - "type": "object", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.vnext.com/banking/account-type-selection.json", - "title": "Account Type Selection Schema", - "description": "Schema for account type selection input", + "type": "workflow", "schema": { "$id": "https://schemas.vnext.com/banking/account-type-selection.json", "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/core/Tasks/account-opening/create-bank-account.json b/core/Tasks/account-opening/create-bank-account.json index 65c40d5..7d16122 100644 --- a/core/Tasks/account-opening/create-bank-account.json +++ b/core/Tasks/account-opening/create-bank-account.json @@ -14,7 +14,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/banking/accounts/create", + "url": "http://localhost:3001/api/banking/accounts/create", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/account-opening/validate-account-policies.json b/core/Tasks/account-opening/validate-account-policies.json index 94304c4..98ef730 100644 --- a/core/Tasks/account-opening/validate-account-policies.json +++ b/core/Tasks/account-opening/validate-account-policies.json @@ -14,7 +14,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/banking/policies/validate-account-opening", + "url": "http://localhost:3001/api/banking/policies/validate-account-opening", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/oauth/check-device-registration.json b/core/Tasks/oauth/check-device-registration.json index 6736ac4..e8b4414 100644 --- a/core/Tasks/oauth/check-device-registration.json +++ b/core/Tasks/oauth/check-device-registration.json @@ -14,7 +14,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/oauth2/devices/check/", + "url": "http://localhost:3001/api/oauth2/devices/check/", "method": "GET", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/oauth/check-push-response.json b/core/Tasks/oauth/check-push-response.json index 91865b0..fd90b2e 100644 --- a/core/Tasks/oauth/check-push-response.json +++ b/core/Tasks/oauth/check-push-response.json @@ -14,7 +14,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/oauth2/push/status", + "url": "http://localhost:3001/api/oauth2/push/status", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/oauth/generate-tokens.json b/core/Tasks/oauth/generate-tokens.json index caa1fe9..4a7a1c3 100644 --- a/core/Tasks/oauth/generate-tokens.json +++ b/core/Tasks/oauth/generate-tokens.json @@ -14,7 +14,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/oauth2/tokens/generate", + "url": "http://localhost:3001/api/oauth2/tokens/generate", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/oauth/register-device.json b/core/Tasks/oauth/register-device.json index 14eada0..78bf3c4 100644 --- a/core/Tasks/oauth/register-device.json +++ b/core/Tasks/oauth/register-device.json @@ -14,7 +14,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/oauth2/devices/register", + "url": "http://localhost:3001/api/oauth2/devices/register", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/oauth/send-otp-notification.json b/core/Tasks/oauth/send-otp-notification.json index 26770e5..907e43a 100644 --- a/core/Tasks/oauth/send-otp-notification.json +++ b/core/Tasks/oauth/send-otp-notification.json @@ -15,7 +15,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/oauth2/otp/send", + "url": "http://localhost:3001/api/oauth2/otp/send", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/oauth/send-push-notification.json b/core/Tasks/oauth/send-push-notification.json index 4278c11..3cbcd1e 100644 --- a/core/Tasks/oauth/send-push-notification.json +++ b/core/Tasks/oauth/send-push-notification.json @@ -14,7 +14,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/oauth2/push/send", + "url": "http://localhost:3001/api/oauth2/push/send", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/oauth/validate-authorization-code.json b/core/Tasks/oauth/validate-authorization-code.json index a0c3491..50eea10 100644 --- a/core/Tasks/oauth/validate-authorization-code.json +++ b/core/Tasks/oauth/validate-authorization-code.json @@ -14,7 +14,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/oauth2/user/validate-authorization-code", + "url": "http://localhost:3001/api/oauth2/user/validate-authorization-code", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/oauth/validate-client.json b/core/Tasks/oauth/validate-client.json index 7cbe900..5b1c580 100644 --- a/core/Tasks/oauth/validate-client.json +++ b/core/Tasks/oauth/validate-client.json @@ -13,7 +13,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/oauth2/client/validate", + "url": "http://localhost:3001/api/oauth2/client/validate", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/oauth/validate-user-credentials.json b/core/Tasks/oauth/validate-user-credentials.json index 82f2a40..cc4150a 100644 --- a/core/Tasks/oauth/validate-user-credentials.json +++ b/core/Tasks/oauth/validate-user-credentials.json @@ -14,7 +14,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/oauth2/user/authenticate", + "url": "http://localhost:3001/api/oauth2/user/authenticate", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/oauth/verify-otp-code.json b/core/Tasks/oauth/verify-otp-code.json index 861b867..a74759a 100644 --- a/core/Tasks/oauth/verify-otp-code.json +++ b/core/Tasks/oauth/verify-otp-code.json @@ -14,7 +14,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/oauth2/otp/verify", + "url": "http://localhost:3001/api/oauth2/otp/verify", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/payments/activate-payment-schedule.json b/core/Tasks/payments/activate-payment-schedule.json index 6556187..61eed63 100644 --- a/core/Tasks/payments/activate-payment-schedule.json +++ b/core/Tasks/payments/activate-payment-schedule.json @@ -13,7 +13,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/payments/schedules/{scheduleId}/activate", + "url": "http://localhost:3001/api/payments/schedules/{scheduleId}/activate", "method": "PATCH", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/payments/archive-payment-record.json b/core/Tasks/payments/archive-payment-record.json index 1149578..64f9d05 100644 --- a/core/Tasks/payments/archive-payment-record.json +++ b/core/Tasks/payments/archive-payment-record.json @@ -13,7 +13,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/payments/schedules/{scheduleId}/archive", + "url": "http://localhost:3001/api/payments/schedules/{scheduleId}/archive", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/payments/deactivate-payment-schedule.json b/core/Tasks/payments/deactivate-payment-schedule.json index 222962c..2c10b5c 100644 --- a/core/Tasks/payments/deactivate-payment-schedule.json +++ b/core/Tasks/payments/deactivate-payment-schedule.json @@ -13,7 +13,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/payments/schedules/{scheduleId}/deactivate", + "url": "http://localhost:3001/api/payments/schedules/{scheduleId}/deactivate", "method": "PATCH", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/payments/get-user-info.json b/core/Tasks/payments/get-user-info.json index cacd2e7..628989a 100644 --- a/core/Tasks/payments/get-user-info.json +++ b/core/Tasks/payments/get-user-info.json @@ -14,7 +14,7 @@ "type": "6", "config": { "method": "GET", - "url": "http://mockoon:3001/api/payments/users/{userId}", + "url": "http://localhost:3001/api/payments/users/{userId}", "headers": { "Content-Type": "application/json" }, diff --git a/core/Tasks/payments/increment-retry-counter.json b/core/Tasks/payments/increment-retry-counter.json index 43459b9..6cc7eb1 100644 --- a/core/Tasks/payments/increment-retry-counter.json +++ b/core/Tasks/payments/increment-retry-counter.json @@ -13,7 +13,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/payments/schedules/{scheduleId}/retry", + "url": "http://localhost:3001/api/payments/schedules/{scheduleId}/retry", "method": "PATCH", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/payments/process-payment.json b/core/Tasks/payments/process-payment.json index 3e1eb58..7e0349d 100644 --- a/core/Tasks/payments/process-payment.json +++ b/core/Tasks/payments/process-payment.json @@ -13,7 +13,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/payments/process", + "url": "http://localhost:3001/api/payments/process", "method": "POST", "headers": { "Content-Type": "application/json" diff --git a/core/Tasks/payments/save-payment-configuration.json b/core/Tasks/payments/save-payment-configuration.json index 17775f1..e735ab0 100644 --- a/core/Tasks/payments/save-payment-configuration.json +++ b/core/Tasks/payments/save-payment-configuration.json @@ -13,7 +13,7 @@ "attributes": { "type": "6", "config": { - "url": "http://mockoon:3001/api/payments/schedules", + "url": "http://localhost:3001/api/payments/schedules", "method": "POST", "headers": { "Content-Type": "application/json" @@ -24,3 +24,4 @@ } } } + diff --git a/core/Tasks/payments/send-payment-notification-sms.json b/core/Tasks/payments/send-payment-notification-sms.json index 90af20d..45ebb56 100644 --- a/core/Tasks/payments/send-payment-notification-sms.json +++ b/core/Tasks/payments/send-payment-notification-sms.json @@ -14,7 +14,7 @@ "type": "6", "config": { "method": "POST", - "url": "http://mockoon:3001/api/payments/notify/sms", + "url": "http://localhost:3001/api/payments/notify/sms", "headers": { "Content-Type": "application/json" }, diff --git a/core/Tasks/payments/send-payment-push-notification.json b/core/Tasks/payments/send-payment-push-notification.json index 558699d..e2df447 100644 --- a/core/Tasks/payments/send-payment-push-notification.json +++ b/core/Tasks/payments/send-payment-push-notification.json @@ -15,7 +15,7 @@ "type": "6", "config": { "method": "POST", - "url": "http://mockoon:3001/api/payments/notify/push", + "url": "http://localhost:3001/api/payments/notify/push", "headers": { "Content-Type": "application/json" }, diff --git a/core/Workflows/account-opening/.meta/account-opening-workflow.diagram.json b/core/Workflows/account-opening/.meta/account-opening-workflow.diagram.json deleted file mode 100644 index e9a9b08..0000000 --- a/core/Workflows/account-opening/.meta/account-opening-workflow.diagram.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "nodePos": { - "account-type-selection": { - "x": 630, - "y": 564.5 - }, - "account-details-input": { - "x": 942, - "y": 475.5 - }, - "account-confirmation": { - "x": 1254, - "y": 475.5 - }, - "policy-validation": { - "x": 31.125874125874105, - "y": 223.04195804195803 - }, - "account-creation": { - "x": 606, - "y": 196 - }, - "account-opening-success": { - "x": 1082, - "y": 228 - }, - "cancelled": { - "x": 106.8527808069793, - "y": 2.3478735005452407 - }, - "__start__": { - "x": -57, - "y": 450.5 - } - } -} \ No newline at end of file diff --git a/core/Workflows/account-opening/account-opening-workflow.json b/core/Workflows/account-opening/account-opening-workflow.json index cceee85..a8f780d 100644 --- a/core/Workflows/account-opening/account-opening-workflow.json +++ b/core/Workflows/account-opening/account-opening-workflow.json @@ -26,6 +26,20 @@ "functions": [], "features": [], "extensions": [], + "cancel": { + "key": "cancel-account-opening", + "target": "cancelled", + "triggerType": 0, + "versionStrategy": "Minor", + "labels": [ + { + "language": "en-US", + "label": "Cancel Account Opening" + } + ], + "onExecutionTasks": [], + "availableIn": [] + }, "sharedTransitions": [], "startTransition": { "key": "initiate-account-opening", @@ -56,12 +70,17 @@ "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKdXNpbmcgQkJULldvcmtmbG93LkRlZmluaXRpb25zOwoKcHVibGljIGNsYXNzIFVzZXJTZXNzaW9uTWFwcGluZyA6IElNYXBwaW5nCnsKICAgIHB1YmxpYyBUYXNrPFNjcmlwdFJlc3BvbnNlPiBJbnB1dEhhbmRsZXIoV29ya2Zsb3dUYXNrIHRhc2ssIFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICByZXR1cm4gVGFzay5Gcm9tUmVzdWx0KG5ldyBTY3JpcHRSZXNwb25zZSgpKTsKICAgIH0KCiAgICAvLy8gPHN1bW1hcnk+CiAgICAvLy8gUG9wdWxhdGUgdGhlIHVzZXIgc2Vzc2lvbiBkYXRhIGludG8gdGhlIHdvcmtmbG93IGluc3RhbmNlCiAgICAvLy8gPC9zdW1tYXJ5PgogICAgcHVibGljIGFzeW5jIFRhc2s8U2NyaXB0UmVzcG9uc2U+IE91dHB1dEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgewogICAgICAgIHJldHVybiBuZXcgU2NyaXB0UmVzcG9uc2UKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgS2V5ID0gInVzZXItc2Vzc2lvbi1vdXRwdXQiLAogICAgICAgICAgICAgICAgRGF0YSA9IG5ldwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIHVzZXJTZXNzaW9uID0gbmV3CiAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICB1c2VySWQgPSBjb250ZXh0LkhlYWRlcnM/WyJ1c2VyX3JlZmVyZW5jZSJdLAogICAgICAgICAgICAgICAgICAgICAgICBkZXZpY2VJZCA9IGNvbnRleHQuSGVhZGVycz9bIngtZGV2aWNlLWlkIl0sCiAgICAgICAgICAgICAgICAgICAgICAgIHVzZXJBZ2VudCA9IGNvbnRleHQuSGVhZGVycz9bInVzZXItYWdlbnQiXSwKICAgICAgICAgICAgICAgICAgICAgICAgaXBBZGRyZXNzID0gY29udGV4dC5IZWFkZXJzP1sieC1mb3J3YXJkZWQtZm9yIl0gPz8gY29udGV4dC5IZWFkZXJzP1sieC1yZWFsLWlwIl0KICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH07CiAgICB9Cn0K" } } - ] + ], + "mapping": { + "location": "./src/InitialTransMapping.csx", + "code": "dXNpbmcgU3lzdGVtOwp1c2luZyBTeXN0ZW0uRHluYW1pYzsKdXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKdXNpbmcgQkJULldvcmtmbG93LkRlZmluaXRpb25zOwoKcHVibGljIGNsYXNzIEluaXRpYWxUcmFuc01hcHBpbmcgOiBJVHJhbnNpdGlvbk1hcHBpbmcKewogICAgcHVibGljIGFzeW5jIFRhc2s8ZHluYW1pYz4gSGFuZGxlcihTY3JpcHRDb250ZXh0IGNvbnRleHQpCiAgICB7CiAgICAgICAgZHluYW1pYyBvdXRwdXQgPSBuZXcgRXhwYW5kb09iamVjdCgpOwogICAgICAgIHRyeQogICAgICAgIHsKICAgICAgICAgICAgb3V0cHV0LmluaXRpYWwgPSBuZXcgewogICAgICAgICAgICAgICAgc2Vzc2lvbiA9IGNvbnRleHQuQm9keS5zZXNzaW9uLAogICAgICAgICAgICAgICAgcmVxdWVzdElkID0gY29udGV4dC5IZWFkZXJzP1sieC1yZXF1ZXN0LWlkIl0gPz8gR3VpZC5OZXdHdWlkKCkuVG9TdHJpbmcoKSwKICAgICAgICAgICAgfTsKICAgICAgICAKCiAgICAgICAgICAgIHJldHVybiBvdXRwdXQ7CiAgICAgICAgfQogICAgICAgIGNhdGNoIChFeGNlcHRpb24gZXgpCiAgICAgICAgewogICAgICAgICAgICByZXR1cm4gb3V0cHV0OwogICAgICAgIH0KICAgIH0KfQo=" + } }, "states": [ { "key": "account-type-selection", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -129,6 +148,7 @@ { "key": "account-details-input", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -194,6 +214,7 @@ { "key": "account-confirmation", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -259,6 +280,7 @@ { "key": "policy-validation", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -340,7 +362,7 @@ "schema": null, "rule": { "location": "./src/PoliciesFailedRule.csx", - "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vLyA8c3VtbWFyeT4KLy8vIFBvbGljaWVzIEZhaWxlZCBSdWxlIC0gQ2hlY2tzIGlmIHBvbGljeSB2YWxpZGF0aW9uIGZhaWxlZAovLy8gVGhpcyBydWxlIGRldGVybWluZXMgaWYgdGhlIGFjY291bnQgb3BlbmluZyByZXF1ZXN0IGZhaWxlZCBwb2xpY3kgdmFsaWRhdGlvbnMuCi8vLyA8L3N1bW1hcnk+CnB1YmxpYyBjbGFzcyBQb2xpY2llc0ZhaWxlZFJ1bGUgOiBJQ29uZGl0aW9uTWFwcGluZwp7CiAgICAvLy8gPHN1bW1hcnk+CiAgICAvLy8gQ2hlY2sgaWYgdGhlIHBvbGljeSB2YWxpZGF0aW9uIGZhaWxlZAogICAgLy8vIDwvc3VtbWFyeT4KICAgIHB1YmxpYyBhc3luYyBUYXNrPGJvb2w+IEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgewogICAgICAgIHRyeQogICAgICAgIHsKICAgICAgICAgICAgLy8gTnVsbCBjaGVjawogICAgICAgICAgICBpZiAoY29udGV4dD8uSW5zdGFuY2U/LkRhdGEgPT0gbnVsbCkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7IC8vIElmIG5vIGRhdGEsIGNvbnNpZGVyIGl0IGZhaWxlZAogICAgICAgICAgICB9CgogICAgICAgICAgICAvLyBQb2xpY3kgdmFsaWRhdGlvbiBkYXRhCiAgICAgICAgICAgIHZhciBwb2xpY3lWYWxpZGF0aW9uID0gY29udGV4dC5JbnN0YW5jZS5EYXRhLnBvbGljeVZhbGlkYXRpb247CiAgICAgICAgICAgIAogICAgICAgICAgICBpZiAocG9saWN5VmFsaWRhdGlvbiA9PSBudWxsKQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTsgLy8gSWYgbm8gcG9saWN5IHZhbGlkYXRpb24gZGF0YSwgY29uc2lkZXIgaXQgZmFpbGVkCiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIC8vIENoZWNrIGlmIHRoZSBwb2xpY3kgdmFsaWRhdGlvbiBmYWlsZWQKICAgICAgICAgICAgdmFyIGZhaWxlZCA9IHBvbGljeVZhbGlkYXRpb24ucGFzc2VkID09IGZhbHNlOwogICAgICAgICAgICAKICAgICAgICAgICAgLy8gQWRkaXRpb25hbCBmYWlsdXJlIGNoZWNrcwogICAgICAgICAgICBpZiAoIWZhaWxlZCkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgLy8gQ2hlY2sgY29tcGxpYW5jZSBmYWlsdXJlcwogICAgICAgICAgICAgICAgdmFyIGNvbXBsaWFuY2VDaGVja3MgPSBwb2xpY3lWYWxpZGF0aW9uLmNvbXBsaWFuY2VDaGVja3M7CiAgICAgICAgICAgICAgICBpZiAoY29tcGxpYW5jZUNoZWNrcyAhPSBudWxsKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIHZhciBreWNGYWlsZWQgPSBjb21wbGlhbmNlQ2hlY2tzLmt5Y0NvbXBsaWFudCA9PSBmYWxzZTsKICAgICAgICAgICAgICAgICAgICB2YXIgYW1sRmFpbGVkID0gY29tcGxpYW5jZUNoZWNrcy5hbWxDb21wbGlhbnQgPT0gZmFsc2U7CiAgICAgICAgICAgICAgICAgICAgdmFyIHNhbmN0aW9uc0ZhaWxlZCA9IGNvbXBsaWFuY2VDaGVja3Muc2FuY3Rpb25zQ2hlY2sgPT0gZmFsc2U7CiAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgZmFpbGVkID0ga3ljRmFpbGVkIHx8IGFtbEZhaWxlZCB8fCBzYW5jdGlvbnNGYWlsZWQ7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIC8vIENoZWNrIHZhbGlkYXRpb24gc2NvcmUgaWYgYXZhaWxhYmxlCiAgICAgICAgICAgICAgICB2YXIgdmFsaWRhdGlvblNjb3JlID0gcG9saWN5VmFsaWRhdGlvbi52YWxpZGF0aW9uU2NvcmU7CiAgICAgICAgICAgICAgICBpZiAodmFsaWRhdGlvblNjb3JlICE9IG51bGwpCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgZmFpbGVkID0gZmFpbGVkIHx8IHZhbGlkYXRpb25TY29yZSA8IDcwOyAvLyBCZWxvdyBtaW5pbXVtIHRocmVzaG9sZAogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAvLyBDaGVjayBmb3Igc3BlY2lmaWMgZXJyb3IgY29kZXMgdGhhdCBpbmRpY2F0ZSBmYWlsdXJlCiAgICAgICAgICAgICAgICB2YXIgZXJyb3JDb2RlID0gcG9saWN5VmFsaWRhdGlvbi5lcnJvckNvZGU7CiAgICAgICAgICAgICAgICBpZiAoIXN0cmluZy5Jc051bGxPckVtcHR5KGVycm9yQ29kZT8uVG9TdHJpbmcoKSkpCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgdmFyIGZhaWx1cmVDb2RlcyA9IG5ld1tdIAogICAgICAgICAgICAgICAgICAgIHsgCiAgICAgICAgICAgICAgICAgICAgICAgICJwb2xpY3lfdmlvbGF0aW9uIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJjb21wbGlhbmNlX3Zpb2xhdGlvbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAicmlza19hc3Nlc3NtZW50X2ZhaWxlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICJreWNfZmFpbGVkIiwKICAgICAgICAgICAgICAgICAgICAgICAgImFtbF9mYWlsZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAic2FuY3Rpb25zX2NoZWNrX2ZhaWxlZCIKICAgICAgICAgICAgICAgICAgICB9OwogICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgIGZhaWxlZCA9IGZhaWxlZCB8fCBmYWlsdXJlQ29kZXMuQ29udGFpbnMoZXJyb3JDb2RlLlRvU3RyaW5nKCkpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CgogICAgICAgICAgICByZXR1cm4gZmFpbGVkOwogICAgICAgIH0KICAgICAgICBjYXRjaCAoRXhjZXB0aW9uIGV4KQogICAgICAgIHsKICAgICAgICAgICAgLy8gSWYgYW4gZXhjZXB0aW9uIG9jY3VycywgbG9nIGl0IGFuZCByZXR1cm4gdHJ1ZSAoZmFpbGVkKQogICAgICAgICAgICBpZiAoY29udGV4dD8uTWV0YURhdGEgIT0gbnVsbCkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgY29udGV4dC5NZXRhRGF0YVsicG9saWNpZXNfZmFpbGVkX3J1bGVfZXJyb3IiXSA9IG5ldwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIGVycm9yID0gZXguTWVzc2FnZSwKICAgICAgICAgICAgICAgICAgICB0eXBlID0gZXguR2V0VHlwZSgpLk5hbWUsCiAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wID0gRGF0ZVRpbWUuVXRjTm93CiAgICAgICAgICAgICAgICB9OwogICAgICAgICAgICB9CgogICAgICAgICAgICByZXR1cm4gdHJ1ZTsgLy8gRXhjZXB0aW9uIG1lYW5zIGZhaWx1cmUKICAgICAgICB9CiAgICB9Cn0K" + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vLyA8c3VtbWFyeT4KLy8vIFBvbGljaWVzIEZhaWxlZCBSdWxlIC0gQ2hlY2tzIGlmIHBvbGljeSB2YWxpZGF0aW9uIGZhaWxlZAovLy8gVGhpcyBydWxlIGRldGVybWluZXMgaWYgdGhlIGFjY291bnQgb3BlbmluZyByZXF1ZXN0IGZhaWxlZCBwb2xpY3kgdmFsaWRhdGlvbnMuCi8vLyA8L3N1bW1hcnk+CnB1YmxpYyBjbGFzcyBQb2xpY2llc0ZhaWxlZFJ1bGUgOiBJQ29uZGl0aW9uTWFwcGluZwp7CiAgICAvLy8gPHN1bW1hcnk+CiAgICAvLy8gQ2hlY2sgaWYgdGhlIHBvbGljeSB2YWxpZGF0aW9uIGZhaWxlZAogICAgLy8vIDwvc3VtbWFyeT4KICAgIHB1YmxpYyBhc3luYyBUYXNrPGJvb2w+IEhhbmRsZXIoU2NyaXB0Q29udGV4dCBjb250ZXh0KQogICAgewogICAgICAgIHRyeQogICAgICAgIHsKICAgICAgICAgICAgLy8gTnVsbCBjaGVjawogICAgICAgICAgICBpZiAoY29udGV4dD8uSW5zdGFuY2U/LkRhdGEgPT0gbnVsbCkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7IC8vIElmIG5vIGRhdGEsIGNvbnNpZGVyIGl0IGZhaWxlZAogICAgICAgICAgICB9CgogICAgICAgICAgICAvLyBQb2xpY3kgdmFsaWRhdGlvbiBkYXRhCiAgICAgICAgICAgIHZhciBwb2xpY3lWYWxpZGF0aW9uID0gY29udGV4dC5JbnN0YW5jZS5EYXRhLnBvbGljeVZhbGlkYXRpb247CiAgICAgICAgICAgIAogICAgICAgICAgICBpZiAocG9saWN5VmFsaWRhdGlvbiA9PSBudWxsKQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTsgLy8gSWYgbm8gcG9saWN5IHZhbGlkYXRpb24gZGF0YSwgY29uc2lkZXIgaXQgZmFpbGVkCiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIC8vIENoZWNrIGlmIHRoZSBwb2xpY3kgdmFsaWRhdGlvbiBmYWlsZWQKICAgICAgICAgICAgdmFyIGZhaWxlZCA9IHBvbGljeVZhbGlkYXRpb24ucGFzc2VkID09IGZhbHNlOwogICAgICAgICAgICAKICAgICAgICAgICAgLy8gQWRkaXRpb25hbCBmYWlsdXJlIGNoZWNrcwogICAgICAgICAgICBpZiAoIWZhaWxlZCkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgLy8gQ2hlY2sgY29tcGxpYW5jZSBmYWlsdXJlcwogICAgICAgICAgICAgICAgdmFyIGNvbXBsaWFuY2VDaGVja3MgPSBwb2xpY3lWYWxpZGF0aW9uLmNvbXBsaWFuY2VDaGVja3M7CiAgICAgICAgICAgICAgICBpZiAoY29tcGxpYW5jZUNoZWNrcyAhPSBudWxsKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIHZhciBreWNGYWlsZWQgPSBjb21wbGlhbmNlQ2hlY2tzLmt5Y0NvbXBsaWFudCA9PSBmYWxzZTsKICAgICAgICAgICAgICAgICAgICB2YXIgYW1sRmFpbGVkID0gY29tcGxpYW5jZUNoZWNrcy5hbWxDb21wbGlhbnQgPT0gZmFsc2U7CiAgICAgICAgICAgICAgICAgICAgdmFyIHNhbmN0aW9uc0ZhaWxlZCA9IGNvbXBsaWFuY2VDaGVja3Muc2FuY3Rpb25zQ2hlY2sgPT0gZmFsc2U7CiAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgZmFpbGVkID0ga3ljRmFpbGVkIHx8IGFtbEZhaWxlZCB8fCBzYW5jdGlvbnNGYWlsZWQ7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIC8vIENoZWNrIHZhbGlkYXRpb24gc2NvcmUgaWYgYXZhaWxhYmxlCiAgICAgICAgICAgICAgICB2YXIgdmFsaWRhdGlvblNjb3JlID0gcG9saWN5VmFsaWRhdGlvbi52YWxpZGF0aW9uU2NvcmU7CiAgICAgICAgICAgICAgICBpZiAodmFsaWRhdGlvblNjb3JlICE9IG51bGwpCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgZmFpbGVkID0gZmFpbGVkIHx8IHZhbGlkYXRpb25TY29yZSA8IDcwOyAvLyBCZWxvdyBtaW5pbXVtIHRocmVzaG9sZAogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAvLyBDaGVjayBmb3Igc3BlY2lmaWMgZXJyb3IgY29kZXMgdGhhdCBpbmRpY2F0ZSBmYWlsdXJlCiAgICAgICAgICAgICAgICB2YXIgZXJyb3JDb2RlID0gcG9saWN5VmFsaWRhdGlvbi5lcnJvckNvZGU7CiAgICAgICAgICAgICAgICBpZiAoIXN0cmluZy5Jc051bGxPckVtcHR5KGVycm9yQ29kZT8uVG9TdHJpbmcoKSkpCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgdmFyIGZhaWx1cmVDb2RlcyA9IG5ldyBbXSAKICAgICAgICAgICAgICAgICAgICB7IAogICAgICAgICAgICAgICAgICAgICAgICAicG9saWN5X3Zpb2xhdGlvbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAiY29tcGxpYW5jZV92aW9sYXRpb24iLCAKICAgICAgICAgICAgICAgICAgICAgICAgInJpc2tfYXNzZXNzbWVudF9mYWlsZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAia3ljX2ZhaWxlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICJhbWxfZmFpbGVkIiwKICAgICAgICAgICAgICAgICAgICAgICAgInNhbmN0aW9uc19jaGVja19mYWlsZWQiCiAgICAgICAgICAgICAgICAgICAgfTsKICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICBzdHJpbmcgY29kZSA9IENvbnZlcnQuVG9TdHJpbmcoZXJyb3JDb2RlKTsKICAgICAgICAgICAgICAgICAgICBmYWlsZWQgPSBmYWlsZWQgfHwgZmFpbHVyZUNvZGVzLkNvbnRhaW5zKGNvZGUsIFN0cmluZ0NvbXBhcmVyLk9yZGluYWxJZ25vcmVDYXNlKTsgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CgogICAgICAgICAgICByZXR1cm4gZmFpbGVkOwogICAgICAgIH0KICAgICAgICBjYXRjaCAoRXhjZXB0aW9uIGV4KQogICAgICAgIHsKICAgICAgICAgICAgLy8gSWYgYW4gZXhjZXB0aW9uIG9jY3VycywgbG9nIGl0IGFuZCByZXR1cm4gdHJ1ZSAoZmFpbGVkKQogICAgICAgICAgICBpZiAoY29udGV4dD8uTWV0YURhdGEgIT0gbnVsbCkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgY29udGV4dC5NZXRhRGF0YVsicG9saWNpZXNfZmFpbGVkX3J1bGVfZXJyb3IiXSA9IG5ldwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIGVycm9yID0gZXguTWVzc2FnZSwKICAgICAgICAgICAgICAgICAgICB0eXBlID0gZXguR2V0VHlwZSgpLk5hbWUsCiAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wID0gRGF0ZVRpbWUuVXRjTm93CiAgICAgICAgICAgICAgICB9OwogICAgICAgICAgICB9CgogICAgICAgICAgICByZXR1cm4gdHJ1ZTsgLy8gRXhjZXB0aW9uIG1lYW5zIGZhaWx1cmUKICAgICAgICB9CiAgICB9Cn0K" }, "timer": null, "view": null, @@ -351,6 +373,7 @@ { "key": "account-creation", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -432,7 +455,7 @@ "schema": null, "rule": { "location": "./src/AccountCreationFailedRule.csx", - "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vLyA8c3VtbWFyeT4KLy8vIEFjY291bnQgQ3JlYXRpb24gRmFpbGVkIFJ1bGUgLSBDaGVja3MgaWYgYWNjb3VudCBjcmVhdGlvbiBmYWlsZWQKLy8vIFRoaXMgcnVsZSBkZXRlcm1pbmVzIGlmIHRoZSBiYW5rIGFjY291bnQgY3JlYXRpb24gZmFpbGVkIGluIHRoZSBjb3JlIGJhbmtpbmcgc3lzdGVtLgovLy8gPC9zdW1tYXJ5PgpwdWJsaWMgY2xhc3MgQWNjb3VudENyZWF0aW9uRmFpbGVkUnVsZSA6IElDb25kaXRpb25NYXBwaW5nCnsKICAgIC8vLyA8c3VtbWFyeT4KICAgIC8vLyBDaGVjayBpZiB0aGUgYWNjb3VudCBjcmVhdGlvbiBmYWlsZWQKICAgIC8vLyA8L3N1bW1hcnk+CiAgICBwdWJsaWMgYXN5bmMgVGFzazxib29sPiBIYW5kbGVyKFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICB0cnkKICAgICAgICB7CiAgICAgICAgICAgIC8vIE51bGwgY2hlY2sKICAgICAgICAgICAgaWYgKGNvbnRleHQ/Lkluc3RhbmNlPy5EYXRhID09IG51bGwpCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIHJldHVybiB0cnVlOyAvLyBJZiBubyBkYXRhLCBjb25zaWRlciBpdCBmYWlsZWQKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy8gQWNjb3VudCBjcmVhdGlvbiBkYXRhCiAgICAgICAgICAgIHZhciBhY2NvdW50Q3JlYXRpb24gPSBjb250ZXh0Lkluc3RhbmNlLkRhdGEuYWNjb3VudENyZWF0aW9uOwogICAgICAgICAgICAKICAgICAgICAgICAgaWYgKGFjY291bnRDcmVhdGlvbiA9PSBudWxsKQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTsgLy8gSWYgbm8gYWNjb3VudCBjcmVhdGlvbiBkYXRhLCBjb25zaWRlciBpdCBmYWlsZWQKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy8gQ2hlY2sgaWYgdGhlIGFjY291bnQgY3JlYXRpb24gZmFpbGVkCiAgICAgICAgICAgIHZhciBmYWlsZWQgPSBhY2NvdW50Q3JlYXRpb24uc3VjY2VzcyA9PSBmYWxzZTsKICAgICAgICAgICAgCiAgICAgICAgICAgIC8vIEFkZGl0aW9uYWwgZmFpbHVyZSBjaGVja3MKICAgICAgICAgICAgaWYgKCFmYWlsZWQpCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIC8vIENoZWNrIGZvciBtaXNzaW5nIGVzc2VudGlhbCBkYXRhIGV2ZW4gaWYgbWFya2VkIGFzIHN1Y2Nlc3MKICAgICAgICAgICAgICAgIHZhciBhY2NvdW50SWQgPSBhY2NvdW50Q3JlYXRpb24uYWNjb3VudElkOwogICAgICAgICAgICAgICAgdmFyIGFjY291bnROdW1iZXIgPSBhY2NvdW50Q3JlYXRpb24uYWNjb3VudE51bWJlcjsKICAgICAgICAgICAgICAgIHZhciBzdGF0dXMgPSBhY2NvdW50Q3JlYXRpb24uc3RhdHVzOwogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAvLyBNaXNzaW5nIGVzc2VudGlhbCBhY2NvdW50IGRldGFpbHMgaW5kaWNhdGVzIGZhaWx1cmUKICAgICAgICAgICAgICAgIGlmIChzdHJpbmcuSXNOdWxsT3JFbXB0eShhY2NvdW50SWQ/LlRvU3RyaW5nKCkpIHx8IAogICAgICAgICAgICAgICAgICAgIHN0cmluZy5Jc051bGxPckVtcHR5KGFjY291bnROdW1iZXI/LlRvU3RyaW5nKCkpKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIGZhaWxlZCA9IHRydWU7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIC8vIE5vbi1hY3RpdmUgc3RhdHVzIGluZGljYXRlcyBmYWlsdXJlCiAgICAgICAgICAgICAgICBpZiAoc3RhdHVzPy5Ub1N0cmluZygpICE9ICJhY3RpdmUiKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIGZhaWxlZCA9IHRydWU7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIC8vIENoZWNrIGlmIGFjY291bnQgaXMgbWFya2VkIGFzIGluYWN0aXZlCiAgICAgICAgICAgICAgICB2YXIgaXNBY3RpdmUgPSBhY2NvdW50Q3JlYXRpb24uaXNBY3RpdmU7CiAgICAgICAgICAgICAgICBpZiAoaXNBY3RpdmUgPT0gZmFsc2UpCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgZmFpbGVkID0gdHJ1ZTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgLy8gQ2hlY2sgZm9yIHNwZWNpZmljIGVycm9yIGluZGljYXRvcnMKICAgICAgICAgICAgICAgIHZhciBlcnJvckNvZGUgPSBhY2NvdW50Q3JlYXRpb24uZXJyb3JDb2RlOwogICAgICAgICAgICAgICAgaWYgKCFzdHJpbmcuSXNOdWxsT3JFbXB0eShlcnJvckNvZGU/LlRvU3RyaW5nKCkpKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIHZhciBmYWlsdXJlQ29kZXMgPSBuZXdbXSAKICAgICAgICAgICAgICAgICAgICB7IAogICAgICAgICAgICAgICAgICAgICAgICAiYWNjb3VudF9jcmVhdGlvbl9lcnJvciIsIAogICAgICAgICAgICAgICAgICAgICAgICAiY29yZV9iYW5raW5nX2Vycm9yIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnZhbGlkX2FjY291bnRfZGF0YSIsCiAgICAgICAgICAgICAgICAgICAgICAgICJhY2NvdW50X2FscmVhZHlfZXhpc3RzIiwKICAgICAgICAgICAgICAgICAgICAgICAgInZhbGlkYXRpb25fZXJyb3IiLAogICAgICAgICAgICAgICAgICAgICAgICAic3lzdGVtX2Vycm9yIiwKICAgICAgICAgICAgICAgICAgICAgICAgInByb2Nlc3NpbmdfZXJyb3IiCiAgICAgICAgICAgICAgICAgICAgfTsKICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICBmYWlsZWQgPSBmYWlsZWQgfHwgZmFpbHVyZUNvZGVzLkNvbnRhaW5zKGVycm9yQ29kZS5Ub1N0cmluZygpKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgLy8gQ2hlY2sgZmFpbHVyZSBjYXRlZ29yeQogICAgICAgICAgICAgICAgdmFyIGZhaWx1cmVDYXRlZ29yeSA9IGFjY291bnRDcmVhdGlvbi5mYWlsdXJlQ2F0ZWdvcnk7CiAgICAgICAgICAgICAgICBpZiAoIXN0cmluZy5Jc051bGxPckVtcHR5KGZhaWx1cmVDYXRlZ29yeT8uVG9TdHJpbmcoKSkpCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgdmFyIGNyaXRpY2FsQ2F0ZWdvcmllcyA9IG5ld1tdIAogICAgICAgICAgICAgICAgICAgIHsgCiAgICAgICAgICAgICAgICAgICAgICAgICJzeXN0ZW1fZXJyb3IiLCAKICAgICAgICAgICAgICAgICAgICAgICAgInZhbGlkYXRpb25fZXJyb3IiLCAKICAgICAgICAgICAgICAgICAgICAgICAgImJ1c2luZXNzX3J1bGVfdmlvbGF0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgImNvcmVfYmFua2luZ19mYWlsdXJlIgogICAgICAgICAgICAgICAgICAgIH07CiAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgZmFpbGVkID0gZmFpbGVkIHx8IGNyaXRpY2FsQ2F0ZWdvcmllcy5Db250YWlucyhmYWlsdXJlQ2F0ZWdvcnkuVG9TdHJpbmcoKSk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIHJldHVybiBmYWlsZWQ7CiAgICAgICAgfQogICAgICAgIGNhdGNoIChFeGNlcHRpb24gZXgpCiAgICAgICAgewogICAgICAgICAgICAvLyBJZiBhbiBleGNlcHRpb24gb2NjdXJzLCBsb2cgaXQgYW5kIHJldHVybiB0cnVlIChmYWlsZWQpCiAgICAgICAgICAgIGlmIChjb250ZXh0Py5NZXRhRGF0YSAhPSBudWxsKQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICBjb250ZXh0Lk1ldGFEYXRhWyJhY2NvdW50X2NyZWF0aW9uX2ZhaWxlZF9ydWxlX2Vycm9yIl0gPSBuZXcKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICBlcnJvciA9IGV4Lk1lc3NhZ2UsCiAgICAgICAgICAgICAgICAgICAgdHlwZSA9IGV4LkdldFR5cGUoKS5OYW1lLAogICAgICAgICAgICAgICAgICAgIHRpbWVzdGFtcCA9IERhdGVUaW1lLlV0Y05vdwogICAgICAgICAgICAgICAgfTsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgcmV0dXJuIHRydWU7IC8vIEV4Y2VwdGlvbiBtZWFucyBmYWlsdXJlCiAgICAgICAgfQogICAgfQp9Cg==" + "code": "dXNpbmcgU3lzdGVtLlRocmVhZGluZy5UYXNrczsKdXNpbmcgQkJULldvcmtmbG93LlNjcmlwdGluZzsKCi8vLyA8c3VtbWFyeT4KLy8vIEFjY291bnQgQ3JlYXRpb24gRmFpbGVkIFJ1bGUgLSBDaGVja3MgaWYgYWNjb3VudCBjcmVhdGlvbiBmYWlsZWQKLy8vIFRoaXMgcnVsZSBkZXRlcm1pbmVzIGlmIHRoZSBiYW5rIGFjY291bnQgY3JlYXRpb24gZmFpbGVkIGluIHRoZSBjb3JlIGJhbmtpbmcgc3lzdGVtLgovLy8gPC9zdW1tYXJ5PgpwdWJsaWMgY2xhc3MgQWNjb3VudENyZWF0aW9uRmFpbGVkUnVsZSA6IElDb25kaXRpb25NYXBwaW5nCnsKICAgIC8vLyA8c3VtbWFyeT4KICAgIC8vLyBDaGVjayBpZiB0aGUgYWNjb3VudCBjcmVhdGlvbiBmYWlsZWQKICAgIC8vLyA8L3N1bW1hcnk+CiAgICBwdWJsaWMgYXN5bmMgVGFzazxib29sPiBIYW5kbGVyKFNjcmlwdENvbnRleHQgY29udGV4dCkKICAgIHsKICAgICAgICB0cnkKICAgICAgICB7CiAgICAgICAgICAgIC8vIE51bGwgY2hlY2sKICAgICAgICAgICAgaWYgKGNvbnRleHQ/Lkluc3RhbmNlPy5EYXRhID09IG51bGwpCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIHJldHVybiB0cnVlOyAvLyBJZiBubyBkYXRhLCBjb25zaWRlciBpdCBmYWlsZWQKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy8gQWNjb3VudCBjcmVhdGlvbiBkYXRhCiAgICAgICAgICAgIHZhciBhY2NvdW50Q3JlYXRpb24gPSBjb250ZXh0Lkluc3RhbmNlLkRhdGEuYWNjb3VudENyZWF0aW9uOwogICAgICAgICAgICAKICAgICAgICAgICAgaWYgKGFjY291bnRDcmVhdGlvbiA9PSBudWxsKQogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTsgLy8gSWYgbm8gYWNjb3VudCBjcmVhdGlvbiBkYXRhLCBjb25zaWRlciBpdCBmYWlsZWQKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy8gQ2hlY2sgaWYgdGhlIGFjY291bnQgY3JlYXRpb24gZmFpbGVkCiAgICAgICAgICAgIHZhciBmYWlsZWQgPSBhY2NvdW50Q3JlYXRpb24uc3VjY2VzcyA9PSBmYWxzZTsKICAgICAgICAgICAgCiAgICAgICAgICAgIC8vIEFkZGl0aW9uYWwgZmFpbHVyZSBjaGVja3MKICAgICAgICAgICAgaWYgKCFmYWlsZWQpCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIC8vIENoZWNrIGZvciBtaXNzaW5nIGVzc2VudGlhbCBkYXRhIGV2ZW4gaWYgbWFya2VkIGFzIHN1Y2Nlc3MKICAgICAgICAgICAgICAgIHZhciBhY2NvdW50SWQgPSBhY2NvdW50Q3JlYXRpb24uYWNjb3VudElkOwogICAgICAgICAgICAgICAgdmFyIGFjY291bnROdW1iZXIgPSBhY2NvdW50Q3JlYXRpb24uYWNjb3VudE51bWJlcjsKICAgICAgICAgICAgICAgIHZhciBzdGF0dXMgPSBhY2NvdW50Q3JlYXRpb24uc3RhdHVzOwogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAvLyBNaXNzaW5nIGVzc2VudGlhbCBhY2NvdW50IGRldGFpbHMgaW5kaWNhdGVzIGZhaWx1cmUKICAgICAgICAgICAgICAgIGlmIChzdHJpbmcuSXNOdWxsT3JFbXB0eShhY2NvdW50SWQ/LlRvU3RyaW5nKCkpIHx8IAogICAgICAgICAgICAgICAgICAgIHN0cmluZy5Jc051bGxPckVtcHR5KGFjY291bnROdW1iZXI/LlRvU3RyaW5nKCkpKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIGZhaWxlZCA9IHRydWU7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIC8vIE5vbi1hY3RpdmUgc3RhdHVzIGluZGljYXRlcyBmYWlsdXJlCiAgICAgICAgICAgICAgICBpZiAoc3RhdHVzPy5Ub1N0cmluZygpICE9ICJhY3RpdmUiKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIGZhaWxlZCA9IHRydWU7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIC8vIENoZWNrIGlmIGFjY291bnQgaXMgbWFya2VkIGFzIGluYWN0aXZlCiAgICAgICAgICAgICAgICB2YXIgaXNBY3RpdmUgPSBhY2NvdW50Q3JlYXRpb24uaXNBY3RpdmU7CiAgICAgICAgICAgICAgICBpZiAoaXNBY3RpdmUgPT0gZmFsc2UpCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgZmFpbGVkID0gdHJ1ZTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgLy8gQ2hlY2sgZm9yIHNwZWNpZmljIGVycm9yIGluZGljYXRvcnMKICAgICAgICAgICAgICAgIHZhciBlcnJvckNvZGUgPSBhY2NvdW50Q3JlYXRpb24uZXJyb3JDb2RlOwogICAgICAgICAgICAgICAgaWYgKCFzdHJpbmcuSXNOdWxsT3JFbXB0eShlcnJvckNvZGU/LlRvU3RyaW5nKCkpKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIHZhciBmYWlsdXJlQ29kZXMgPSBuZXdbXSAKICAgICAgICAgICAgICAgICAgICB7IAogICAgICAgICAgICAgICAgICAgICAgICAiYWNjb3VudF9jcmVhdGlvbl9lcnJvciIsIAogICAgICAgICAgICAgICAgICAgICAgICAiY29yZV9iYW5raW5nX2Vycm9yIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJpbnZhbGlkX2FjY291bnRfZGF0YSIsCiAgICAgICAgICAgICAgICAgICAgICAgICJhY2NvdW50X2FscmVhZHlfZXhpc3RzIiwKICAgICAgICAgICAgICAgICAgICAgICAgInZhbGlkYXRpb25fZXJyb3IiLAogICAgICAgICAgICAgICAgICAgICAgICAic3lzdGVtX2Vycm9yIiwKICAgICAgICAgICAgICAgICAgICAgICAgInByb2Nlc3NpbmdfZXJyb3IiCiAgICAgICAgICAgICAgICAgICAgfTsKICAgICAgICAgICAgICAgICAgICBzdHJpbmcgY29kZSA9IENvbnZlcnQuVG9TdHJpbmcoZXJyb3JDb2RlKTsKICAgICAgICAgICAgICAgICAgICBmYWlsZWQgPSBmYWlsZWQgfHwgZmFpbHVyZUNvZGVzLkNvbnRhaW5zKGNvZGUsIFN0cmluZ0NvbXBhcmVyLk9yZGluYWxJZ25vcmVDYXNlKTsgCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIC8vIENoZWNrIGZhaWx1cmUgY2F0ZWdvcnkKICAgICAgICAgICAgICAgIHZhciBmYWlsdXJlQ2F0ZWdvcnkgPSBhY2NvdW50Q3JlYXRpb24uZmFpbHVyZUNhdGVnb3J5OwogICAgICAgICAgICAgICAgaWYgKCFzdHJpbmcuSXNOdWxsT3JFbXB0eShmYWlsdXJlQ2F0ZWdvcnk/LlRvU3RyaW5nKCkpKQogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIHZhciBjcml0aWNhbENhdGVnb3JpZXMgPSBuZXdbXSAKICAgICAgICAgICAgICAgICAgICB7IAogICAgICAgICAgICAgICAgICAgICAgICAic3lzdGVtX2Vycm9yIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJ2YWxpZGF0aW9uX2Vycm9yIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJidXNpbmVzc19ydWxlX3Zpb2xhdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICJjb3JlX2JhbmtpbmdfZmFpbHVyZSIKICAgICAgICAgICAgICAgICAgICB9OwogICAgICAgICAgICAgICAgICAgIHN0cmluZyBjb2RlID0gQ29udmVydC5Ub1N0cmluZyhlcnJvckNvZGUpOwogICAgICAgICAgICAgICAgICAgIGZhaWxlZCA9IGZhaWxlZCB8fCBjcml0aWNhbENhdGVnb3JpZXMuQ29udGFpbnMoY29kZSwgU3RyaW5nQ29tcGFyZXIuT3JkaW5hbElnbm9yZUNhc2UpOyAKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQoKICAgICAgICAgICAgcmV0dXJuIGZhaWxlZDsKICAgICAgICB9CiAgICAgICAgY2F0Y2ggKEV4Y2VwdGlvbiBleCkKICAgICAgICB7CiAgICAgICAgICAgIC8vIElmIGFuIGV4Y2VwdGlvbiBvY2N1cnMsIGxvZyBpdCBhbmQgcmV0dXJuIHRydWUgKGZhaWxlZCkKICAgICAgICAgICAgaWYgKGNvbnRleHQ/Lk1ldGFEYXRhICE9IG51bGwpCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgIGNvbnRleHQuTWV0YURhdGFbImFjY291bnRfY3JlYXRpb25fZmFpbGVkX3J1bGVfZXJyb3IiXSA9IG5ldwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgIGVycm9yID0gZXguTWVzc2FnZSwKICAgICAgICAgICAgICAgICAgICB0eXBlID0gZXguR2V0VHlwZSgpLk5hbWUsCiAgICAgICAgICAgICAgICAgICAgdGltZXN0YW1wID0gRGF0ZVRpbWUuVXRjTm93CiAgICAgICAgICAgICAgICB9OwogICAgICAgICAgICB9CgogICAgICAgICAgICByZXR1cm4gdHJ1ZTsgLy8gRXhjZXB0aW9uIG1lYW5zIGZhaWx1cmUKICAgICAgICB9CiAgICB9Cn0K" }, "timer": null, "view": null, @@ -443,6 +466,7 @@ { "key": "account-opening-success", "stateType": 3, + "subType": 1, "versionStrategy": "Minor", "labels": [ { @@ -471,6 +495,7 @@ { "key": "cancelled", "stateType": 3, + "subType": 3, "versionStrategy": "Minor", "labels": [ { diff --git a/core/Workflows/account-opening/src/AccountCreationFailedRule.csx b/core/Workflows/account-opening/src/AccountCreationFailedRule.csx index 6ad3359..3e76cc9 100644 --- a/core/Workflows/account-opening/src/AccountCreationFailedRule.csx +++ b/core/Workflows/account-opening/src/AccountCreationFailedRule.csx @@ -73,8 +73,8 @@ public class AccountCreationFailedRule : IConditionMapping "system_error", "processing_error" }; - - failed = failed || failureCodes.Contains(errorCode.ToString()); + string code = Convert.ToString(errorCode); + failed = failed || failureCodes.Contains(code, StringComparer.OrdinalIgnoreCase); } // Check failure category @@ -88,8 +88,8 @@ public class AccountCreationFailedRule : IConditionMapping "business_rule_violation", "core_banking_failure" }; - - failed = failed || criticalCategories.Contains(failureCategory.ToString()); + string code = Convert.ToString(errorCode); + failed = failed || criticalCategories.Contains(code, StringComparer.OrdinalIgnoreCase); } } diff --git a/core/Workflows/account-opening/src/InitialTransMapping.csx b/core/Workflows/account-opening/src/InitialTransMapping.csx index 99fcbe2..709e0c0 100644 --- a/core/Workflows/account-opening/src/InitialTransMapping.csx +++ b/core/Workflows/account-opening/src/InitialTransMapping.csx @@ -15,6 +15,7 @@ public class InitialTransMapping : ITransitionMapping session = context.Body.session, requestId = context.Headers?["x-request-id"] ?? Guid.NewGuid().ToString(), }; + return output; } diff --git a/core/Workflows/account-opening/src/PoliciesFailedRule.csx b/core/Workflows/account-opening/src/PoliciesFailedRule.csx index 544e785..3bb887e 100644 --- a/core/Workflows/account-opening/src/PoliciesFailedRule.csx +++ b/core/Workflows/account-opening/src/PoliciesFailedRule.csx @@ -56,7 +56,7 @@ public class PoliciesFailedRule : IConditionMapping var errorCode = policyValidation.errorCode; if (!string.IsNullOrEmpty(errorCode?.ToString())) { - var failureCodes = new[] + var failureCodes = new [] { "policy_violation", "compliance_violation", @@ -66,7 +66,8 @@ public class PoliciesFailedRule : IConditionMapping "sanctions_check_failed" }; - failed = failed || failureCodes.Contains(errorCode.ToString()); + string code = Convert.ToString(errorCode); + failed = failed || failureCodes.Contains(code, StringComparer.OrdinalIgnoreCase); } } diff --git a/core/Workflows/oauth/.meta/oauth-authentication-workflow.diagram.json b/core/Workflows/oauth/.meta/oauth-authentication-workflow.diagram.json deleted file mode 100644 index aa5b032..0000000 --- a/core/Workflows/oauth/.meta/oauth-authentication-workflow.diagram.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "nodePos": { - "client-validation": { - "x": -22, - "y": 580.5 - }, - "grant-type-selection": { - "x": 549, - "y": 240.5 - }, - "password-auth": { - "x": 1255, - "y": 100 - }, - "authorization-code-auth": { - "x": 1025, - "y": 300 - }, - "mfa-check": { - "x": 1618, - "y": 56 - }, - "push-notification-mfa": { - "x": 2768, - "y": 440.5 - }, - "otp-mfa": { - "x": 2219, - "y": -183.5 - }, - "device-registration": { - "x": 2786, - "y": 240.5 - }, - "token-generation": { - "x": 3253, - "y": 340.5 - }, - "authentication-success": { - "x": 3693, - "y": 421 - }, - "authentication-failed": { - "x": 3553, - "y": 100 - }, - "__start__": { - "x": -296, - "y": 280.5 - } - } -} \ No newline at end of file diff --git a/core/Workflows/oauth/.meta/password-subflow.diagram.json b/core/Workflows/oauth/.meta/password-subflow.diagram.json new file mode 100644 index 0000000..89868f0 --- /dev/null +++ b/core/Workflows/oauth/.meta/password-subflow.diagram.json @@ -0,0 +1,20 @@ +{ + "nodePos": { + "validate-user-credentials": { + "x": 100, + "y": 200 + }, + "password-success": { + "x": 621, + "y": 100 + }, + "password-failed": { + "x": 621, + "y": 300 + }, + "__start__": { + "x": -296, + "y": 200 + } + } +} \ No newline at end of file diff --git a/core/Workflows/oauth/authorization-code-subflow.json b/core/Workflows/oauth/authorization-code-subflow.json index 3dead3c..ef2b1d8 100644 --- a/core/Workflows/oauth/authorization-code-subflow.json +++ b/core/Workflows/oauth/authorization-code-subflow.json @@ -48,6 +48,7 @@ { "key": "validate-authorization-code", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -131,6 +132,7 @@ { "key": "authorization-code-success", "stateType": 3, + "subType": 1, "versionStrategy": "Minor", "labels": [ { @@ -151,6 +153,7 @@ { "key": "authorization-code-failed", "stateType": 3, + "subType": 2, "versionStrategy": "Minor", "labels": [ { diff --git a/core/Workflows/oauth/oauth-authentication-workflow.json b/core/Workflows/oauth/oauth-authentication-workflow.json index 449fd9f..50f5a7d 100644 --- a/core/Workflows/oauth/oauth-authentication-workflow.json +++ b/core/Workflows/oauth/oauth-authentication-workflow.json @@ -48,6 +48,7 @@ { "key": "client-validation", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -144,6 +145,7 @@ { "key": "grant-type-selection", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -237,6 +239,7 @@ { "key": "password-auth", "stateType": 4, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -318,6 +321,7 @@ { "key": "authorization-code-auth", "stateType": 4, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -399,6 +403,7 @@ { "key": "mfa-check", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -482,6 +487,7 @@ { "key": "push-notification-mfa", "stateType": 4, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -563,6 +569,7 @@ { "key": "otp-mfa", "stateType": 4, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -644,6 +651,7 @@ { "key": "device-registration", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -703,6 +711,7 @@ { "key": "token-generation", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -786,6 +795,7 @@ { "key": "authentication-success", "stateType": 3, + "subType": 1, "versionStrategy": "Minor", "labels": [ { @@ -806,6 +816,7 @@ { "key": "authentication-failed", "stateType": 3, + "subType": 2, "versionStrategy": "Minor", "labels": [ { diff --git a/core/Workflows/oauth/otp-mfa-subflow.json b/core/Workflows/oauth/otp-mfa-subflow.json index 1efe227..f5f1e5a 100644 --- a/core/Workflows/oauth/otp-mfa-subflow.json +++ b/core/Workflows/oauth/otp-mfa-subflow.json @@ -56,6 +56,7 @@ { "key": "send-otp", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -139,6 +140,7 @@ { "key": "submit-otp", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -195,6 +197,7 @@ { "key": "verify-otp", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -288,6 +291,7 @@ { "key": "otp-success", "stateType": 3, + "subType": 1, "versionStrategy": "Minor", "labels": [ { @@ -308,6 +312,7 @@ { "key": "otp-failed", "stateType": 3, + "subType": 2, "versionStrategy": "Minor", "labels": [ { @@ -328,6 +333,7 @@ { "key": "otp-expired", "stateType": 3, + "subType": 3, "versionStrategy": "Minor", "labels": [ { diff --git a/core/Workflows/oauth/password-subflow.json b/core/Workflows/oauth/password-subflow.json index 4a9356f..4ec4fe5 100644 --- a/core/Workflows/oauth/password-subflow.json +++ b/core/Workflows/oauth/password-subflow.json @@ -48,6 +48,7 @@ { "key": "validate-user-credentials", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -131,6 +132,7 @@ { "key": "password-success", "stateType": 3, + "subType": 1, "versionStrategy": "Minor", "labels": [ { @@ -151,6 +153,7 @@ { "key": "password-failed", "stateType": 3, + "subType": 2, "versionStrategy": "Minor", "labels": [ { diff --git a/core/Workflows/oauth/push-notification-mfa-subflow.json b/core/Workflows/oauth/push-notification-mfa-subflow.json index 3ade79b..7916618 100644 --- a/core/Workflows/oauth/push-notification-mfa-subflow.json +++ b/core/Workflows/oauth/push-notification-mfa-subflow.json @@ -56,6 +56,7 @@ { "key": "send-push-notification", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -139,6 +140,7 @@ { "key": "wait-for-approval", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -230,6 +232,7 @@ { "key": "push-success", "stateType": 3, + "subType": 1, "versionStrategy": "Minor", "labels": [ { @@ -250,6 +253,7 @@ { "key": "push-failed", "stateType": 3, + "subType": 2, "versionStrategy": "Minor", "labels": [ { @@ -270,6 +274,7 @@ { "key": "push-expired", "stateType": 3, + "subType": 3, "versionStrategy": "Minor", "labels": [ { diff --git a/core/Workflows/payments/payment-notification-subflow.json b/core/Workflows/payments/payment-notification-subflow.json index 6999df7..f290937 100644 --- a/core/Workflows/payments/payment-notification-subflow.json +++ b/core/Workflows/payments/payment-notification-subflow.json @@ -48,6 +48,7 @@ { "key": "get-user-info", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -107,6 +108,7 @@ { "key": "send-notifications", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -179,6 +181,7 @@ { "key": "notification-complete", "stateType": 3, + "subType": 1, "versionStrategy": "Minor", "labels": [ { diff --git a/core/Workflows/payments/payment-process.json b/core/Workflows/payments/payment-process.json index 603a79e..411c091 100644 --- a/core/Workflows/payments/payment-process.json +++ b/core/Workflows/payments/payment-process.json @@ -48,6 +48,7 @@ { "key": "payment-pending", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -114,6 +115,7 @@ { "key": "process-payment", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -197,6 +199,7 @@ { "key": "payment-failed-state", "stateType": 2, + "subType": 2, "versionStrategy": "Minor", "labels": [ { @@ -311,6 +314,7 @@ { "key": "payment-retry", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -370,6 +374,7 @@ { "key": "payment-success-state", "stateType": 3, + "subType": 1, "versionStrategy": "Minor", "labels": [ { @@ -390,6 +395,7 @@ { "key": "payment-cancelled", "stateType": 3, + "subType": 3, "versionStrategy": "Minor", "labels": [ { diff --git a/core/Workflows/payments/scheduled-payments-workflow.json b/core/Workflows/payments/scheduled-payments-workflow.json index ad307c9..e43b8ef 100644 --- a/core/Workflows/payments/scheduled-payments-workflow.json +++ b/core/Workflows/payments/scheduled-payments-workflow.json @@ -55,6 +55,7 @@ { "key": "payment-configuration", "stateType": 1, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -138,6 +139,7 @@ { "key": "payment-active", "stateType": 4, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -254,6 +256,7 @@ { "key": "payment-deactive", "stateType": 2, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -352,6 +355,7 @@ { "key": "payment-cycle-check", "stateType": 4, + "subType": 0, "versionStrategy": "Minor", "labels": [ { @@ -409,6 +413,7 @@ { "key": "payment-finished", "stateType": 3, + "subType": 1, "versionStrategy": "Minor", "labels": [ { @@ -443,6 +448,7 @@ { "key": "payment-terminated", "stateType": 3, + "subType": 3, "versionStrategy": "Minor", "labels": [ { diff --git a/index.js b/index.js index 37728a5..91c114f 100644 --- a/index.js +++ b/index.js @@ -1,24 +1,38 @@ const fs = require('fs'); const path = require('path'); -// Find the domain directory dynamically -function findDomainDirectory() { - const entries = fs.readdirSync('.', { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory() && - !entry.name.startsWith('.') && - entry.name !== 'node_modules' && - entry.name !== 'dist') { - // Check if it contains typical vnext structure - const domainPath = entry.name; - if (fs.existsSync(path.join(domainPath, 'Schemas')) || - fs.existsSync(path.join(domainPath, 'Workflows')) || - fs.existsSync(path.join(domainPath, 'Tasks'))) { - return domainPath; - } - } +// Load configuration from vnext.config.json +function loadConfig() { + try { + return JSON.parse(fs.readFileSync('vnext.config.json', 'utf8')); + } catch (error) { + return null; + } +} + +// Get paths configuration with defaults +function getPathsConfig() { + const config = loadConfig(); + const defaults = { + componentsRoot: 'core', + schemas: 'Schemas', + workflows: 'Workflows', + tasks: 'Tasks', + views: 'Views', + functions: 'Functions', + extensions: 'Extensions' + }; + + if (config && config.paths) { + return { ...defaults, ...config.paths }; } - return null; + return defaults; +} + +// Find the domain directory from config +function findDomainDirectory() { + const pathsConfig = getPathsConfig(); + return pathsConfig.componentsRoot; } // Load JSON files from a directory @@ -30,6 +44,11 @@ function loadJsonFiles(dirPath) { const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { + // Skip .meta directory + if (entry.name === '.meta') { + continue; + } + if (entry.isFile() && entry.name.endsWith('.json')) { try { const filePath = path.join(dirPath, entry.name); @@ -48,62 +67,82 @@ function loadJsonFiles(dirPath) { module.exports = { // Get the domain configuration getDomainConfig: function() { - try { - return JSON.parse(fs.readFileSync('vnext.config.json', 'utf8')); - } catch (error) { - return null; - } + return getPathsConfig(); + }, + + // Get paths configuration + getPathsConfig: function() { + return getPathsConfig(); }, // Get all schemas getSchemas: function() { const domainDir = findDomainDirectory(); if (!domainDir) return {}; - return loadJsonFiles(path.join(domainDir, 'Schemas')); + const pathsConfig = getPathsConfig(); + return loadJsonFiles(path.join(domainDir, pathsConfig.schemas)); }, // Get all workflows getWorkflows: function() { const domainDir = findDomainDirectory(); if (!domainDir) return {}; - return loadJsonFiles(path.join(domainDir, 'Workflows')); + const pathsConfig = getPathsConfig(); + return loadJsonFiles(path.join(domainDir, pathsConfig.workflows)); }, // Get all tasks getTasks: function() { const domainDir = findDomainDirectory(); if (!domainDir) return {}; - return loadJsonFiles(path.join(domainDir, 'Tasks')); + const pathsConfig = getPathsConfig(); + return loadJsonFiles(path.join(domainDir, pathsConfig.tasks)); }, // Get all views getViews: function() { const domainDir = findDomainDirectory(); if (!domainDir) return {}; - return loadJsonFiles(path.join(domainDir, 'Views')); + const pathsConfig = getPathsConfig(); + return loadJsonFiles(path.join(domainDir, pathsConfig.views)); }, // Get all functions getFunctions: function() { const domainDir = findDomainDirectory(); if (!domainDir) return {}; - return loadJsonFiles(path.join(domainDir, 'Functions')); + const pathsConfig = getPathsConfig(); + return loadJsonFiles(path.join(domainDir, pathsConfig.functions)); }, // Get all extensions getExtensions: function() { const domainDir = findDomainDirectory(); if (!domainDir) return {}; - return loadJsonFiles(path.join(domainDir, 'Extensions')); + const pathsConfig = getPathsConfig(); + return loadJsonFiles(path.join(domainDir, pathsConfig.extensions)); }, // Get available component types getAvailableTypes: function() { - return ['schemas', 'workflows', 'tasks', 'views', 'functions', 'extensions']; + const pathsConfig = getPathsConfig(); + return [pathsConfig.schemas, pathsConfig.workflows, pathsConfig.tasks, pathsConfig.views, pathsConfig.functions, pathsConfig.extensions]; }, // Get domain directory name getDomainName: function() { return findDomainDirectory(); + }, + + // Get component path for a specific type + getComponentPath: function(componentType) { + const domainDir = findDomainDirectory(); + if (!domainDir) return null; + const pathsConfig = getPathsConfig(); + const pathKey = componentType.toLowerCase(); + if (pathsConfig[pathKey]) { + return path.join(domainDir, pathsConfig[pathKey]); + } + return null; } }; diff --git a/mockoon/banking-api-mocks.json b/mockoon/banking-api-mocks.json deleted file mode 100644 index 8c09d84..0000000 --- a/mockoon/banking-api-mocks.json +++ /dev/null @@ -1,271 +0,0 @@ -{ - "uuid": "242003bc-c853-4710-982e-722d644ac9f6", - "lastMigration": 33, - "name": "Banking API Mocks", - "endpointPrefix": "", - "latency": 0, - "port": 3001, - "hostname": "localhost", - "folders": [], - "routes": [ - { - "uuid": "1667a100-73a2-4bbf-a9ab-ef607d05e428", - "type": "http", - "documentation": "Validates user session for banking operations", - "method": "post", - "endpoint": "api/banking/user/validate-session", - "responses": [ - { - "uuid": "67973c31-b471-47b4-a0a5-5aa24568abda", - "body": "{\n \"statusCode\": 200,\n \"data\": {\n \"userId\": \"{{faker 'string.uuid'}}\",\n \"userEmail\": \"{{faker 'internet.email'}}\",\n \"userRole\": \"customer\",\n \"permissions\": [\"account-opening\", \"view-accounts\", \"transfer-money\"],\n \"sessionExpiresAt\": \"{{faker 'date.future'}}\",\n \"customerSegment\": \"retail\",\n \"branchCode\": \"{{faker 'string.numeric' 4}}\",\n \"customerSince\": \"{{faker 'date.past'}}\"\n }\n}", - "latency": 0, - "statusCode": 200, - "label": "Session Valid", - "headers": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "bodyType": "INLINE", - "filePath": "", - "databucketID": "", - "sendFileAsBody": false, - "rules": [ - { - "target": "header", - "modifier": "authorization", - "value": "Bearer.*", - "invert": false, - "operator": "regex" - } - ], - "rulesOperator": "AND", - "disableTemplating": false, - "fallbackTo404": false, - "default": true, - "crudKey": "id", - "callbacks": [] - }, - { - "uuid": "08450fc4-cc49-488b-9e47-89652146dd2f", - "body": "{\n \"statusCode\": 401,\n \"data\": {\n \"error\": {\n \"message\": \"Invalid or expired session\",\n \"code\": \"invalid_session\",\n \"description\": \"The user session is invalid or has expired\"\n }\n }\n}", - "latency": 0, - "statusCode": 401, - "label": "Session Invalid", - "headers": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "bodyType": "INLINE", - "filePath": "", - "databucketID": "", - "sendFileAsBody": false, - "rules": [], - "rulesOperator": "OR", - "disableTemplating": false, - "fallbackTo404": false, - "default": false, - "crudKey": "id", - "callbacks": [] - } - ], - "responseMode": null, - "streamingMode": null, - "streamingInterval": 0 - }, - { - "uuid": "5b7650bc-84ac-40e0-86fe-30f72cde5bad", - "type": "http", - "documentation": "Validates account opening against bank policies", - "method": "post", - "endpoint": "api/banking/policies/validate-account-opening", - "responses": [ - { - "uuid": "0a54a02c-52f4-42c3-b1ef-5137078227cd", - "body": "{\n \"statusCode\": 200,\n \"data\": {\n \"validationId\": \"{{faker 'string.uuid'}}\",\n \"complianceChecks\": {\n \"kycCompliant\": true,\n \"amlCompliant\": true,\n \"sanctionsCheck\": true,\n \"riskAssessment\": \"low\"\n },\n \"accountLimits\": {\n \"dailyTransactionLimit\": 50000,\n \"monthlyTransactionLimit\": 500000,\n \"minimumBalance\": 0,\n \"maximumBalance\": 1000000\n },\n \"additionalRequirements\": [],\n \"approvedBy\": \"system\",\n \"approvalLevel\": \"automated\",\n \"policyVersion\": \"1.0.0\",\n \"validationScore\": {{faker 'number.int' min=85 max=100}}\n }\n}", - "latency": 500, - "statusCode": 200, - "label": "Policy Validation Passed", - "headers": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "bodyType": "INLINE", - "filePath": "", - "databucketID": "", - "sendFileAsBody": false, - "rules": [ - { - "target": "body", - "modifier": "currency", - "value": "TRY|USD|EUR|GBP", - "invert": false, - "operator": "regex" - } - ], - "rulesOperator": "AND", - "disableTemplating": false, - "fallbackTo404": false, - "default": true, - "crudKey": "id", - "callbacks": [] - }, - { - "uuid": "95b0e5c2-a907-4f68-8df9-a865c5122fb6", - "body": "{\n \"statusCode\": 403,\n \"data\": {\n \"validationId\": \"{{faker 'string.uuid'}}\",\n \"error\": {\n \"message\": \"Policy validation failed\",\n \"code\": \"policy_violation\",\n \"description\": \"Account opening request does not meet policy requirements\"\n },\n \"failedChecks\": [\"risk_assessment\"],\n \"recommendations\": [\n \"Please review your account information\",\n \"Contact customer support for assistance\"\n ],\n \"canRetry\": true,\n \"retryAfter\": 300\n }\n}", - "latency": 500, - "statusCode": 403, - "label": "Policy Validation Failed", - "headers": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "bodyType": "INLINE", - "filePath": "", - "databucketID": "", - "sendFileAsBody": false, - "rules": [], - "rulesOperator": "OR", - "disableTemplating": false, - "fallbackTo404": false, - "default": false, - "crudKey": "id", - "callbacks": [] - } - ], - "responseMode": null, - "streamingMode": null, - "streamingInterval": 0 - }, - { - "uuid": "a4ed547b-87f3-4359-a1b8-9057053da31a", - "type": "http", - "documentation": "Creates a new bank account in core banking system", - "method": "post", - "endpoint": "api/banking/accounts/create", - "responses": [ - { - "uuid": "22c09646-06cb-4a91-9747-1c15cd1a1dc8", - "body": "{\n \"statusCode\": 201,\n \"data\": {\n \"accountId\": \"{{faker 'string.uuid'}}\",\n \"accountNumber\": \"{{faker 'finance.accountNumber'}}\",\n \"iban\": \"TR{{faker 'string.numeric' 24}}\",\n \"accountType\": \"demand-deposit\",\n \"currency\": \"{{body 'currency'}}\",\n \"branchCode\": \"{{body 'branchCode'}}\",\n \"status\": \"active\",\n \"isActive\": true,\n \"currentBalance\": {{body 'initialDeposit' 0}},\n \"availableBalance\": {{body 'initialDeposit' 0}},\n \"limits\": {\n \"dailyTransactionLimit\": 50000,\n \"monthlyTransactionLimit\": 500000,\n \"minimumBalance\": 0,\n \"maximumBalance\": 1000000\n },\n \"debitCardRequested\": false,\n \"onlineBankingEnabled\": true,\n \"mobileBankingEnabled\": true,\n \"creationReference\": \"{{faker 'string.uuid'}}\",\n \"coreBankingId\": \"CB{{faker 'string.numeric' 10}}\",\n \"services\": [\n \"online-banking\",\n \"mobile-banking\",\n \"sms-banking\"\n ]\n }\n}", - "latency": 1000, - "statusCode": 201, - "label": "Account Created Successfully", - "headers": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "bodyType": "INLINE", - "filePath": "", - "databucketID": "", - "sendFileAsBody": false, - "rules": [ - { - "target": "body", - "modifier": "accountName", - "value": ".+", - "invert": false, - "operator": "regex" - } - ], - "rulesOperator": "AND", - "disableTemplating": false, - "fallbackTo404": false, - "default": true, - "crudKey": "id", - "callbacks": [] - }, - { - "uuid": "07ffbdb2-a50d-4583-871f-5a7fe839fa70", - "body": "{\n \"statusCode\": 422,\n \"data\": {\n \"error\": {\n \"message\": \"Account creation failed\",\n \"code\": \"validation_error\",\n \"description\": \"Account data validation failed\"\n },\n \"failureReason\": \"invalid_account_name\",\n \"failureCategory\": \"validation_error\",\n \"canRetry\": true,\n \"retryAfter\": 300,\n \"maxRetries\": 3,\n \"recommendations\": [\n \"Please check your account information and try again\",\n \"Account name must be between 1-50 characters\"\n ],\n \"supportReference\": \"{{faker 'string.uuid'}}\"\n }\n}", - "latency": 800, - "statusCode": 422, - "label": "Account Creation Failed", - "headers": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "bodyType": "INLINE", - "filePath": "", - "databucketID": "", - "sendFileAsBody": false, - "rules": [], - "rulesOperator": "OR", - "disableTemplating": false, - "fallbackTo404": false, - "default": false, - "crudKey": "id", - "callbacks": [] - } - ], - "responseMode": null, - "streamingMode": null, - "streamingInterval": 0 - } - ], - "rootChildren": [ - { - "type": "route", - "uuid": "1667a100-73a2-4bbf-a9ab-ef607d05e428" - }, - { - "type": "route", - "uuid": "5b7650bc-84ac-40e0-86fe-30f72cde5bad" - }, - { - "type": "route", - "uuid": "a4ed547b-87f3-4359-a1b8-9057053da31a" - } - ], - "proxyMode": false, - "proxyHost": "", - "proxyRemovePrefix": false, - "tlsOptions": { - "enabled": false, - "type": "CERT", - "pfxPath": "", - "certPath": "", - "keyPath": "", - "caPath": "", - "passphrase": "" - }, - "cors": true, - "headers": [ - { - "key": "Access-Control-Allow-Origin", - "value": "*" - }, - { - "key": "Access-Control-Allow-Methods", - "value": "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS" - }, - { - "key": "Access-Control-Allow-Headers", - "value": "Content-Type,Origin,Accept,Authorization,Content-Length,X-Requested-With,X-User-Id,X-Request-Id,X-Workflow-Instance" - } - ], - "proxyReqHeaders": [ - { - "key": "", - "value": "" - } - ], - "proxyResHeaders": [ - { - "key": "", - "value": "" - } - ], - "data": [], - "callbacks": [] -} \ No newline at end of file diff --git a/mockoon/migration-api.json b/mockoon/migration-api.json new file mode 100644 index 0000000..803a69e --- /dev/null +++ b/mockoon/migration-api.json @@ -0,0 +1,1551 @@ +{ + "uuid": "cc2c7a20-cf65-4f34-bdeb-6bbd3e3f9525", + "lastMigration": 33, + "name": "vNext - Sample Api Mock", + "endpointPrefix": "", + "latency": 0, + "port": 3001, + "hostname": "localhost", + "folders": [ + { + "uuid": "46fb060c-5347-41f5-809e-86e40337f2fa", + "name": "ouath2", + "children": [ + { + "type": "route", + "uuid": "8ba61c4a-f251-47ef-a4a6-92398c4d66ef" + }, + { + "type": "route", + "uuid": "41dbf3e6-de0a-460e-879b-614d59ff813f" + }, + { + "type": "route", + "uuid": "2c335800-21d9-4ea7-bad3-e5e6ae8af460" + }, + { + "type": "route", + "uuid": "95eecaa5-c860-4be1-87b7-22c96833e35e" + }, + { + "type": "route", + "uuid": "8bf14db4-9693-4511-8a8c-c4129661c76b" + }, + { + "type": "route", + "uuid": "8d1c1536-9e82-4cb1-9349-79852ef6131b" + }, + { + "type": "route", + "uuid": "aac5c06b-6c01-4fda-a241-ebfdbf4c5045" + }, + { + "type": "route", + "uuid": "ca026001-69e8-41c9-9dfc-2c59a4f7021d" + }, + { + "type": "route", + "uuid": "956b0537-fa8f-4169-bf00-fbd7f43b4d4d" + }, + { + "type": "route", + "uuid": "6f09aa19-7f4e-41f5-a60e-45ec17077c22" + } + ] + }, + { + "uuid": "b3a79260-52ee-40db-b9f5-057764d77ceb", + "name": "payment", + "children": [ + { + "type": "route", + "uuid": "0400d70c-1c06-417b-976f-9067fdde9e37" + }, + { + "type": "route", + "uuid": "6fcdb524-386e-463a-982f-ad2cf770b258" + }, + { + "type": "route", + "uuid": "373fb5aa-4591-443e-a08d-bcf6e01f36b3" + }, + { + "type": "route", + "uuid": "11d5f117-fe6c-45c4-ae1b-cae4e6514801" + }, + { + "type": "route", + "uuid": "510a89ef-42e4-4b00-b92b-ee182658f57e" + }, + { + "type": "route", + "uuid": "0f592f33-c8a9-490b-8c3c-12f3193d9eb3" + }, + { + "type": "route", + "uuid": "7a54e9b9-abfc-4574-b274-ccfb8ec53f15" + }, + { + "type": "route", + "uuid": "207ece34-36e7-4926-b96b-d0564f6f24ed" + }, + { + "type": "route", + "uuid": "26d4f313-c36c-408b-b532-67f51fe0d118" + } + ] + }, + { + "uuid": "4b345806-3c85-4752-98bd-15b973301107", + "name": "account-opening", + "children": [ + { + "type": "route", + "uuid": "7579d9f9-d6ab-487b-b22c-9a04b7e3498d" + }, + { + "type": "route", + "uuid": "33139996-5a0a-4514-b461-adb9d8c42e0c" + } + ] + }, + { + "uuid": "0c0c687a-810a-434f-affc-a23e45a90dfc", + "name": "contract", + "children": [ + { + "type": "route", + "uuid": "5e07a4b9-bb6d-4983-91ca-c310ff06bb11" + }, + { + "type": "route", + "uuid": "0fe9d51a-a402-45ec-955c-dca673b35b3b" + } + ] + } + ], + "routes": [ + { + "uuid": "5a05d227-3eb2-4253-8b26-20b6a95b66ae", + "type": "http", + "documentation": "Health check endpoint", + "method": "get", + "endpoint": "health", + "responses": [ + { + "uuid": "2a89fdb6-4edc-4911-865a-eae2cf5c5b60", + "body": "{\n \"status\": \"ok\",\n \"timestamp\": \"{{now}}\",\n \"service\": \"migration-api\",\n \"version\": \"1.0.0\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Health OK", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "8ba61c4a-f251-47ef-a4a6-92398c4d66ef", + "type": "http", + "documentation": "OAuth2 Client validation - İstemci doğrulama", + "method": "post", + "endpoint": "api/oauth2/client/validate", + "responses": [ + { + "uuid": "818941e1-e6e0-4c1b-acc0-73c5eab77e84", + "body": "{\n \"success\": true,\n \"clientId\": \"{{body 'client_id'}}\",\n \"grantTypes\": [\"client_credentials\", \"password\", \"authorization_code\"],\n \"redirectUris\": [\"http://localhost:3000/callback\"],\n \"scopes\": [\"api\", \"read\", \"write\"],\n \"isActive\": true\n}", + "latency": 100, + "statusCode": 200, + "label": "Client Geçerli", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "client_id", + "value": "", + "invert": true, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "9e2a036a-a8b4-4f45-bc3d-91693f57e322", + "body": "{\n \"success\": false,\n \"error\": \"Geçersiz client credentials\",\n \"errorCode\": \"invalid_client\"\n}", + "latency": 50, + "statusCode": 401, + "label": "Client Geçersiz", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "client_id", + "value": "", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "41dbf3e6-de0a-460e-879b-614d59ff813f", + "type": "http", + "documentation": "OAuth2 User credentials validation - Kullanıcı kimlik bilgileri doğrulama", + "method": "post", + "endpoint": "api/oauth2/user/authenticate", + "responses": [ + { + "uuid": "219ffca1-bd0d-4a93-abbf-033b90943857", + "body": "{\n \"success\": true,\n \"userId\": \"{{faker 'string.uuid'}}\",\n \"username\": \"{{body 'username'}}\",\n \"email\": \"{{body 'username'}}@example.com\",\n \"roles\": [\"user\"],\n \"mfaRequired\": true\n}", + "latency": 150, + "statusCode": 200, + "label": "Kullanıcı Geçerli", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "username", + "value": "admin", + "invert": false, + "operator": "equals" + }, + { + "target": "body", + "modifier": "password", + "value": "password123", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "AND", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "c6e20796-a6f8-4e89-937c-54fd85b4438f", + "body": "{\n \"success\": false,\n \"error\": \"Geçersiz kullanıcı adı veya şifre\",\n \"errorCode\": \"invalid_credentials\"\n}", + "latency": 100, + "statusCode": 401, + "label": "Kullanıcı Geçersiz", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "8bf14db4-9693-4511-8a8c-c4129661c76b", + "type": "http", + "documentation": "OAuth2 Authorization code validation - Authorization code doğrulama", + "method": "post", + "endpoint": "api/oauth2/user/validate-authorization-code", + "responses": [ + { + "uuid": "27016b3c-e3ea-4f53-9979-762844e6067f", + "body": "{\n \"success\": true,\n \"code\": \"{{body 'code'}}\",\n \"clientId\": \"{{body 'client_id'}}\",\n \"userId\": \"{{faker 'string.uuid'}}\",\n \"redirectUri\": \"{{body 'redirect_uri'}}\",\n \"codeChallenge\": \"{{body 'code_challenge'}}\",\n \"codeChallengeMethod\": \"{{body 'code_challenge_method'}}\",\n \"isValid\": true,\n \"expiresAt\": \"{{dateTimeAdd (now) 10 'minutes'}}\"\n}", + "latency": 100, + "statusCode": 200, + "label": "Authorization Code Geçerli", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "2c335800-21d9-4ea7-bad3-e5e6ae8af460", + "type": "http", + "documentation": "Device registration check - Cihaz kayıt durumu kontrolü", + "method": "get", + "endpoint": "api/oauth2/devices/check", + "responses": [ + { + "uuid": "2df2b0fe-6114-406a-8536-cc7e8a34ffb7", + "body": "{\n \"isRegistered\": true,\n \"deviceId\": \"{{header 'X-Device-Id'}}\",\n \"deviceName\": \"iPhone 15 Pro\",\n \"deviceType\": \"mobile\",\n \"supportsPush\": true,\n \"registeredAt\": \"2024-09-10T01:48:20.501Z\",\n \"lastSeenAt\": \"{{now}}\"\n}", + "latency": 80, + "statusCode": 200, + "label": "Cihaz Kayıtlı", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "header", + "modifier": "X-Device-Id", + "value": "", + "invert": true, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "e3c46ef5-4156-49d0-8ff9-a554b5e501d5", + "body": "{\n \"isRegistered\": false,\n \"deviceId\": \"{{header 'X-Device-Id'}}\",\n \"supportsPush\": false,\n \"requiresRegistration\": true\n}", + "latency": 60, + "statusCode": 200, + "label": "Cihaz Kayıtlı Değil", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "header", + "modifier": "X-Device-Id", + "value": "unregistered-.*", + "invert": false, + "operator": "regex" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "95eecaa5-c860-4be1-87b7-22c96833e35e", + "type": "http", + "documentation": "Send push notification for MFA - MFA için push bildirimi gönder", + "method": "post", + "endpoint": "api/oauth2/push/send", + "responses": [ + { + "uuid": "0f3b22cd-5e87-4c72-9346-188fa9d9120d", + "body": "{\n \"success\": true,\n \"notificationId\": \"push_{{faker 'string.uuid'}}\",\n \"deviceId\": \"{{body 'deviceId'}}\",\n \"userId\": \"{{body 'userId'}}\",\n \"message\": \"Giriş onayı gerekli\",\n \"sentAt\": \"{{now}}\",\n \"expiresAt\": \"{{dateTimeAdd (now) 5 'minutes'}}\",\n \"status\": \"sent\"\n}", + "latency": 200, + "statusCode": 200, + "label": "Push Bildirimi Gönderildi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "8d1c1536-9e82-4cb1-9349-79852ef6131b", + "type": "http", + "documentation": "Check push notification response - Push bildirimi yanıtını kontrol et", + "method": "post", + "endpoint": "api/oauth2/push/status", + "responses": [ + { + "uuid": "0ab9012d-e65e-428b-a643-a045ab203693", + "body": "{\n \"notificationId\": \"{{body 'notificationId'}}\",\n \"status\": \"approved\",\n \"respondedAt\": \"{{now}}\",\n \"deviceId\": \"{{body 'deviceId'}}\",\n \"action\": \"approve\",\n \"success\": true\n}", + "latency": 100, + "statusCode": 200, + "label": "Push Onaylandı", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "notificationId", + "value": "push_.*", + "invert": false, + "operator": "regex" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "51e7f58b-e90b-4cfb-847b-1875d8156fb2", + "body": "{\n \"notificationId\": \"{{body 'notificationId'}}\",\n \"status\": \"denied\",\n \"respondedAt\": \"{{now}}\",\n \"deviceId\": \"{{body 'deviceId'}}\",\n \"action\": \"deny\",\n \"success\": false,\n \"reason\": \"User denied the request\"\n}", + "latency": 80, + "statusCode": 200, + "label": "Push Reddedildi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "action", + "value": "deny", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "aac5c06b-6c01-4fda-a241-ebfdbf4c5045", + "type": "http", + "documentation": "Send OTP notification - OTP bildirimi gönder (SMS/Email)", + "method": "post", + "endpoint": "api/oauth2/otp/send", + "responses": [ + { + "uuid": "113f9d3f-32d8-491a-9c29-56db03240f0f", + "body": "{\n \"success\": true,\n \"otpId\": \"otp_{{faker 'string.uuid'}}\",\n \"userId\": \"{{body 'userId'}}\",\n \"method\": \"{{body 'method'}}\",\n \"target\": \"{{body 'target'}}\",\n \"maskedTarget\": \"***@***.com\",\n \"sentAt\": \"{{now}}\",\n \"expiresAt\": \"{{dateTimeAdd (now) 5 'minutes'}}\",\n \"length\": 6\n}", + "latency": 250, + "statusCode": 200, + "label": "OTP Gönderildi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "ca026001-69e8-41c9-9dfc-2c59a4f7021d", + "type": "http", + "documentation": "Verify OTP code - OTP kodu doğrula", + "method": "post", + "endpoint": "api/oauth2/otp/verify", + "responses": [ + { + "uuid": "df8f0537-450f-4491-b825-e7d6dd39d0cc", + "body": "{\n \"success\": true,\n \"otpId\": \"{{body 'otpId'}}\",\n \"code\": \"{{body 'code'}}\",\n \"isValid\": true,\n \"verifiedAt\": \"{{now}}\",\n \"remainingAttempts\": 2\n}", + "latency": 100, + "statusCode": 200, + "label": "OTP Geçerli", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "code", + "value": "123456", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "16f286db-7997-4b14-a987-1c639ee2c9f5", + "body": "{\n \"success\": false,\n \"otpId\": \"{{body 'otpId'}}\",\n \"code\": \"{{body 'code'}}\",\n \"isValid\": false,\n \"error\": \"Geçersiz OTP kodu\",\n \"remainingAttempts\": 1\n}", + "latency": 80, + "statusCode": 400, + "label": "OTP Geçersiz", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "956b0537-fa8f-4169-bf00-fbd7f43b4d4d", + "type": "http", + "documentation": "Register device for MFA - MFA için cihaz kaydet", + "method": "post", + "endpoint": "api/oauth2/devices/register", + "responses": [ + { + "uuid": "650d204a-51aa-4662-9e9a-06664ad7b059", + "body": "{\n \"success\": true,\n \"deviceId\": \"{{faker 'string.uuid'}}\",\n \"userId\": \"{{body 'userId'}}\",\n \"deviceName\": \"{{body 'deviceName'}}\",\n \"deviceType\": \"{{body 'deviceType'}}\",\n \"supportsPush\": true,\n \"registeredAt\": \"{{now}}\",\n \"token\": \"device_{{faker 'string.alphanumeric' 32}}\"\n}", + "latency": 150, + "statusCode": 201, + "label": "Cihaz Başarıyla Kaydedildi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "6f09aa19-7f4e-41f5-a60e-45ec17077c22", + "type": "http", + "documentation": "Generate OAuth2 tokens - OAuth2 token'ları oluştur", + "method": "post", + "endpoint": "api/oauth2/tokens/generate", + "responses": [ + { + "uuid": "c4a04178-0461-45ac-a6b0-238608a93fd4", + "body": "{\n \"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.{{faker 'string.alphanumeric' 50}}\",\n \"refresh_token\": \"rt_{{faker 'string.alphanumeric' 32}}\",\n \"token_type\": \"Bearer\",\n \"expires_in\": 3600,\n \"scope\": \"{{body 'scope'}}\",\n \"client_id\": \"{{body 'clientId'}}\",\n \"user_id\": \"{{body 'userId'}}\",\n \"device_id\": \"{{body 'deviceId'}}\",\n \"issued_at\": \"{{now}}\",\n \"expires_at\": \"{{dateTimeAdd (now) 1 'hours'}}\"\n}", + "latency": 120, + "statusCode": 200, + "label": "Token'lar Oluşturuldu", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "0400d70c-1c06-417b-976f-9067fdde9e37", + "type": "http", + "documentation": "Save Payment Configuration - Ödeme yapılandırmasını kaydet", + "method": "post", + "endpoint": "api/payments/schedules", + "responses": [ + { + "uuid": "e884dba5-3969-4a04-8ecd-0d0e5fb0927d", + "body": "{\n \"success\": true,\n \"scheduleId\": \"{{faker 'string.uuid'}}\",\n \"userId\": \"{{body 'userId'}}\",\n \"amount\": {{body 'amount'}},\n \"currency\": \"{{body 'currency'}}\",\n \"frequency\": \"{{body 'frequency'}}\",\n \"startDate\": \"{{body 'startDate'}}\",\n \"endDate\": \"{{body 'endDate'}}\",\n \"paymentMethodId\": \"{{body 'paymentMethodId'}}\",\n \"description\": \"{{body 'description'}}\",\n \"recipientId\": \"{{body 'recipientId'}}\",\n \"isAutoRetry\": {{body 'isAutoRetry'}},\n \"maxRetries\": {{body 'maxRetries'}},\n \"status\": \"inactive\",\n \"nextPaymentDate\": \"{{dateTimeAdd (now) 1 'days'}}\",\n \"createdAt\": \"{{now}}\",\n \"maxPayments\": 4\n}", + "latency": 200, + "statusCode": 201, + "label": "Ödeme Yapılandırması Kaydedildi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "dd9e529a-6371-493a-a703-9891bd1efb7c", + "body": "{\n \"success\": false,\n \"error\": \"Geçersiz ödeme yapılandırması\",\n \"errorCode\": \"invalid_payment_config\",\n \"details\": \"Eksik veya geçersiz parametreler\"\n}", + "latency": 100, + "statusCode": 400, + "label": "Geçersiz Yapılandırma", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "userId", + "value": "", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "6fcdb524-386e-463a-982f-ad2cf770b258", + "type": "http", + "documentation": "Process Payment - Ödeme işle", + "method": "post", + "endpoint": "api/payments/process", + "responses": [ + { + "uuid": "011b7be9-5d5e-4de5-92f1-3567cf7ee69f", + "body": "{\n \"success\": true,\n \"transactionId\": \"txn_{{faker 'string.uuid'}}\",\n \"scheduleId\": \"{{body 'scheduleId'}}\",\n \"amount\": {{body 'amount'}},\n \"currency\": \"{{body 'currency'}}\",\n \"paymentMethod\": \"card\",\n \"processedAt\": \"{{now}}\",\n \"fees\": {\n \"amount\": {{faker 'number.float' 0.5 5.0 2}},\n \"currency\": \"{{body 'currency'}}\"\n },\n \"status\": \"completed\"\n}", + "latency": 1500, + "statusCode": 200, + "label": "Ödeme Başarılı", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "amount", + "value": "0", + "invert": true, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "53781cfd-a6f1-4378-be6f-872ab89f8b73", + "body": "{\n \"success\": false,\n \"error\": \"Yetersiz bakiye\",\n \"errorCode\": \"insufficient_funds\",\n \"amount\": {{body 'amount'}},\n \"availableBalance\": {{faker 'number.float' 0 100 2}}\n}", + "latency": 800, + "statusCode": 402, + "label": "Yetersiz Bakiye", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "amount", + "value": "1000", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "99a674ff-c9bd-4786-bda6-186bdfd5d078", + "body": "{\n \"success\": false,\n \"error\": \"Ödeme işleme hatası\",\n \"errorCode\": \"payment_processing_error\",\n \"description\": \"Ödeme sağlayıcısı geçici olarak kullanılamıyor\"\n}", + "latency": 5000, + "statusCode": 503, + "label": "Servis Kullanılamıyor", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "373fb5aa-4591-443e-a08d-bcf6e01f36b3", + "type": "http", + "documentation": "Activate Payment Schedule - Ödeme planını aktifleştir", + "method": "patch", + "endpoint": "api/payments/schedules/:scheduleId/activate", + "responses": [ + { + "uuid": "688a2e3c-b375-4115-8099-9ce8a7a0188e", + "body": "{\n \"success\": true,\n \"scheduleId\": \"{{urlParam 'scheduleId'}}\",\n \"status\": \"active\",\n \"activatedAt\": \"{{now}}\",\n \"nextPaymentDate\": \"{{dateTimeAdd (now) 1 'days'}}\",\n \"isActive\": true\n}", + "latency": 300, + "statusCode": 200, + "label": "Plan Aktifleştirildi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "a077bdd7-9b8d-44d2-904a-e7f4e2550cc6", + "body": "{\n \"success\": false,\n \"error\": \"Ödeme planı bulunamadı\",\n \"errorCode\": \"schedule_not_found\",\n \"scheduleId\": \"{{urlParam 'scheduleId'}}\"\n}", + "latency": 100, + "statusCode": 404, + "label": "Plan Bulunamadı", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "params", + "modifier": "scheduleId", + "value": "invalid-.*", + "invert": false, + "operator": "regex" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "11d5f117-fe6c-45c4-ae1b-cae4e6514801", + "type": "http", + "documentation": "Deactivate Payment Schedule - Ödeme planını deaktifleştir", + "method": "patch", + "endpoint": "api/payments/schedules/:scheduleId/deactivate", + "responses": [ + { + "uuid": "e0d0ab89-aafa-47d3-b08c-46f56caf0ccd", + "body": "{\n \"success\": true,\n \"scheduleId\": \"{{urlParam 'scheduleId'}}\",\n \"status\": \"deactive\",\n \"deactivatedAt\": \"{{now}}\",\n \"reason\": \"{{body 'reason'}}\",\n \"isActive\": false\n}", + "latency": 200, + "statusCode": 200, + "label": "Plan Deaktifleştirildi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "510a89ef-42e4-4b00-b92b-ee182658f57e", + "type": "http", + "documentation": "Archive Payment Record - Ödeme kaydını arşivle", + "method": "post", + "endpoint": "api/payments/schedules/:scheduleId/archive", + "responses": [ + { + "uuid": "3de0ad5d-7435-403f-ac36-bc8a37e49dfd", + "body": "{\n \"success\": true,\n \"scheduleId\": \"{{urlParam 'scheduleId'}}\",\n \"status\": \"archived\",\n \"archivedAt\": \"{{now}}\",\n \"completionReason\": \"{{body 'completionReason'}}\",\n \"finalPaymentCount\": {{body 'finalPaymentCount'}}\n}", + "latency": 150, + "statusCode": 200, + "label": "Kayıt Arşivlendi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "0f592f33-c8a9-490b-8c3c-12f3193d9eb3", + "type": "http", + "documentation": "Increment Retry Counter - Yeniden deneme sayacını artır", + "method": "patch", + "endpoint": "api/payments/schedules/:scheduleId/retry", + "responses": [ + { + "uuid": "e29a1679-70fa-4683-a2c4-cedf6bf8ad98", + "body": "{\n \"success\": true,\n \"scheduleId\": \"{{urlParam 'scheduleId'}}\",\n \"retryCount\": {{body 'retryCount'}},\n \"retryAt\": \"{{body 'retryAt'}}\",\n \"maxRetries\": 3,\n \"retriesRemaining\": {{subtract 3 (body 'retryCount')}}\n}", + "latency": 100, + "statusCode": 200, + "label": "Sayaç Güncellendi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "7a54e9b9-abfc-4574-b274-ccfb8ec53f15", + "type": "http", + "documentation": "Send Payment Notification Sms - Sms Bilgilendirme", + "method": "post", + "endpoint": "api/payments/notify/sms", + "responses": [ + { + "uuid": "c8cb9051-5141-4d59-be65-54409f532adb", + "body": "{\n \"success\": true,\n \"sendedAt\": \"{{now}}\"\n}", + "latency": 200, + "statusCode": 201, + "label": "Sms Gönderildi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "75c96004-c78a-4790-a7f3-928c533b57b0", + "body": "{\n \"success\": false,\n \"error\": \"Geçersiz ödeme yapılandırması\",\n \"errorCode\": \"invalid_payment_config\",\n \"details\": \"Eksik veya geçersiz parametreler\"\n}", + "latency": 100, + "statusCode": 400, + "label": "Geçersiz Yapılandırma", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "userId", + "value": "", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "207ece34-36e7-4926-b96b-d0564f6f24ed", + "type": "http", + "documentation": "Send Payment Notification Push - Mobile Bilgilendirme", + "method": "post", + "endpoint": "api/payments/notify/push", + "responses": [ + { + "uuid": "634f50b5-8c31-41d4-ade6-6f1ee8a7536a", + "body": "{\n \"success\": true,\n \"sendedAt\": \"{{now}}\"\n}", + "latency": 200, + "statusCode": 201, + "label": "Push Gönderildi", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "258a1313-08c6-45f4-89dd-9c3c57393510", + "body": "{\n \"success\": false,\n \"error\": \"Geçersiz ödeme yapılandırması\",\n \"errorCode\": \"invalid_payment_config\",\n \"details\": \"Eksik veya geçersiz parametreler\"\n}", + "latency": 100, + "statusCode": 400, + "label": "Geçersiz Yapılandırma", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "userId", + "value": "", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "26d4f313-c36c-408b-b532-67f51fe0d118", + "type": "http", + "documentation": "Get User Info - Kullanıcı Bilgisini Getir", + "method": "get", + "endpoint": "api/payments/users/:userId", + "responses": [ + { + "uuid": "042dae89-63bc-423e-8505-f2825df4245d", + "body": "{\n \"name\": \"John\",\n \"surname\": \"Smith\",\n \"email\": \"john.smith@example.com\",\n \"phoneNumber\": \"905452424847\",\n \"registeredDevices\": [{\n \"deviceId\": \"{{faker 'string.uuid'}}\",\n \"deviceInfo\": \"Ios 18\",\n \"active\": true\n }],\n \"language\": \"tr-TR\"\n}", + "latency": 200, + "statusCode": 200, + "label": "Kullanıcı Bilgisi Döner", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "7579d9f9-d6ab-487b-b22c-9a04b7e3498d", + "type": "http", + "documentation": "Validates account opening against bank policies", + "method": "post", + "endpoint": "api/banking/policies/validate-account-opening", + "responses": [ + { + "uuid": "5b154c2e-31e6-42be-8238-8623488b9f11", + "body": "{\n \"validationId\": \"{{faker 'string.uuid'}}\",\n \"complianceChecks\": {\n \"kycCompliant\": true,\n \"amlCompliant\": true,\n \"sanctionsCheck\": true,\n \"riskAssessment\": \"low\"\n },\n \"accountLimits\": {\n \"dailyTransactionLimit\": 50000,\n \"monthlyTransactionLimit\": 500000,\n \"minimumBalance\": 0,\n \"maximumBalance\": 1000000\n },\n \"additionalRequirements\": [],\n \"approvedBy\": \"system\",\n \"approvalLevel\": \"automated\",\n \"policyVersion\": \"1.0.0\",\n \"validationScore\": {{faker 'number.int' min=85 max=100}}\n}", + "latency": 0, + "statusCode": 200, + "label": "Policy Validation Passed", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "currency", + "value": "TRY|USD|EUR|GBP", + "invert": false, + "operator": "regex" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "bb862b01-f62e-437a-9e8d-c7cf2a38d0d3", + "body": "{\n \"validationId\": \"{{faker 'string.uuid'}}\",\n \"error\": {\n \"message\": \"Policy validation failed\",\n \"code\": \"policy_violation\",\n \"description\": \"Account opening request does not meet policy requirements\"\n },\n \"failedChecks\": [\n \"risk_assessment\"\n ],\n \"recommendations\": [\n \"Please review your account information\",\n \"Contact customer support for assistance\"\n ],\n \"canRetry\": true,\n \"retryAfter\": 300\n}", + "latency": 0, + "statusCode": 403, + "label": "Policy Validation Failed", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "33139996-5a0a-4514-b461-adb9d8c42e0c", + "type": "http", + "documentation": "Creates a new bank account in core banking system", + "method": "post", + "endpoint": "api/banking/accounts/create", + "responses": [ + { + "uuid": "308fd823-ef23-4833-95be-0d5c520a1dea", + "body": "{\n \"accountId\": \"{{faker 'string.uuid'}}\",\n \"accountNumber\": \"{{faker 'finance.accountNumber'}}\",\n \"iban\": \"TR{{faker 'string.numeric' 24}}\",\n \"accountType\": \"demand-deposit\",\n \"currency\": \"{{body 'currency'}}\",\n \"branchCode\": \"{{body 'branchCode'}}\",\n \"status\": \"active\",\n \"isActive\": true,\n \"currentBalance\": {{body 'initialDeposit' 0}},\n \"availableBalance\": {{body 'initialDeposit' 0}},\n \"limits\": {\n \"dailyTransactionLimit\": 50000,\n \"monthlyTransactionLimit\": 500000,\n \"minimumBalance\": 0,\n \"maximumBalance\": 1000000\n },\n \"debitCardRequested\": false,\n \"onlineBankingEnabled\": true,\n \"mobileBankingEnabled\": true,\n \"creationReference\": \"{{faker 'string.uuid'}}\",\n \"coreBankingId\": \"CB{{faker 'string.numeric' 10}}\",\n \"services\": [\n \"online-banking\",\n \"mobile-banking\",\n \"sms-banking\"\n ]\n}", + "latency": 0, + "statusCode": 201, + "label": "Account Created Successfully", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "body", + "modifier": "accountName", + "value": ".+", + "invert": false, + "operator": "regex" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "6b220657-625b-43ef-8bac-80a265d9c3ba", + "body": "{\n \"error\": {\n \"message\": \"Account creation failed\",\n \"code\": \"validation_error\",\n \"description\": \"Account data validation failed\"\n },\n \"failureReason\": \"invalid_account_name\",\n \"failureCategory\": \"validation_error\",\n \"canRetry\": true,\n \"retryAfter\": 300,\n \"maxRetries\": 3,\n \"recommendations\": [\n \"Please check your account information and try again\",\n \"Account name must be between 1-50 characters\"\n ],\n \"supportReference\": \"{{faker 'string.uuid'}}\"\n}", + "latency": 0, + "statusCode": 422, + "label": "Account Creation Failed", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "5e07a4b9-bb6d-4983-91ca-c310ff06bb11", + "type": "http", + "documentation": "", + "method": "post", + "endpoint": "api/contract/group/query", + "responses": [ + { + "uuid": "12a6bb9b-5e3f-4393-8fce-f66b76f6c370", + "body": "{\n \"statusCode\": 200,\n \"data\": {\n \"groupCode\": \"{{body 'groupCode'}}\",\n \"contracts\": [\n {\n \"contractId\": \"{{faker 'string.uuid'}}\",\n \"contractType\": \"account-opening\",\n \"contractName\": \"Hesap Açılış Sözleşmesi\",\n \"templateId\": \"TPL-ACCOUNT-001\",\n \"contractData\": {\n \"accountType\": \"demand-deposit\",\n \"currency\": \"TRY\"\n }\n },\n {\n \"contractId\": \"{{faker 'string.uuid'}}\",\n \"contractType\": \"terms-conditions\",\n \"contractName\": \"Genel Hüküm ve Koşullar\",\n \"templateId\": \"TPL-TERMS-001\",\n \"contractData\": {\n \"version\": \"2.0\"\n }\n },\n {\n \"contractId\": \"{{faker 'string.uuid'}}\",\n \"contractType\": \"kvkk\",\n \"contractName\": \"KVKK Aydınlatma Metni\",\n \"templateId\": \"TPL-KVKK-001\",\n \"contractData\": {\n \"consentDate\": \"{{now 'yyyy-MM-dd'}}\"\n }\n }\n ],\n \"totalContracts\": 3,\n \"retrievedAt\": \"{{now}}\"\n }\n}", + "latency": 0, + "statusCode": 200, + "label": "Contracts Retrieved Successfully", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "b816189d-1b14-4475-b5e3-770cdfd88a5a", + "body": "{\n \"statusCode\": 404,\n \"data\": {\n \"error\": {\n \"message\": \"Contract group not found\",\n \"code\": \"group_not_found\",\n \"description\": \"The specified contract group code does not exist\"\n }\n }\n}", + "latency": 0, + "statusCode": 404, + "label": "Contract Group Not Found", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + }, + { + "uuid": "0fe9d51a-a402-45ec-955c-dca673b35b3b", + "type": "http", + "documentation": "", + "method": "post", + "endpoint": "api/contract/document/render", + "responses": [ + { + "uuid": "d7ee8c1b-f0a0-49df-86a9-5b4421305c80", + "body": "{\n \"statusCode\": 200,\n \"data\": {\n \"documentId\": \"{{faker 'string.uuid'}}\",\n \"documentUrl\": \"https://cdn.example.com/contracts/{{faker 'string.alphanumeric' 32}}.pdf\",\n \"documentSize\": {{faker 'number.int' min=50000 max=500000}},\n \"pageCount\": {{faker 'number.int' min=1 max=10}},\n \"checksum\": \"{{faker 'string.alphanumeric' 64}}\",\n \"expiresAt\": \"{{faker 'date.future'}}\",\n \"renderedAt\": \"{{now}}\",\n \"format\": \"pdf\",\n \"templateVersion\": \"1.0.0\"\n }\n}", + "latency": 0, + "statusCode": 200, + "label": "Document Rendered Successfully", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] + }, + { + "uuid": "5d073833-c477-4dc1-9a2d-bb48d947f8e5", + "body": "{\n \"statusCode\": 500,\n \"data\": {\n \"error\": {\n \"message\": \"Document rendering failed\",\n \"code\": \"render_failed\",\n \"description\": \"Unable to render the contract document\"\n }\n }\n}", + "latency": 0, + "statusCode": 500, + "label": "Document Render Failed", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false, + "crudKey": "id", + "callbacks": [] + } + ], + "responseMode": null, + "streamingMode": null, + "streamingInterval": 0 + } + ], + "rootChildren": [ + { + "type": "route", + "uuid": "5a05d227-3eb2-4253-8b26-20b6a95b66ae" + }, + { + "type": "folder", + "uuid": "46fb060c-5347-41f5-809e-86e40337f2fa" + }, + { + "type": "folder", + "uuid": "b3a79260-52ee-40db-b9f5-057764d77ceb" + }, + { + "type": "folder", + "uuid": "4b345806-3c85-4752-98bd-15b973301107" + }, + { + "type": "folder", + "uuid": "0c0c687a-810a-434f-affc-a23e45a90dfc" + } + ], + "proxyMode": false, + "proxyHost": "", + "proxyRemovePrefix": true, + "tlsOptions": { + "enabled": false, + "type": "CERT", + "pfxPath": "", + "certPath": "", + "keyPath": "", + "caPath": "", + "passphrase": "" + }, + "cors": true, + "headers": [ + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS" + }, + { + "key": "Access-Control-Allow-Headers", + "value": "Content-Type,Origin,Accept,Authorization,Content-Length,X-Requested-With" + } + ], + "proxyReqHeaders": [ + { + "key": "", + "value": "" + } + ], + "proxyResHeaders": [ + { + "key": "", + "value": "" + } + ], + "data": [], + "callbacks": [] +} \ No newline at end of file diff --git a/mockoon/upload-to-mockoon.sh b/mockoon/upload-to-mockoon.sh new file mode 100755 index 0000000..4136103 --- /dev/null +++ b/mockoon/upload-to-mockoon.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Mockoon sunucusuna environment yükleme scripti +# Kullanım: ./upload-to-mockoon.sh + +MOCKOON_URL="https://poc-mockoon.apps.nonprod.ebt.bank" +JSON_FILE="migration-api.json" + +echo "🚀 Mockoon sunucusuna bağlanılıyor..." +echo "📍 Sunucu: $MOCKOON_URL" +echo "📄 Dosya: $JSON_FILE" +echo "" + +# Sunucu durumunu kontrol et +echo "1️⃣ Sunucu durumu kontrol ediliyor..." +if curl -k -s -f "$MOCKOON_URL/health" > /dev/null 2>&1; then + echo "✅ Sunucu aktif" +else + echo "⚠️ Sunucu health check başarısız, devam ediliyor..." +fi + +echo "" +echo "2️⃣ Environment yükleniyor..." + +# Environment'ı POST et +RESPONSE=$(curl -k -s -w "\n%{http_code}" -X POST \ + "$MOCKOON_URL/api/environments" \ + -H "Content-Type: application/json" \ + -d @"$JSON_FILE" 2>&1) + +HTTP_CODE=$(echo "$RESPONSE" | tail -n1) +BODY=$(echo "$RESPONSE" | head -n-1) + +echo "📡 HTTP Durum Kodu: $HTTP_CODE" +echo "" + +if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then + echo "✅ Başarılı! Environment yüklendi." + echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY" +elif [ "$HTTP_CODE" = "409" ]; then + echo "ℹ️ Environment zaten mevcut, güncelleme deneniyor..." + + # UUID'yi dosyadan al + UUID=$(jq -r '.uuid' "$JSON_FILE") + + # PUT ile güncelle + RESPONSE=$(curl -k -s -w "\n%{http_code}" -X PUT \ + "$MOCKOON_URL/api/environments/$UUID" \ + -H "Content-Type: application/json" \ + -d @"$JSON_FILE" 2>&1) + + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + BODY=$(echo "$RESPONSE" | head -n-1) + + if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "204" ]; then + echo "✅ Environment güncellendi!" + echo "$BODY" | jq '.' 2>/dev/null || echo "$BODY" + else + echo "❌ Güncelleme başarısız!" + echo "$BODY" + fi +else + echo "❌ Yükleme başarısız!" + echo "$BODY" +fi + +echo "" +echo "🏁 İşlem tamamlandı." + diff --git a/package-lock.json b/package-lock.json index c37e764..a14016b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,23 @@ { - "name": "@burgan-tech/vnext-template", - "version": "0.0.3", + "name": "@burgan-tech/vnext-example", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@burgan-tech/vnext-template", - "version": "0.0.3", + "name": "@burgan-tech/vnext-example", + "version": "0.0.1", + "hasInstallScript": true, "license": "MIT", + "dependencies": { + "@burgan-tech/vnext-schema": "^0.0.26" + }, + "bin": { + "vnext-setup": "setup.js", + "vnext-template": "init.js" + }, "devDependencies": { + "@burgan-tech/vnext-schema": "^0.0.26", "ajv": "^8.12.0", "ajv-formats": "^2.1.1" }, @@ -16,6 +25,16 @@ "node": ">=16.0.0" } }, + "node_modules/@burgan-tech/vnext-schema": { + "version": "0.0.26", + "resolved": "https://registry.npmjs.org/@burgan-tech/vnext-schema/-/vnext-schema-0.0.26.tgz", + "integrity": "sha512-Ionn1/61LJIWkGsvSzPcbs8g03PNAjpeYEdtw+V7bIcDWiSjviKh79iCgYgNTE5YovEVtCxD2Mb6Yg5n4eyyAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", diff --git a/package.json b/package.json index 884e9b1..e6ec049 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,18 @@ "scripts": { "test": "node test.js", "validate": "node validate.js", - "build": "echo 'Build completed - package is ready'", - "prepublishOnly": "npm run validate" + "build": "node build.js", + "build:reference": "node build.js -t reference", + "build:runtime": "node build.js -t runtime", + "sync-schema": "node sync-schema-version.js", + "setup": "node setup.js", + "postinstall": "node setup.js && npm run sync-schema", + "prepublishOnly": "npm run sync-schema && npm run validate" + }, + "bin": { + "@burgan-tech/vnext-template": "./init.js", + "vnext-template": "./init.js", + "vnext-setup": "./setup.js" }, "keywords": [ "vnext", @@ -30,24 +40,26 @@ "homepage": "https://github.com/burgan-tech/vnext-template#readme", "files": [ "index.js", + "init.js", + "setup.js", + "build.js", "core/", "vnext.config.json", "README.md", "package.json", - "CHANGELOG.md", "LICENSE", - "test.js", "validate.js", - "test-domain-detection.sh", - ".cursorrules", - ".gitignore", - ".gitattributes" + "sync-schema-version.js" ], "engines": { "node": ">=16.0.0" }, "devDependencies": { + "@burgan-tech/vnext-schema": "^0.0.27", "ajv": "^8.12.0", "ajv-formats": "^2.1.1" + }, + "dependencies": { + "@burgan-tech/vnext-schema": "^0.0.27" } } diff --git a/postman/vNext Example Runtime.postman_collection.json b/postman/vNext Example Runtime.postman_collection.json index 15c2f9b..1f22b3d 100644 --- a/postman/vNext Example Runtime.postman_collection.json +++ b/postman/vNext Example Runtime.postman_collection.json @@ -966,7 +966,7 @@ "variable": [ { "key": "baseUrl", - "value": "http://mockoon:4201/api/v1" + "value": "http://localhost:4201/api/v1" }, { "key": "requestId", diff --git a/setup.js b/setup.js new file mode 100644 index 0000000..21db5b9 --- /dev/null +++ b/setup.js @@ -0,0 +1,243 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); + +// Load configuration from vnext.config.json +function loadConfig() { + try { + return JSON.parse(fs.readFileSync('vnext.config.json', 'utf8')); + } catch (error) { + return null; + } +} + +// Get paths configuration with defaults +function getPathsConfig() { + const config = loadConfig(); + const defaults = { + componentsRoot: 'core', + schemas: 'Schemas', + workflows: 'Workflows', + tasks: 'Tasks', + views: 'Views', + functions: 'Functions', + extensions: 'Extensions' + }; + + if (config && config.paths) { + return { ...defaults, ...config.paths }; + } + return defaults; +} + +// Check if already set up (componentsRoot directory exists with vnext structure) +function isAlreadySetup() { + const pathsConfig = getPathsConfig(); + const componentsRoot = pathsConfig.componentsRoot; + + // Check if componentsRoot directory exists and contains vnext structure + if (componentsRoot && fs.existsSync(componentsRoot)) { + const schemasPath = path.join(componentsRoot, pathsConfig.schemas); + const workflowsPath = path.join(componentsRoot, pathsConfig.workflows); + const tasksPath = path.join(componentsRoot, pathsConfig.tasks); + + if (fs.existsSync(schemasPath) || + fs.existsSync(workflowsPath) || + fs.existsSync(tasksPath)) { + return true; + } + } + + return false; +} + +// Validate domain name +function validateDomainName(domainName) { + if (!domainName) { + console.error('❌ Domain name cannot be empty'); + return false; + } + // Validate domain name format (alphanumeric, hyphens, underscores) + if (!/^[a-zA-Z0-9_-]+$/.test(domainName)) { + console.error('❌ Domain name can only contain letters, numbers, hyphens, and underscores'); + return false; + } + return true; +} + +// Prompt for domain name +function promptDomainName() { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + return new Promise((resolve) => { + rl.question('Enter your domain name (e.g., "user-management", "order-processing"): ', (answer) => { + rl.close(); + const domainName = answer.trim(); + if (!validateDomainName(domainName)) { + process.exit(1); + } + resolve(domainName); + }); + }); +} + +// Get domain name from command line arguments, environment variable, or prompt +async function getDomainName() { + // Check for command line argument + const args = process.argv.slice(2); + if (args.length > 0) { + const domainName = args[0].trim(); + if (validateDomainName(domainName)) { + return domainName; + } else { + process.exit(1); + } + } + + // Check for environment variable (for npm install usage) + if (process.env.DOMAIN_NAME) { + const domainName = process.env.DOMAIN_NAME.trim(); + if (validateDomainName(domainName)) { + return domainName; + } else { + console.error(`❌ Invalid domain name in DOMAIN_NAME environment variable: $core`); + process.exit(1); + } + } + + // Otherwise prompt for it + return await promptDomainName(); +} + +// Replace core in a file +function replaceInFile(filePath, domainName) { + try { + let content = fs.readFileSync(filePath, 'utf8'); + const originalContent = content; + content = content.replace(/\{domainName\}/g, domainName); + + if (content !== originalContent) { + fs.writeFileSync(filePath, content, 'utf8'); + return true; + } + return false; + } catch (error) { + console.warn(`⚠️ Warning: Could not process ${filePath}: ${error.message}`); + return false; + } +} + +// Replace core in all files recursively +function replaceInDirectory(dirPath, domainName, processedFiles = new Set()) { + if (!fs.existsSync(dirPath)) { + return; + } + + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dirPath, entry.name); + const relativePath = path.relative('.', fullPath); + + // Skip node_modules, .git, and other common directories + if (entry.name === 'node_modules' || + entry.name === '.git' || + entry.name === 'dist' || + entry.name === '.github' || + entry.name.startsWith('.')) { + continue; + } + + if (entry.isDirectory()) { + replaceInDirectory(fullPath, domainName, processedFiles); + } else if (entry.isFile()) { + // Process text files + const ext = path.extname(entry.name).toLowerCase(); + if (['.json', '.js', '.md', '.sh', '.txt', '.yml', '.yaml'].includes(ext) || + entry.name === '.gitignore' || + entry.name === '.gitattributes') { + if (!processedFiles.has(relativePath)) { + if (replaceInFile(fullPath, domainName)) { + processedFiles.add(relativePath); + } + } + } + } + } +} + +// Rename core directory +function renameDomainDirectory(domainName) { + const templateDir = 'core'; + const targetDir = domainName; + + if (fs.existsSync(templateDir)) { + if (fs.existsSync(targetDir)) { + console.error(`❌ Error: Directory ${targetDir} already exists`); + process.exit(1); + } + fs.renameSync(templateDir, targetDir); + console.log(` ✓ Renamed ${templateDir}/ to ${targetDir}/`); + return true; + } + return false; +} + +// Check if running in node_modules (installed as dependency) +function isInstalledAsDependency() { + const cwd = process.cwd(); + return cwd.includes('node_modules'); +} + +// Main setup function +async function setup() { + // Skip if installed as a dependency + if (isInstalledAsDependency()) { + return; + } + + // Check if already set up first + if (isAlreadySetup()) { + return; + } + + // Check if core directory exists (template not yet configured) + if (!fs.existsSync('core')) { + console.log('⚠️ Template directory core not found'); + console.log(' This might already be a configured project.\n'); + return; + } + + console.log('🚀 vNext Template Setup'); + console.log('=======================\n'); + + // Get domain name from command line or prompt + const domainName = await getDomainName(); + console.log(`\n📝 Setting up domain: $core\n`); + + // Replace in all files + console.log('🔄 Replacing core in files...'); + const processedFiles = new Set(); + replaceInDirectory('.', domainName, processedFiles); + console.log(` ✓ Processed ${processedFiles.size} file(s)`); + + // Rename directory + console.log('\n📁 Renaming domain directory...'); + renameDomainDirectory(domainName); + + console.log('\n✅ Setup complete!'); + console.log(`\nYour domain "$core" is now configured.`); + console.log('You can start adding your schemas, workflows, tasks, and other components.\n'); +} + +// Run setup +setup().catch((error) => { + console.error('❌ Setup failed:', error.message); + process.exit(1); +}); + diff --git a/sync-schema-version.js b/sync-schema-version.js new file mode 100644 index 0000000..7e946b0 --- /dev/null +++ b/sync-schema-version.js @@ -0,0 +1,58 @@ +const fs = require('fs'); +const path = require('path'); + +// Read vnext.config.json +const vnextConfigPath = path.join(__dirname, 'vnext.config.json'); +const packageJsonPath = path.join(__dirname, 'package.json'); + +try { + // Read and parse vnext.config.json + const vnextConfig = JSON.parse(fs.readFileSync(vnextConfigPath, 'utf8')); + const schemaVersion = vnextConfig.schemaVersion; + + if (!schemaVersion) { + console.error('Error: schemaVersion not found in vnext.config.json'); + process.exit(1); + } + + // Read and parse package.json + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + + // Update package.json dependencies + if (!packageJson.dependencies) { + packageJson.dependencies = {}; + } + + const schemaPackage = '@burgan-tech/vnext-schema'; + const versionRange = `^${schemaVersion}`; + + let packageJsonChanged = false; + + // Check if version needs updating + const currentVersion = packageJson.dependencies[schemaPackage]; + if (currentVersion !== versionRange) { + packageJson.dependencies[schemaPackage] = versionRange; + + // Write updated package.json + fs.writeFileSync( + packageJsonPath, + JSON.stringify(packageJson, null, 2) + '\n', + 'utf8' + ); + + console.log(`✓ Updated ${schemaPackage} to ${versionRange} in package.json`); + packageJsonChanged = true; + } else { + console.log(`✓ ${schemaPackage} is already at ${versionRange}`); + } + + // If package.json was changed, warn that npm install may need to be run again + if (packageJsonChanged) { + console.log('\n⚠️ package.json was updated. Run "npm install" to install the updated dependency.'); + } + +} catch (error) { + console.error('Error syncing schema version:', error.message); + process.exit(1); +} + diff --git a/test.js b/test.js index d26aab0..70e2d9d 100644 --- a/test.js +++ b/test.js @@ -60,7 +60,7 @@ test('getAvailableTypes returns expected array', () => { throw new Error('getAvailableTypes should return an array'); } - const expectedTypes = ['schemas', 'workflows', 'tasks', 'views', 'functions', 'extensions']; + const expectedTypes = ['Schemas', 'Workflows', 'Tasks', 'Views', 'Functions', 'Extensions']; for (const type of expectedTypes) { if (!types.includes(type)) { throw new Error(`Expected type '${type}' not found in available types`); diff --git a/validate.js b/validate.js index 595c628..5e735cb 100644 --- a/validate.js +++ b/validate.js @@ -2,6 +2,409 @@ const fs = require('fs'); const path = require('path'); +const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); + +// ANSI color codes for terminal output +const colors = { + reset: '\x1b[0m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + bright: '\x1b[1m', + dim: '\x1b[2m' +}; + +// Helper function to colorize text +function colorize(text, color) { + return `${colors[color]}${text}${colors.reset}`; +} + +// Helper function to create clickable file links (VS Code terminal format) +function createFileLink(filePath, line = null, column = null) { + const absolutePath = path.resolve(filePath); + let link = `file://${absolutePath}`; + if (line !== null) { + link += `:${line}`; + if (column !== null) { + link += `:${column}`; + } + } + return link; +} + +// Helper function to parse line/column from error messages +function parseErrorLocation(errorMessage) { + // Match patterns like "line 94 column 107" or "at position 3200 (line 94 column 107)" + const lineMatch = errorMessage.match(/line\s+(\d+)/i); + const columnMatch = errorMessage.match(/column\s+(\d+)/i); + + if (lineMatch) { + return { + line: parseInt(lineMatch[1], 10), + column: columnMatch ? parseInt(columnMatch[1], 10) : null + }; + } + return null; +} + +// Helper function to find line number for a JSON path in a file +function findLineNumberForPath(filePath, jsonPath) { + try { + const fileContent = fs.readFileSync(filePath, 'utf8'); + const lines = fileContent.split('\n'); + + // Parse the JSON path (e.g., "/attributes/states/0/transitions/1") + const pathParts = jsonPath.split('/').filter(part => part.length > 0); + + if (pathParts.length === 0) { + return null; + } + + // Parse JSON to get actual structure + let jsonData; + try { + jsonData = JSON.parse(fileContent); + } catch (e) { + // If JSON parsing fails, fall back to text search + return findLineNumberByTextSearch(lines, pathParts); + } + + // Navigate through the JSON structure to find the path + let current = jsonData; + for (let i = 0; i < pathParts.length; i++) { + const part = pathParts[i]; + + // Check if part is an array index + if (/^\d+$/.test(part)) { + const index = parseInt(part, 10); + if (Array.isArray(current) && index < current.length) { + current = current[index]; + } else { + return null; + } + } else if (current && typeof current === 'object' && part in current) { + current = current[part]; + } else { + // Path doesn't exist, try text search for the last part + return findLineNumberByTextSearch(lines, pathParts.slice(i)); + } + } + + // Now find the line number where this value appears + return findValueLineNumber(lines, pathParts, jsonData); + } catch (error) { + // Fallback to text search + try { + const fileContent = fs.readFileSync(filePath, 'utf8'); + const lines = fileContent.split('\n'); + const pathParts = jsonPath.split('/').filter(part => part.length > 0); + return findLineNumberByTextSearch(lines, pathParts); + } catch (e) { + return null; + } + } +} + +// Helper to find line number by searching for property names in text +function findLineNumberByTextSearch(lines, pathParts) { + if (pathParts.length === 0) return null; + + const targetProperty = pathParts[pathParts.length - 1]; + + // For array indices, search for the parent property + if (/^\d+$/.test(targetProperty)) { + if (pathParts.length > 1) { + const parentProperty = pathParts[pathParts.length - 2]; + // Search for parent property and count array elements + let foundParent = false; + let arrayDepth = 0; + let elementIndex = 0; + const targetIndex = parseInt(targetProperty, 10); + + for (let lineNum = 0; lineNum < lines.length; lineNum++) { + const line = lines[lineNum]; + + if (line.includes(`"${parentProperty}"`)) { + foundParent = true; + } + + if (foundParent) { + if (line.includes('[')) { + arrayDepth++; + } + if (line.includes(']')) { + arrayDepth--; + if (arrayDepth === 0 && elementIndex === targetIndex) { + return lineNum + 1; + } + if (arrayDepth === 0) { + elementIndex++; + } + } + if (arrayDepth > 0 && line.includes('{') && elementIndex === targetIndex) { + return lineNum + 1; + } + } + } + } + return null; + } + + // For regular properties, search for the property name + for (let lineNum = 0; lineNum < lines.length; lineNum++) { + const line = lines[lineNum]; + // Look for "propertyName": pattern + if (line.match(new RegExp(`"${targetProperty.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:`))) { + return lineNum + 1; + } + } + + return null; +} + +// Helper to find line number of a value in JSON structure +function findValueLineNumber(lines, pathParts, jsonData) { + // Navigate to the value + let current = jsonData; + for (const part of pathParts) { + if (/^\d+$/.test(part)) { + current = current[parseInt(part, 10)]; + } else { + current = current[part]; + } + } + + // Search for the property name in the file + const targetProperty = pathParts[pathParts.length - 1]; + + // For array indices, find the array element + if (/^\d+$/.test(targetProperty)) { + const parentProperty = pathParts[pathParts.length - 2]; + return findArrayElementLine(lines, parentProperty, parseInt(targetProperty, 10)); + } + + // For regular properties, find the property line + for (let lineNum = 0; lineNum < lines.length; lineNum++) { + if (lines[lineNum].match(new RegExp(`"${targetProperty.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:`))) { + return lineNum + 1; + } + } + + return null; +} + +// Helper to find line number of an array element +function findArrayElementLine(lines, arrayProperty, elementIndex) { + let foundArray = false; + let bracketDepth = 0; + let currentIndex = 0; + + for (let lineNum = 0; lineNum < lines.length; lineNum++) { + const line = lines[lineNum]; + + // Find the array property + if (!foundArray && line.includes(`"${arrayProperty}"`)) { + foundArray = true; + continue; + } + + if (foundArray) { + // Count brackets to track array depth + for (const char of line) { + if (char === '[') { + bracketDepth++; + if (bracketDepth === 1) { + // Start of array + continue; + } + } else if (char === ']') { + bracketDepth--; + if (bracketDepth === 0) { + // End of array + break; + } + } else if (bracketDepth === 1 && char === '{') { + // Found an object in the array + if (currentIndex === elementIndex) { + return lineNum + 1; + } + currentIndex++; + } + } + + // Check if we're at the target index + if (bracketDepth === 1 && currentIndex === elementIndex && line.trim().startsWith('{')) { + return lineNum + 1; + } + } + } + + return null; +} + +// Helper function to find line number for error in JSON file +function findErrorLineNumber(filePath, err) { + try { + const fileContent = fs.readFileSync(filePath, 'utf8'); + const lines = fileContent.split('\n'); + + // Get the error path + const errPath = err.instancePath || err.dataPath || ''; + + if (!errPath) { + // For root-level errors (like additionalProperty), search for the property + if (err.params && err.params.additionalProperty) { + const prop = err.params.additionalProperty; + for (let i = 0; i < lines.length; i++) { + if (lines[i].match(new RegExp(`"${prop.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:`))) { + return i + 1; + } + } + } + return null; + } + + // Parse JSON path and find the line where the property appears + const pathParts = errPath.split('/').filter(part => part.length > 0); + + if (pathParts.length === 0) return null; + + // For nested paths, find the line where the target property/object appears + // Navigate through the path to find the actual line in the file + let currentDepth = 0; + let pathIndex = 0; + let inString = false; + let escapeNext = false; + let currentKey = ''; + let bracketDepth = 0; + let braceDepth = 0; + let arrayIndex = 0; + let foundPath = false; + + for (let lineNum = 0; lineNum < lines.length; lineNum++) { + const line = lines[lineNum]; + + for (let i = 0; i < line.length; i++) { + const char = line[i]; + + if (escapeNext) { + escapeNext = false; + continue; + } + + if (char === '\\') { + escapeNext = true; + continue; + } + + if (char === '"' && !escapeNext) { + inString = !inString; + if (!inString && currentKey) { + // Check if this key matches our path + if (pathIndex < pathParts.length && currentKey === pathParts[pathIndex]) { + pathIndex++; + if (pathIndex === pathParts.length) { + // Found the target path + return lineNum + 1; + } + } + currentKey = ''; + } + continue; + } + + if (!inString) { + if (char === '{') { + braceDepth++; + } else if (char === '}') { + braceDepth--; + if (braceDepth < currentDepth) { + // Reset path tracking when exiting a level + if (pathIndex > 0 && braceDepth < pathIndex) { + pathIndex = Math.max(0, pathIndex - 1); + } + } + } else if (char === '[') { + bracketDepth++; + // Check if we're at an array index in the path + if (pathIndex < pathParts.length && /^\d+$/.test(pathParts[pathIndex])) { + const targetIndex = parseInt(pathParts[pathIndex], 10); + if (arrayIndex === targetIndex && bracketDepth === 1) { + pathIndex++; + if (pathIndex === pathParts.length) { + return lineNum + 1; + } + } + } + } else if (char === ']') { + bracketDepth--; + if (bracketDepth === 0) { + arrayIndex++; + } + } else if (char === ':' && currentKey) { + currentKey = ''; + } else if (char.match(/[a-zA-Z0-9_]/) && !inString) { + if (i === 0 || line[i-1] === '"' || (i > 0 && line[i-1].match(/[^a-zA-Z0-9_]/))) { + currentKey += char; + } + } + } else { + if (char.match(/[a-zA-Z0-9_]/)) { + currentKey += char; + } + } + } + } + + // Fallback: search for the last property in the path + const lastProperty = pathParts[pathParts.length - 1]; + if (!/^\d+$/.test(lastProperty)) { + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes(`"${lastProperty}"`)) { + return i + 1; + } + } + } + + return null; + } catch (error) { + return null; + } +} + +// Helper function to format error message with better handling of additionalProperty +function formatErrorMessage(err, filePath = null) { + let message = err.message; + let lineNumber = null; + + // Find line number in the JSON file where the error occurs + if (filePath) { + lineNumber = findErrorLineNumber(filePath, err); + } + + // Handle "must NOT have additional properties" with better formatting + if (message.includes('must NOT have additional properties') && err.params && err.params.additionalProperty) { + const prop = err.params.additionalProperty; + message = `must NOT have additional property ${colorize(`"${prop}"`, 'yellow')}`; + } + + // Handle "must have required property" with better formatting + if (message.includes('must have required property') && err.params && err.params.missingProperty) { + const prop = err.params.missingProperty; + message = `must have required property ${colorize(`"${prop}"`, 'yellow')}`; + } + + // Add line number if found + if (lineNumber !== null) { + message += ` ${colorize(`(line ${lineNumber})`, 'dim')}`; + } + + return message; +} // Validation script for vnext-template package console.log('🔍 Running vnext-template validation...'); @@ -9,6 +412,16 @@ console.log('🔍 Running vnext-template validation...'); let validationsPassed = 0; let validationsFailed = 0; +// Track schema validation statistics +let schemaValidationStats = { + filesValidated: 0, + filesPassed: 0, + filesFailed: 0, + enabled: false, + passedFiles: [], + failedFiles: [] +}; + function validate(description, validationFunction) { try { console.log(`\n🔍 Validating: ${description}`); @@ -136,6 +549,7 @@ validate('vnext.config.json validation', () => { validate('Domain directory structure', () => { const vnextTemplate = require('./index.js'); const domainName = vnextTemplate.getDomainName(); + const pathsConfig = vnextTemplate.getPathsConfig(); if (!domainName) { console.log(` ⚠ No domain directory found (template will be empty)`); @@ -146,8 +560,15 @@ validate('Domain directory structure', () => { throw new Error(`Domain directory $core does not exist`); } - // Check for vnext structure directories - const vnextDirs = ['Schemas', 'Workflows', 'Tasks', 'Views', 'Functions', 'Extensions']; + // Check for vnext structure directories using paths config + const vnextDirs = [ + pathsConfig.schemas, + pathsConfig.workflows, + pathsConfig.tasks, + pathsConfig.views, + pathsConfig.functions, + pathsConfig.extensions + ]; const existingDirs = vnextDirs.filter(dir => fs.existsSync(path.join(domainName, dir))); if (existingDirs.length === 0) { @@ -172,6 +593,11 @@ validate('JSON files syntax validation', () => { const entries = fs.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { + // Skip .meta directory + if (entry.name === '.meta') { + continue; + } + const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { @@ -205,22 +631,239 @@ validate('JSON files syntax validation', () => { return true; }); +// Validation 5b: JSON files schema validation using @burgan-tech/vnext-schema +validate('JSON files schema validation using @burgan-tech/vnext-schema', () => { + let vnextSchema; + try { + vnextSchema = require('@burgan-tech/vnext-schema'); + schemaValidationStats.enabled = true; + } catch (error) { + console.log(` ⚠ @burgan-tech/vnext-schema package not available: ${error.message}`); + console.log(` ⚠ Skipping schema validation (syntax validation still performed)`); + return true; // Don't fail if package is not available + } + + const vnextTemplate = require('./index.js'); + const domainName = vnextTemplate.getDomainName(); + const pathsConfig = vnextTemplate.getPathsConfig(); + + if (!domainName || !fs.existsSync(domainName)) { + console.log(` ⚠ No domain directory found, skipping schema validation`); + return true; + } + + // Initialize AJV with formats support + const ajv = new Ajv({ + strict: false, // Allow unknown keywords like enumDescriptions + allErrors: true, // Collect all errors + verbose: true // Include schema path in errors + }); + addFormats(ajv); + + // Map directory names to schema types dynamically from paths config + const directoryToSchemaType = { + [pathsConfig.schemas]: 'schema', + [pathsConfig.workflows]: 'workflow', + [pathsConfig.tasks]: 'task', + [pathsConfig.views]: 'view', + [pathsConfig.functions]: 'function', + [pathsConfig.extensions]: 'extension' + }; + + // Get available schema types + const availableTypes = vnextSchema.getAvailableTypes ? vnextSchema.getAvailableTypes() : []; + + // Compile validators for each schema type + const validators = {}; + for (const [dirName, schemaType] of Object.entries(directoryToSchemaType)) { + if (availableTypes.includes(schemaType)) { + try { + const schema = vnextSchema.getSchema ? vnextSchema.getSchema(schemaType) : null; + if (schema) { + validators[dirName] = { + validator: ajv.compile(schema), + type: schemaType + }; + } + } catch (error) { + console.log(` ⚠ Warning: Could not compile validator for ${schemaType}: ${error.message}`); + } + } + } + + if (Object.keys(validators).length === 0) { + console.log(` ⚠ No validators available, skipping schema validation`); + return true; + } + + let validatedCount = 0; + let errorCount = 0; + const errors = []; + const passedFiles = []; + + // Validate JSON files against schemas + const validateJsonAgainstSchema = (dirPath, domainPath) => { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + + for (const entry of entries) { + // Skip .meta directory + if (entry.name === '.meta') { + continue; + } + + const fullPath = path.join(dirPath, entry.name); + + if (entry.isDirectory()) { + validateJsonAgainstSchema(fullPath, domainPath); + } else if (entry.isFile() && entry.name.endsWith('.json')) { + // Determine which schema type to use based on directory path + // Check if any of the parent directories match our schema directories + let validator = null; + let schemaType = null; + + const relativePath = path.relative(domainPath, path.dirname(fullPath)); + const pathParts = relativePath.split(path.sep); + + // Find the first matching directory in the path + for (const part of pathParts) { + if (validators[part]) { + validator = validators[part]; + schemaType = validator.type; + break; + } + } + + if (validator) { + validatedCount++; + try { + const jsonContent = JSON.parse(fs.readFileSync(fullPath, 'utf8')); + const valid = validator.validator(jsonContent); + + if (valid) { + // Track passed files + passedFiles.push({ + file: fullPath, + type: schemaType + }); + } else { + errorCount++; + const validationErrors = validator.validator.errors || []; + const errorMessages = validationErrors.map(err => { + const errPath = err.instancePath || err.dataPath || ''; + const pathColor = errPath ? 'cyan' : 'dim'; + const messageColor = 'red'; + const paramsColor = 'yellow'; + + // Format error message with better handling and line numbers + const formattedMessage = formatErrorMessage(err, fullPath); + + let output = ''; + if (errPath) { + output += colorize(errPath, pathColor) + ': '; + } + output += colorize(formattedMessage, messageColor); + + // Show params if they exist and aren't already included in the message + if (err.params && !err.params.additionalProperty && !err.params.missingProperty) { + output += ' ' + colorize('(' + JSON.stringify(err.params) + ')', paramsColor); + } + + return ` ${output}`; + }).join('\n'); + + errors.push({ + file: fullPath, + type: schemaType, + message: `Schema validation failed for ${schemaType}:\n${errorMessages}` + }); + } + } catch (error) { + errorCount++; + // Parse line/column from JSON parse errors + const location = parseErrorLocation(error.message); + + errors.push({ + file: fullPath, + type: schemaType || 'unknown', + message: `Error validating file: ${error.message}`, + location: location + }); + } + } + } + } + }; + + validateJsonAgainstSchema(domainName, domainName); + + // Update schema validation statistics + schemaValidationStats.filesValidated = validatedCount; + schemaValidationStats.filesPassed = passedFiles.length; + schemaValidationStats.filesFailed = errorCount; + schemaValidationStats.passedFiles = passedFiles; + schemaValidationStats.failedFiles = errors; + + // Display failed files if any + if (errorCount > 0) { + console.log(colorize(` ❌ Schema validation failed for ${errorCount} file(s):`, 'red')); + errors.forEach(err => { + console.log(`\n ${colorize('File:', 'bright')} ${err.file}`); + if (err.location) { + console.log(` ${colorize('Location:', 'bright')} line ${colorize(err.location.line, 'yellow')}${err.location.column ? `, column ${colorize(err.location.column, 'yellow')}` : ''}`); + } + console.log(` ${colorize('Type:', 'bright')} ${colorize(err.type, 'magenta')}`); + console.log(` ${err.message}`); + }); + } + + // Display passed files similar to failed files (always show if any passed) + if (passedFiles.length > 0) { + if (errorCount > 0) { + console.log(''); // Add spacing between failed and passed sections + } + console.log(colorize(` ✓ Schema validation passed for ${passedFiles.length} file(s):`, 'green')); + passedFiles.forEach(passed => { + console.log(`\n ${colorize('File:', 'bright')} ${passed.file}`); + console.log(` ${colorize('Type:', 'bright')} ${colorize(passed.type, 'magenta')}`); + console.log(` ${colorize('✓ Valid', 'green')}`); + }); + } else if (validatedCount === 0) { + console.log(` ⚠ No files found to validate against schemas`); + } + + // Throw error if there are validation failures + if (errorCount > 0) { + throw new Error(`Schema validation failed for ${errorCount} file(s)`); + } + + return true; +}); + // Validation 6: Module functionality test validate('Module functionality test', () => { const vnextTemplate = require('./index.js'); // Test basic functionality - const config = vnextTemplate.getDomainConfig(); + const pathsConfig = vnextTemplate.getPathsConfig(); const availableTypes = vnextTemplate.getAvailableTypes(); if (!Array.isArray(availableTypes)) { throw new Error('getAvailableTypes must return an array'); } - const expectedTypes = ['schemas', 'workflows', 'tasks', 'views', 'functions', 'extensions']; - for (const type of expectedTypes) { - if (!availableTypes.includes(type)) { - throw new Error(`Missing expected type: ${type}`); + // Verify availableTypes contains expected directory names from paths config + const expectedDirs = [ + pathsConfig.schemas, + pathsConfig.workflows, + pathsConfig.tasks, + pathsConfig.views, + pathsConfig.functions, + pathsConfig.extensions + ]; + + for (const dir of expectedDirs) { + if (!availableTypes.includes(dir)) { + throw new Error(`Missing expected directory in availableTypes: ${dir}`); } } @@ -243,6 +886,7 @@ validate('Module functionality test', () => { console.log(` ✓ All component getters working`); console.log(` ✓ Available types: ${availableTypes.join(', ')}`); + console.log(` ✓ Paths config loaded from vnext.config.json`); return true; }); @@ -290,10 +934,50 @@ validate('Semantic versioning compliance', () => { }); // Print validation results -console.log('\n📊 Validation Results:'); -console.log(`✅ Passed: ${validationsPassed}`); -console.log(`❌ Failed: ${validationsFailed}`); -console.log(`📈 Total: ${validationsPassed + validationsFailed}`); +console.log('\n' + '═'.repeat(60)); +console.log('📊 Validation Results:'); +console.log('═'.repeat(60)); +console.log(` ✅ Passed: ${colorize(validationsPassed, 'green')}`); +console.log(` ❌ Failed: ${colorize(validationsFailed, 'red')}`); +console.log(` 📈 Total: ${validationsPassed + validationsFailed}`); + +// Print schema validation statistics if enabled +if (schemaValidationStats.enabled) { + console.log('\n' + '─'.repeat(60)); + console.log('📋 Schema Validation Statistics:'); + console.log('─'.repeat(60)); + console.log(` Files validated: ${colorize(schemaValidationStats.filesValidated, 'cyan')}`); + console.log(` ${colorize('✓ Passed:', 'green')} ${colorize(schemaValidationStats.filesPassed, 'green')}`); + console.log(` ${colorize('✗ Failed:', 'red')} ${colorize(schemaValidationStats.filesFailed, 'red')}`); + + // Print failed files summary if any + if (schemaValidationStats.failedFiles.length > 0) { + console.log('\n' + '─'.repeat(60)); + console.log(colorize('❌ Failed Files Summary:', 'red')); + console.log('─'.repeat(60)); + + schemaValidationStats.failedFiles.forEach((err, index) => { + const fileName = path.basename(err.file); + const relativePath = err.file; + + if (index > 0) { + console.log(''); // Line break between files + } + + console.log(` ${colorize((index + 1) + '.', 'bright')} ${colorize(fileName, 'yellow')}`); + console.log(` Path: ${colorize(relativePath, 'dim')}`); + console.log(` Type: ${colorize(err.type, 'magenta')}`); + + if (err.location) { + console.log(` Line: ${colorize(err.location.line, 'cyan')}${err.location.column ? `, Column: ${colorize(err.location.column, 'cyan')}` : ''}`); + } + }); + + console.log('─'.repeat(60)); + } +} + +console.log('═'.repeat(60)); if (validationsFailed > 0) { console.log('\n❌ Validation failed! Please fix the issues above.'); diff --git a/vnext.config.json b/vnext.config.json index 3e6a4c5..e2cf59a 100644 --- a/vnext.config.json +++ b/vnext.config.json @@ -2,8 +2,8 @@ "version": "1.0.0", "description": "core Domain Definition Configuration", "domain": "core", - "runtimeVersion": "0.0.20", - "schemaVersion": "0.0.25", + "runtimeVersion": "0.0.22", + "schemaVersion": "0.0.27", "paths": { "componentsRoot": "core", "tasks": "Tasks", From 462743ceeac0721175fbd38fadb568318dbc5dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tayfun=20Y=C4=B1lmaz?= Date: Thu, 4 Dec 2025 02:00:37 +0300 Subject: [PATCH 2/2] Switch vNext CLI usage to local npm scripts Replaces global vNext CLI commands with local npm scripts for validation and build steps in the GitHub Actions workflow. Removes global installation of vNext CLI and updates commands to use 'npm run validate' and 'npm run build' with appropriate arguments. --- .github/workflows/build-and-publish.yml | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 0e27e2a..fd5d8b2 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -82,16 +82,9 @@ jobs: fi echo "Dependencies installed successfully" - - name: Install vNext CLI - run: | - echo "Installing vNext CLI globally..." - npm install -g @burgan-tech/vnext-cli - echo "vNext CLI version:" - vnext --version - - name: Validate vNext components run: | - echo "🔍 Validating vNext components with reference resolution..." + echo "🔍 Validating vNext components..." # Temporarily rename diagram files to exclude them from validation echo "📋 Excluding diagram files from validation..." @@ -107,9 +100,9 @@ jobs: echo " No diagram files found to exclude" fi - # Run validation with reference resolution + # Run validation using local npm script VALIDATION_EXIT_CODE=0 - if vnext validate --resolve-refs; then + if npm run validate; then echo "✅ vNext validation completed successfully" else VALIDATION_EXIT_CODE=$? @@ -152,8 +145,8 @@ jobs: run: | echo "🏗️ Building Reference package..." - # Build reference package - if vnext build --type reference --output dist-reference; then + # Build reference package using local npm script (skip validation as it's already done) + if npm run build -- -t reference -o dist-reference --skip-validation; then echo "✅ Reference package built successfully" # List contents of reference build @@ -168,8 +161,8 @@ jobs: run: | echo "🏗️ Building Runtime package..." - # Build runtime package - if vnext build --type runtime --output dist-runtime; then + # Build runtime package using local npm script (skip validation as it's already done) + if npm run build -- -t runtime -o dist-runtime --skip-validation; then echo "✅ Runtime package built successfully" # List contents of runtime build