├── .forceignore ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── README.md │ ├── beta_create.yml │ ├── beta_promote.yml │ ├── feature_test.yml │ └── package_install.yml ├── .gitignore ├── README.md ├── category ├── apex │ └── default.xml └── xml │ ├── default.md │ └── default.xml ├── cumulusci.yml ├── datasets └── mapping.yml ├── force-app └── main │ └── default │ ├── classes │ ├── OpenAPIParser.cls │ ├── OpenAPIParser.cls-meta.xml │ ├── OpenAPIParserBatch.cls │ ├── OpenAPIParserBatch.cls-meta.xml │ ├── OpenAPIParserModular.cls │ ├── OpenAPIParserModular.cls-meta.xml │ ├── OpenAPIParserModularBatch.cls │ ├── OpenAPIParserModularBatch.cls-meta.xml │ ├── OpenAPIStorageQueueable.cls │ └── OpenAPIStorageQueueable.cls-meta.xml │ ├── lwc │ └── swaggerViewer │ │ ├── swaggerViewer.css │ │ ├── swaggerViewer.html │ │ ├── swaggerViewer.js │ │ └── swaggerViewer.js-meta.xml │ ├── remoteSiteSettings │ └── editor_swagger.remoteSite-meta.xml │ └── staticresources │ ├── swaggerui.resource-meta.xml │ └── swaggerui │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── index.css │ ├── index.html │ ├── oauth2-redirect.html │ ├── swagger-initializer.js │ ├── swagger-ui-bundle.js │ ├── swagger-ui-bundle.js.map │ ├── swagger-ui-es-bundle-core.js │ ├── swagger-ui-es-bundle-core.js.map │ ├── swagger-ui-es-bundle.js │ ├── swagger-ui-es-bundle.js.map │ ├── swagger-ui-standalone-preset.js │ ├── swagger-ui-standalone-preset.js.map │ ├── swagger-ui.css │ ├── swagger-ui.css.map │ ├── swagger-ui.js │ └── swagger-ui.js.map ├── orgs ├── beta.json ├── dev.json ├── feature.json └── release.json ├── pmd-rules.xml ├── robot └── OpenAPI-Unlocked │ └── tests │ └── create_contact.robot ├── scripts └── GenerateSpec.apex ├── sfdx-project.json ├── tasks └── openapi_spec.py └── unpackaged ├── experience ├── src │ ├── digitalExperienceConfigs │ │ └── API1.digitalExperienceConfig-meta.xml │ ├── digitalExperiences │ │ └── site │ │ │ └── API1 │ │ │ ├── API1.digitalExperience-meta.xml │ │ │ ├── sfdc_cms__route │ │ │ ├── Check_Password │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ ├── Error │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ ├── Forgot_Password │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ ├── Home │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ ├── Login │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ ├── News_Detail__c │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ ├── Register │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ ├── Service_Not_Available │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ ├── Too_Many_Requests │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ └── docs__c │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ ├── sfdc_cms__site │ │ │ └── API1 │ │ │ │ ├── _meta.json │ │ │ │ └── content.json │ │ │ └── sfdc_cms__view │ │ │ ├── Docs │ │ │ ├── _meta.json │ │ │ └── content.json │ │ │ ├── checkPasswordResetEmail │ │ │ ├── _meta.json │ │ │ └── content.json │ │ │ ├── error │ │ │ ├── _meta.json │ │ │ └── content.json │ │ │ ├── forgotPassword │ │ │ ├── _meta.json │ │ │ └── content.json │ │ │ ├── home │ │ │ ├── _meta.json │ │ │ └── content.json │ │ │ ├── login │ │ │ ├── _meta.json │ │ │ └── content.json │ │ │ ├── newsDetail │ │ │ ├── _meta.json │ │ │ └── content.json │ │ │ ├── register │ │ │ ├── _meta.json │ │ │ └── content.json │ │ │ ├── serviceNotAvailable │ │ │ ├── _meta.json │ │ │ └── content.json │ │ │ └── tooManyRequests │ │ │ ├── _meta.json │ │ │ └── content.json │ ├── navigationMenus │ │ └── SFDC_Default_Navigation_API.navigationMenu-meta.xml │ ├── networkBranding │ │ ├── cbAPI.networkBranding │ │ └── cbAPI.networkBranding-meta.xml │ ├── networks │ │ └── API.network-meta.xml │ └── sites │ │ └── API.site-meta.xml └── users │ ├── guest.json │ └── owner.json ├── post └── classes │ ├── AccountAPI.cls │ ├── AccountAPI.cls-meta.xml │ ├── ContactAPI.cls │ ├── ContactAPI.cls-meta.xml │ ├── TestAPI.cls │ └── TestAPI.cls-meta.xml └── pre ├── connectedApps └── OpenAPIUnlocked.connectedApp-meta.xml └── staticresources ├── OPENAPI_SPEC.json └── OPENAPI_SPEC.resource-meta.xml /.forceignore: -------------------------------------------------------------------------------- 1 | **/jsconfig.json 2 | 3 | **/.eslintrc.json 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Critical Changes 4 | 5 | # Changes 6 | 7 | # Issues Closed 8 | -------------------------------------------------------------------------------- /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # Using these workflows 2 | 3 | ## Initial Setup 4 | - [x] Navigate to Your Repository > Settings > Secrets and Actions > Actions 5 | - [x] Create a new Repository Secret: `DEV_HUB_AUTH_URL`, and populate it with your Dev Hub's `sfdxAuthUrl` 6 | - [x] [Optional] Create a new Repository Secret: `BETA_ORG_AUTH_URL`, and populate it with your UAT Sandbox's `sfdxAuthUrl` 7 | - [x] [Optional] Create a new Repository Secret: `PROD_ORG_AUTH_URL`, and populate it with your Production Org's `sfdxAuthUrl` 8 | 9 | ([How do I obtain an `sfdxAuthUrl`?](https://github.com/Nimba-Solutions/.github/wiki/Obtain-an-SFDX-Auth-URL)) 10 | 11 | ## Releases 12 | 13 | ### [Recommended] Release this project using the Built-in CICD Actions 14 | 15 | #### [Automatic] Generate Feature Test Packages & Beta Packages 16 | 1. [Contribute to this Project normally.](/README.md#development) 17 | 2. Confirm that the built-in GitHub Actions are running when Tasks are submitted for testing: 18 | - `Test Feature (Unlocked)` should run when a `feature/**` Pull Request is opened, or subsequent commits are made. 19 | - `Beta - Create (Unlocked)` should run when any Pull Request is made against the `main` branch. 20 | 21 | #### [Manual] Promote the Latest Beta Package 22 | 1. Navigate to Your Repository > Actions > `Beta - Promote (Unlocked)`. 23 | 2. Click `Run Workflow`. 24 | 3. Confirm. 25 | 26 | #### [Manual] Install the Latest Promoted Package 27 | 1. Navigate to Your Repository > Actions > `Package - Install (Unlocked)`. 28 | 2. Click `Run Workflow`. 29 | 3. Select `Sandbox` or `Production`. 30 | 4. Confirm. 31 | 32 | Note: Depending on the configuration of your GitHub Organization, you may need to specify some or all of the additional `GITHUB_TOKEN` permissions for these workflows to run successfully: 33 | 34 | ```yml 35 | permissions: 36 | actions: write 37 | attestations: write 38 | checks: write 39 | contents: write 40 | deployments: write 41 | discussions: write 42 | issues: write 43 | packages: write 44 | pages: write 45 | pull-requests: write 46 | repository-projects: write 47 | security-events: write 48 | statuses: write 49 | ``` 50 | -------------------------------------------------------------------------------- /.github/workflows/beta_create.yml: -------------------------------------------------------------------------------- 1 | # This workflow generates a new Beta Package Version and installs it into a Validation Org. 2 | # It also generates a corresponding Github Release and Tag. 3 | # Beta Package Versions can only be installed into scratch orgs and sandboxes. 4 | # To enable it for production installation you need to run the `Beta (Unlocked) - Promote` workflow. 5 | 6 | name: Beta - Create (Unlocked) 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - "main" 12 | # Only generate a new package if the contents of the following directories have been changed. 13 | paths: 14 | - "force-app/**" 15 | - "unpackaged/**" 16 | - "datasets/**" 17 | jobs: 18 | upload-beta: 19 | uses: nimba-actions/standard-workflows/.github/workflows/beta-unlocked.yml@main 20 | secrets: 21 | cci-token: ${{ secrets.CUMULUSCI_TOKEN }} 22 | dev-hub-auth-url: ${{ secrets.DEV_HUB_AUTH_URL }} 23 | with: 24 | cumulusci-version: "3.78.0" 25 | sfdx-version: "7.209.6" 26 | org-name: "dev" 27 | debug: true 28 | install-beta: 29 | needs: upload-beta 30 | uses: nimba-actions/standard-workflows/.github/workflows/install-beta.yml@main 31 | secrets: 32 | cci-token: ${{ secrets.CUMULUSCI_TOKEN }} 33 | dev-hub-auth-url: ${{ secrets.DEV_HUB_AUTH_URL }} 34 | with: 35 | cumulusci-version: "3.90.1" 36 | sfdx-version: "7.209.6" 37 | debug: true 38 | 39 | permissions: 40 | actions: write 41 | attestations: write 42 | checks: write 43 | contents: write 44 | deployments: write 45 | discussions: write 46 | issues: write 47 | packages: write 48 | pages: write 49 | pull-requests: write 50 | repository-projects: write 51 | security-events: write 52 | statuses: write 53 | -------------------------------------------------------------------------------- /.github/workflows/beta_promote.yml: -------------------------------------------------------------------------------- 1 | # This workflow promotes the latest Beta Package Version. 2 | # It does *not* deliver the promoted package to any org. 3 | # To deliver a Promoted Release to an Org, you need to run the `Package Install` workflow. 4 | 5 | name: Beta - Promote (Unlocked) 6 | 7 | on: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | upload-prod: 12 | uses: nimba-actions/standard-workflows/.github/workflows/production-unlocked.yml@main 13 | secrets: 14 | cci-token: ${{ secrets.CUMULUSCI_TOKEN }} 15 | dev-hub-auth-url: ${{ secrets.DEV_HUB_AUTH_URL }} 16 | with: 17 | cumulusci-version: "3.78.0" 18 | 19 | permissions: 20 | actions: write 21 | attestations: write 22 | checks: write 23 | contents: write 24 | deployments: write 25 | discussions: write 26 | issues: write 27 | packages: write 28 | pages: write 29 | pull-requests: write 30 | repository-projects: write 31 | security-events: write 32 | statuses: write 33 | -------------------------------------------------------------------------------- /.github/workflows/feature_test.yml: -------------------------------------------------------------------------------- 1 | # This workflow generates a FEATURE TEST PACKAGE from the latest Commit SHA. 2 | # It only serves to validate the deployment and packageability of the latest commit. 3 | # It does NOT affect the version of the package and is not included in the package ancestry. 4 | # To generate a new Beta Package Version, you need to run the `Beta (Unlocked) - Create` workflow. 5 | 6 | name: Feature - Test (Unlocked) 7 | 8 | on: 9 | pull_request: 10 | branches: 11 | - feature/** 12 | - main 13 | 14 | jobs: 15 | feature-test: 16 | if: startsWith(github.head_ref, 'feature/') 17 | uses: nimba-actions/standard-workflows/.github/workflows/feature-unlocked.yml@main 18 | secrets: 19 | cci-token: ${{ secrets.CUMULUSCI_TOKEN }} 20 | dev-hub-auth-url: ${{ secrets.DEV_HUB_AUTH_URL }} 21 | with: 22 | cumulusci-version: "3.90.0" 23 | sfdx-version: "7.209.6" 24 | 25 | apex-scan: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v3 29 | with: 30 | fetch-depth: 0 31 | 32 | - name: Use Node.js 20.x 33 | uses: actions/setup-node@v3 34 | with: 35 | node-version: "20.x" 36 | 37 | - name: Install Salesforce CLI and Scanner 38 | run: | 39 | npm install @salesforce/cli -g 40 | sf plugins install @salesforce/sfdx-scanner@latest 41 | 42 | - name: Run SFDX Scanner - Report findings as comments 43 | uses: mitchspano/sfdx-scan-pull-request@v0.1.16 44 | with: 45 | pmdconfig: pmd-rules.xml 46 | severity-threshold: 4 47 | strictly-enforced-rules: '[{ "engine": "pmd", "category": "Performance", "rule": "AvoidDebugStatements" }]' 48 | report-mode: comments 49 | delete-resolved-comments: true 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | -------------------------------------------------------------------------------- /.github/workflows/package_install.yml: -------------------------------------------------------------------------------- 1 | # This workflow delivers the latest Production Release to an org. 2 | # It does *not* promote a Beta Release. 3 | # To promote a Beta Release to enable it for installation you need to run the `Production Release` workflow. 4 | 5 | name: Package - Install (Unlocked) 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | environment: 10 | description: "Choose the environment" 11 | required: true 12 | default: "production" 13 | type: choice 14 | options: 15 | - production 16 | - sandbox 17 | 18 | jobs: 19 | install-package: 20 | uses: nimba-actions/standard-workflows/.github/workflows/install-production.yml@main 21 | secrets: 22 | cci-token: ${{ secrets.CUMULUSCI_TOKEN }} 23 | prod-org-auth-url: ${{ inputs.environment == 'production' && secrets.PROD_ORG_AUTH_URL || secrets.BETA_ORG_AUTH_URL }} 24 | with: 25 | cumulusci-version: "3.78.0" 26 | 27 | permissions: 28 | actions: write 29 | attestations: write 30 | checks: write 31 | contents: write 32 | deployments: write 33 | discussions: write 34 | issues: write 35 | packages: write 36 | pages: write 37 | pull-requests: write 38 | repository-projects: write 39 | security-events: write 40 | statuses: write 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # CCI 2 | .cci 3 | /src.orig 4 | /src 5 | 6 | # Python 7 | *.pyc 8 | __pycache__ 9 | 10 | # Robot Framework results 11 | robot/OpenAPI-Unlocked/results/ 12 | 13 | # Salesforce cache 14 | .sf/ 15 | .sfdx/ 16 | .localdevserver/ 17 | deploy-options.json 18 | 19 | # LWC VSCode autocomplete 20 | **/lwc/jsconfig.json 21 | 22 | # LWC Jest coverage reports 23 | coverage/ 24 | 25 | # Logs 26 | logs 27 | *.log 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # Dependency directories 33 | node_modules/ 34 | 35 | # Eslint cache 36 | .eslintcache 37 | 38 | # MacOS system files 39 | .DS_Store 40 | 41 | # Windows system files 42 | Thumbs.db 43 | ehthumbs.db 44 | [Dd]esktop.ini 45 | $RECYCLE.BIN/ 46 | 47 | # Editors 48 | *.sublime-project 49 | *.sublime-workspace 50 | .vscode 51 | .idea 52 | .project 53 | .settings -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI-Unlocked 2 | 3 | OpenAPI-Unlocked brings the power of OpenAPI (Swagger) documentation to Salesforce development through an unlocked package. Enable your team to create beautiful, interactive API documentation with minimal effort - directly on Salesforce! 4 | 5 | ## Overview 6 | 7 | OpenAPI-Unlocked allows Salesforce developers to add simple annotations to their `@RestResource` Apex classes that automatically generate standardized OpenAPI (Swagger) documentation. Publish your API documentation directly to Experience Cloud sites (experimental) and give your integration partners the gift of clear, interactive documentation. 8 | 9 | ## Key Features 10 | 11 | - **Reduce Integration Friction**: Clear documentation means faster partner onboarding and fewer support tickets 12 | - **Stay in Sync**: Documentation is generated directly from your code, so it's always up-to-date 13 | - **Developer Friendly**: Simple annotations that feel familiar to Java developers 14 | - **Complex Type Support**: Automatically generates schema definitions for SObjects and Apex-defined types 15 | - **Governor Limit Safe**: Batch processing ensures reliability with any codebase size 16 | - **100% Native**: Built as a Salesforce unlocked package with no external dependencies 17 | 18 | ## Basic Usage 19 | 20 | ### 1. Add Swagger annotations to your `@RestResource` Apex classes: 21 | 22 | ```apex 23 | /** 24 | * @openapi 25 | * @title Account Management API 26 | * @description API for managing Account records 27 | * @version 1.0.0 28 | */ 29 | @RestResource(urlMapping='/account/*') 30 | global with sharing class AccountAPI { 31 | 32 | /** 33 | * @openapi 34 | * @operation getAccount 35 | * @summary Get an account by ID 36 | * @description Retrieves an account record by its ID 37 | * @tag Account 38 | * @security oauth2 read:accounts 39 | * @param id path string Account ID 40 | * @response 200 {description: "Account retrieved successfully", type: "Account"} 41 | * @response 404 {description: "Account not found"} 42 | */ 43 | @HttpGet 44 | global static Account getAccount() { 45 | RestRequest req = RestContext.request; 46 | String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1); 47 | 48 | List accounts = [SELECT Id, Name, Industry, Phone FROM Account WHERE Id = :accountId]; 49 | if (accounts.isEmpty()) { 50 | RestContext.response.statusCode = 404; 51 | return null; 52 | } 53 | 54 | return accounts[0]; 55 | } 56 | 57 | /** 58 | * @openapi 59 | * @operation createAccount 60 | * @summary Create a new account 61 | * @description Creates a new account record 62 | * @tag Account 63 | * @requestBody {description: "Account data to create", type: "Account"} 64 | * @response 201 {description: "Account created successfully", type: "Account"} 65 | * @response 400 {description: "Invalid input"} 66 | */ 67 | @HttpPost 68 | global static Account createAccount() { 69 | RestRequest req = RestContext.request; 70 | Account newAccount = (Account)JSON.deserialize( 71 | req.requestBody.toString(), 72 | Account.class 73 | ); 74 | 75 | try { 76 | insert newAccount; 77 | RestContext.response.statusCode = 201; 78 | return newAccount; 79 | } catch (Exception e) { 80 | RestContext.response.statusCode = 400; 81 | return null; 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | ### 2. Run `OpenAPIParser.parseClasses()` 88 | 89 | Generate the OpenAPI documentation by running this static method in Anonymous Apex. 90 | 91 | ```apex 92 | // Parse everything parsable 93 | OpenAPIParser.parseClasses(); 94 | 95 | // Optionally limit parsing to a specific namespace 96 | OpenAPIParser.parseClasses('acme'); 97 | ``` 98 | 99 | This will: 100 | - Identify and parse classes with OpenApi annotations 101 | - Automatically register and include all referenced schemas 102 | - Store results in one or many static resource 103 | - Return job status information while processing runs in the background 104 | 105 | > [!Tip] 106 | > Automating spec generation via `cci task run generate_spec --org ` can make for a great addition to your existing CI pipeline :) 107 | 108 | ### 3. Download the resulting StaticResource from your Org 109 | 110 | ### 4. Upload your spec into your preferred tool (e.g. Postman, SwaggerUI) 111 | ![image](https://github.com/user-attachments/assets/65422716-e39b-42df-af07-5f1f7edce6c1) 112 | 113 | ## Annotation Reference 114 | 115 | OpenAPI-Unlocked supports the following annotations: 116 | 117 | | Annotation | Level | Description | Example | 118 | |----------------|--------|------------------------------------------------------------------|--------------------------------------------------------------| 119 | | `@openapi` | All | Marks a class or method for OpenAPI processing | `@openapi` | 120 | | `@title` | Class | API title | `@title Account Management API` | 121 | | `@description` | All | Detailed description | `@description API for managing Account records` | 122 | | `@version` | Class | API version | `@version 1.0.0` | 123 | | `@operation` | Method | Unique operation ID | `@operation getAccount` | 124 | | `@summary` | Method | Brief summary | `@summary Get an account by ID` | 125 | | `@tag` | Method | API grouping tag | `@tag Account` | 126 | | `@security` | Method | Security requirements | `@security oauth2 read:accounts write:accounts` | 127 | | `@param` | Method | Path/query parameters | `@param id path string Account ID` | 128 | | `@requestBody` | Method | Request body definition | `@requestBody {description: "Account data", type: "Account"}` | 129 | | `@response` | Method | Response definition | `@response 200 {description: "Success", type: "Account"}` | 130 | 131 | ## Advanced Type Handling 132 | 133 | OpenAPI Unlocked automatically extracts and registers schemas for: 134 | 135 | 1. **Standard Salesforce SObjects** (Account, Contact, etc.) 136 | 2. **Custom SObjects** specific to your org 137 | 3. **Apex-Defined Types** (both top-level and nested classes) 138 | 139 | For example, with a custom class: 140 | 141 | ```apex 142 | global class TestAPI { 143 | // Nested request schema 144 | global class TestRequest { 145 | global String name; 146 | global Integer count; 147 | global Boolean isActive; 148 | global String nested; 149 | } 150 | 151 | // Nested response schema 152 | global class TestResponse { 153 | global String name; 154 | global Integer count; 155 | global Boolean isActive; 156 | global String nested; 157 | } 158 | 159 | /** 160 | * @openapi 161 | * @operation createTest 162 | * @summary Create test data 163 | * @description Creates test data with various property patterns 164 | * @tag Test 165 | * @security oauth2 write:test 166 | * @requestBody {description: "Test data to create", type: "TestAPI.TestRequest"} 167 | * @response 201 {description: "Test data created successfully", type: "TestAPI.TestResponse"} 168 | */ 169 | @HttpPost 170 | global static TestResponse createTest(TestRequest request) { 171 | // Implementation... 172 | } 173 | } 174 | ``` 175 | 176 | The system will automatically: 177 | 1. Extract schema definitions for `TestAPI.TestRequest` and `TestAPI.TestResponse` 178 | 2. Register these schemas in the central registry 179 | 3. Reference them with `$ref: "#/components/schemas/TestAPI.TestRequest"` in the OpenAPI output 180 | 181 | ## Handling Large Codebases 182 | 183 | For extremely large APIs with many endpoints, you can split the documentation by API tag: 184 | 185 | ```apex 186 | // Generate a separate specification for each API tag 187 | OpenAPIParserModular.generateModularSpecifications(); 188 | ``` 189 | 190 | This creates multiple Static Resources, one per API tag (e.g., `OPENAPI_ACCOUNT.json`, `OPENAPI_CONTACT.json`), and a master index (`OPENAPI_MASTER_INDEX.json`) that references all modules. 191 | 192 | ## Development 193 | 194 | To work on this project in a scratch org: 195 | 196 | 1. [Set up CumulusCI](https://cumulusci.readthedocs.io/en/latest/tutorial.html) 197 | 2. Run `cci flow run dev_org --org dev` to deploy this project 198 | 3. Run `cci org browser dev` to open the org in your browser 199 | 200 | ## Contributing 201 | 202 | We welcome contributions! Please follow these steps: 203 | 204 | 1. Fork the repository 205 | 2. Create a feature branch (`git checkout -b feature/amazing-feature`) 206 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 207 | 4. Push to the branch (`git push origin feature/amazing-feature`) 208 | 5. Open a Pull Request 209 | 210 | ## License 211 | 212 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 213 | 214 | ## Acknowledgments 215 | 216 | - OpenAPI Initiative 217 | - Salesforce Developer Community 218 | - All our amazing contributors 219 | 220 | --- 221 | 222 | 223 | -------------------------------------------------------------------------------- /category/apex/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | apex-default 7 | 8 | 9 | 10 | 3 11 | 12 | 13 | 14 | 15 | 16 | 3 17 | 18 | 19 | 20 | 21 | 22 | 3 23 | 24 | 25 | 26 | 27 | 28 | 3 29 | 30 | 31 | 32 | 33 | 34 | 3 35 | 36 | 37 | 38 | 39 | 40 | 3 41 | 42 | 43 | 44 | 45 | 46 | 3 47 | 48 | 49 | 50 | 51 | 52 | 3 53 | 54 | 55 | 56 | 57 | 58 | 3 59 | 60 | 61 | 62 | 63 | 64 | 3 65 | 66 | 67 | 68 | 69 | 70 | 3 71 | 72 | 73 | 3 74 | 75 | 76 | 3 77 | 78 | 79 | 3 80 | 81 | 82 | 3 83 | 84 | 85 | 86 | 87 | 3 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 3 96 | 97 | 98 | 3 99 | 100 | 101 | 3 102 | 103 | 104 | 105 | 106 | 3 107 | 108 | 109 | 3 110 | 111 | 112 | 3 113 | 114 | 115 | 3 116 | 117 | 118 | 119 | 120 | 3 121 | 122 | 123 | 3 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 3 133 | 134 | 135 | 3 136 | 137 | 138 | 3 139 | 140 | 141 | 3 142 | 143 | 144 | 3 145 | 146 | 147 | 3 148 | 149 | 150 | 3 151 | 152 | 153 | 3 154 | 155 | 156 | 3 157 | 158 | 159 | 3 160 | 161 | 162 | 3 163 | 164 | 165 | 166 | 167 | 3 168 | 169 | 170 | 3 171 | 172 | 173 | 3 174 | 175 | 176 | 3 177 | 178 | 179 | 180 | 181 | 3 182 | 183 | 184 | 3 185 | 186 | 187 | 3 188 | 189 | 190 | 3 191 | 192 | 193 | 3 194 | 195 | 196 | 197 | 198 | 3 199 | 200 | 201 | 202 | 203 | 204 | 3 205 | 206 | 207 | 208 | 209 | 3 210 | 211 | 212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /category/xml/default.md: -------------------------------------------------------------------------------- 1 | # Metadata Rules 2 | 3 | ## Flow Rules 4 | | Rule Name | Message/Description | 5 | |-----------|-------------------| 6 | | DMLStatementInFlowLoop | DML Operations shouldn't be done inside of Flow loops | 7 | 8 | ## Permission Rules 9 | | Rule Name | Message/Description | 10 | |-----------|-------------------| 11 | | ViewSetupByNonSysAdmins | Exposing the setup menu to non-authorized users. | 12 | 13 | ## Permission Sets 14 | | Rule Name | Message/Description | 15 | |-----------|-------------------| 16 | | PermissionSetRequiresDescription | Permission Sets should have a description | 17 | 18 | ## Objects 19 | | Rule Name | Message/Description | 20 | |-----------|-------------------| 21 | | CustomObjectRequiresDescription | Custom objects (__c) should have a description | 22 | 23 | ## Fields 24 | | Rule Name | Message/Description | 25 | |-----------|-------------------| 26 | | CustomFieldRequiresDescription | Custom fields should have a description | 27 | | NoUnderscoresInFieldNames | Custom field name should not contain underscores. | 28 | | NoFieldPermissionsInProfile | Field permissions should not be included in profile metadata. | 29 | | NoObjectPermissionsInProfile | Object permissions should not be included in profile metadata. | 30 | 31 | ## Field Naming Conventions 32 | | Rule Name | Required Pattern | 33 | |-----------|-----------------| 34 | | CheckboxFieldNamingConvention | PascalCaseBool__c | 35 | | TextAreaFieldNamingConvention | PascalCaseTxt__c | 36 | | RichTextFieldNamingConvention | PascalCaseTxt__c | 37 | | LongTextAreaFieldNamingConvention | PascalCaseTxt__c | 38 | | NumberFieldNamingConvention | PascalCaseNumber__c | 39 | | DateFieldNamingConvention | PascalCaseDate__c | 40 | | LookupFieldNamingConvention | PascalCaseId__c | 41 | | MasterDetailFieldNamingConvention | PascalCaseId__c | 42 | | DateTimeFieldNamingConvention | PascalCaseDateTime__c | 43 | | UrlFieldNamingConvention | PascalCaseUrl__c | 44 | | PicklistFieldNamingConvention | PascalCasePk__c | 45 | | MultiSelectPicklistFieldNamingConvention | PascalCasePk__c | 46 | | CurrencyFieldNamingConvention | PascalCaseCurrency__c | 47 | | PercentFieldNamingConvention | PascalCasePercent__c | 48 | -------------------------------------------------------------------------------- /category/xml/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | metadata-default 8 | 9 | 10 | 11 | 12 | 2 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 1 27 | 28 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 2 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 2 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 2 66 | 67 | 68 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Custom fields should not contain underscores in their names. 78 | 79 | 3 80 | 81 | 82 | 85 | 86 | 87 | 88 | 89 | 90 | Profiles should not contain field permissions - these should be in permission sets 91 | 1 92 | 93 | 94 | 97 | 98 | 99 | 100 | 101 | 102 | Profiles should not contain object permissions - these should be in permission sets 103 | 1 104 | 105 | 106 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 2 116 | 117 | 118 | 124 | 125 | 126 | 127 | 128 | 129 | 2 130 | 131 | 132 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 294 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /cumulusci.yml: -------------------------------------------------------------------------------- 1 | minimum_cumulusci_version: "3.78.0" 2 | project: 3 | name: OpenAPI-Unlocked 4 | package: 5 | name: OpenAPI-Unlocked 6 | api_version: "63.0" 7 | git: 8 | default_branch: "main" 9 | prefix_feature: "feature/" 10 | prefix_beta: "beta/" 11 | prefix_release: "release/" 12 | source_format: sfdx 13 | 14 | tasks: 15 | robot: 16 | options: 17 | suites: robot/OpenAPI-Unlocked/tests 18 | options: 19 | outputdir: robot/OpenAPI-Unlocked/results 20 | 21 | robot_testdoc: 22 | options: 23 | path: robot/OpenAPI-Unlocked/tests 24 | output: robot/OpenAPI-Unlocked/doc/OpenAPI-Unlocked_tests.html 25 | 26 | run_tests: 27 | options: 28 | required_org_code_coverage_percent: 75 29 | 30 | deploy_pre: 31 | group: "Dev/CI" 32 | options: 33 | transforms: 34 | - transform: find_replace 35 | options: 36 | patterns: 37 | - find: "ORG_DOMAIN" 38 | inject_org_url: True 39 | 40 | deploy: 41 | group: "Dev/CI" 42 | options: 43 | transforms: 44 | - transform: find_replace 45 | options: 46 | patterns: 47 | - find: "ORG_DOMAIN" 48 | inject_org_url: True 49 | 50 | generate_spec: 51 | group: "Dev/CI" 52 | description: Generates OpenAPI specification from Apex classes 53 | class_path: tasks.openapi_spec.GenerateOpenAPISpec 54 | options: 55 | version: "beta/0.3.0.16" 56 | title: "My Custom API" 57 | description: "API documentation for my custom endpoints" 58 | 59 | # Digital Experience Setup Tasks 60 | create_experience: 61 | group: "experience" 62 | description: "Creates a Digital Experience site for API documentation" 63 | class_path: cumulusci.tasks.salesforce.CreateCommunity 64 | options: 65 | template: Build Your Own (LWR) 66 | name: API 67 | url_path_prefix: api 68 | timeout: 60000 69 | skip_existing: true 70 | 71 | publish_experience: 72 | group: "experience" 73 | description: Publishes the API Documentation Experience Site 74 | class_path: cumulusci.tasks.salesforce.PublishCommunity 75 | options: 76 | name: API 77 | 78 | create_experience_owner: 79 | group: "experience" 80 | description: Creates the Experience Cloud site owner 81 | class_path: cumulusci.tasks.salesforce.composite.CompositeApi 82 | options: 83 | data_files: 84 | - "unpackaged/experience/users/owner.json" 85 | 86 | deploy_experience_cloud: 87 | class_path: cumulusci.tasks.salesforce.Deploy 88 | options: 89 | path: "unpackaged/experience/src" 90 | 91 | assign_api_guest_user_permission_set: 92 | group: "experience" 93 | description: Assigns necessary permissions to the guest user 94 | class_path: cumulusci.tasks.salesforce.composite.CompositeApi 95 | options: 96 | data_files: 97 | - "unpackaged/experience/users/guest.json" 98 | 99 | flows: 100 | # Override standard flows to include experience cloud management 101 | config_dev: 102 | steps: 103 | 3: 104 | flow: make_experience 105 | 106 | # Digital Experience Setup Flows 107 | make_experience: 108 | steps: 109 | 1: 110 | task: create_experience 111 | 2: 112 | task: create_experience_owner 113 | 3: 114 | task: update_admin_profile 115 | 4: 116 | task: deploy_experience_cloud 117 | 5: 118 | task: publish_experience 119 | 6: 120 | task: assign_api_guest_user_permission_set 121 | 122 | deploy_experience: 123 | steps: 124 | 1: 125 | task: create_experience 126 | 2: 127 | task: deploy_experience_cloud 128 | 3: 129 | task: publish_experience 130 | -------------------------------------------------------------------------------- /datasets/mapping.yml: -------------------------------------------------------------------------------- 1 | Insert Accounts: 2 | sf_object: Account 3 | table: Account 4 | fields: 5 | Name: Name 6 | Description: Description 7 | BillingStreet: BillingStreet 8 | BillingCity: BillingCity 9 | BillingState: BillingState 10 | BillingPostalCode: BillingPostalCode 11 | BillingCountry: BillingCountry 12 | ShippingStreet: ShippingStreet 13 | ShippingCity: ShippingCity 14 | ShippingState: ShippingState 15 | ShippingPostalCode: ShippingPostalCode 16 | ShippingCountry: ShippingCountry 17 | Phone: Phone 18 | Fax: Fax 19 | Website: Website 20 | NumberOfEmployees: NumberOfEmployees 21 | AccountNumber: AccountNumber 22 | Site: Site 23 | Type: Type 24 | Insert Contacts: 25 | sf_object: Contact 26 | table: Contact 27 | fields: 28 | Salutation: Salutation 29 | FirstName: FirstName 30 | LastName: LastName 31 | Email: Email 32 | Phone: Phone 33 | MobilePhone: MobilePhone 34 | OtherPhone: OtherPhone 35 | HomePhone: HomePhone 36 | Title: Title 37 | Birthdate: Birthdate 38 | MailingStreet: MailingStreet 39 | MailingCity: MailingCity 40 | MailingState: MailingState 41 | MailingPostalCode: MailingPostalCode 42 | MailingCountry: MailingCountry 43 | lookups: 44 | AccountId: 45 | table: Account -------------------------------------------------------------------------------- /force-app/main/default/classes/OpenAPIParser.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /force-app/main/default/classes/OpenAPIParserBatch.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Batch Apex implementation for parsing OpenAPI annotations from Apex classes 3 | * Designed to handle large codebases with proper governor limit management 4 | */ 5 | public class OpenAPIParserBatch implements Database.Batchable, Database.Stateful { 6 | 7 | // Stateful variables to maintain information between batches 8 | private Map combinedSpec; 9 | private Map combinedPaths; 10 | private String combinedTitle = 'Salesforce REST API'; 11 | private String combinedDescription = 'Combined REST API documentation'; 12 | private String combinedVersion = '1.0.0'; 13 | private String namespace; 14 | // Keep a stateful copy of schemas to persist between batch transactions 15 | private Map> batchSchemas = new Map>(); 16 | 17 | /** 18 | * Constructor for parsing all classes in the default namespace 19 | */ 20 | public OpenAPIParserBatch() { 21 | this(null); 22 | } 23 | 24 | /** 25 | * Constructor for parsing classes in a specific namespace 26 | * @param namespace The namespace to parse (null for default namespace) 27 | */ 28 | public OpenAPIParserBatch(String namespace) { 29 | this.namespace = namespace; 30 | this.combinedSpec = new Map(); 31 | this.combinedPaths = new Map(); 32 | this.batchSchemas = new Map>(); 33 | 34 | // Initialize OpenAPI spec with version 35 | this.combinedSpec.put('openapi', '3.0.0'); 36 | 37 | // Reset the schema registry for a fresh start 38 | OpenAPIParser.resetSchemaRegistry(); 39 | } 40 | 41 | /** 42 | * Start method that queries for classes to process 43 | * Cannot filter on Body in SOQL - filtering happens in execute method 44 | */ 45 | public Database.QueryLocator start(Database.BatchableContext bc) { 46 | String query = 'SELECT Id, Name, Body FROM ApexClass'; 47 | if (namespace != null) { 48 | query += ' WHERE NamespacePrefix = :namespace'; 49 | } 50 | return Database.getQueryLocator(query); 51 | } 52 | 53 | /** 54 | * Process each batch of classes 55 | */ 56 | public void execute(Database.BatchableContext bc, List scope) { 57 | List allClasses = (List)scope; 58 | System.debug('Received batch of ' + allClasses.size() + ' classes'); 59 | 60 | // Filter classes with OpenAPI annotations in Apex code 61 | List classes = new List(); 62 | Pattern openApiPattern = Pattern.compile('@openapi'); 63 | 64 | for (ApexClass cls : allClasses) { 65 | Matcher openApiMatcher = openApiPattern.matcher(cls.Body); 66 | if (openApiMatcher.find()) { 67 | classes.add(cls); 68 | } 69 | } 70 | 71 | System.debug('Filtered down to ' + classes.size() + ' classes with @openapi annotations'); 72 | 73 | // Track query usage to avoid hitting limits 74 | Integer queriesRemaining = Limits.getLimitQueries() - Limits.getQueries(); 75 | Integer heapRemaining = Limits.getLimitHeapSize() - Limits.getHeapSize(); 76 | 77 | for (ApexClass apexClass : classes) { 78 | // Check remaining governors before processing each class 79 | if (Limits.getQueries() >= Limits.getLimitQueries() - 5 || 80 | Limits.getHeapSize() >= Limits.getLimitHeapSize() * 0.9) { 81 | System.debug('Approaching governor limits, stopping batch processing early'); 82 | break; 83 | } 84 | 85 | System.debug('Processing class: ' + apexClass.Name); 86 | 87 | // Process this class and extract all paths 88 | Map classSpec = processClass(apexClass); 89 | 90 | if (classSpec != null) { 91 | // Merge info section - only take the first encountered class info 92 | if (classSpec.containsKey('info')) { 93 | Map info = (Map)classSpec.get('info'); 94 | // Only override the title/description once - from the first class that has it 95 | if (info.containsKey('title') && combinedTitle == 'Salesforce REST API') { 96 | combinedTitle = (String)info.get('title'); 97 | } 98 | if (info.containsKey('description') && combinedDescription == 'Combined REST API documentation') { 99 | combinedDescription = (String)info.get('description'); 100 | } 101 | if (info.containsKey('version')) { 102 | combinedVersion = (String)info.get('version'); 103 | } 104 | } 105 | 106 | // Merge paths in a memory-efficient way 107 | if (classSpec.containsKey('paths')) { 108 | Map paths = (Map)classSpec.get('paths'); 109 | for (String path : paths.keySet()) { 110 | if (!combinedPaths.containsKey(path)) { 111 | combinedPaths.put(path, paths.get(path)); 112 | } else { 113 | // Merge HTTP methods for the same path 114 | Map existingMethods = (Map)combinedPaths.get(path); 115 | Map newMethods = (Map)paths.get(path); 116 | for (String method : newMethods.keySet()) { 117 | existingMethods.put(method, newMethods.get(method)); 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | // Copy schemas from the registry to our stateful variable after each batch 126 | Map> currentSchemas = OpenAPIParser.getSchemaRegistry(); 127 | if (currentSchemas != null && !currentSchemas.isEmpty()) { 128 | for (String schemaName : currentSchemas.keySet()) { 129 | if (!batchSchemas.containsKey(schemaName)) { 130 | batchSchemas.put(schemaName, currentSchemas.get(schemaName)); 131 | System.debug('Saved schema to batch state: ' + schemaName); 132 | } 133 | } 134 | System.debug('Current batch schemas size: ' + batchSchemas.size()); 135 | } 136 | } 137 | 138 | /** 139 | * Finish method that finalizes and stores the module 140 | */ 141 | public void finish(Database.BatchableContext bc) { 142 | // Set the combined info 143 | combinedSpec.put('info', new Map{ 144 | 'title' => combinedTitle, 145 | 'description' => combinedDescription, 146 | 'version' => combinedVersion 147 | }); 148 | 149 | // Add servers section with the org's instance URL 150 | String baseUrl = URL.getOrgDomainUrl().toExternalForm() + '/services/apexrest'; 151 | combinedSpec.put('servers', new List>{ 152 | new Map{ 153 | 'url' => baseUrl, 154 | 'description' => 'Salesforce Apex REST API' 155 | } 156 | }); 157 | 158 | // Set the combined paths 159 | combinedSpec.put('paths', combinedPaths); 160 | 161 | System.debug('Number of schemas collected during batch execution: ' + batchSchemas.size()); 162 | if (batchSchemas.isEmpty()) { 163 | System.debug(LoggingLevel.WARN, 'WARNING: No schemas were collected during batch execution!'); 164 | } 165 | 166 | // Add components section with schemas from our stateful variable instead of the registry 167 | Map components = new Map{ 168 | 'securitySchemes' => new Map{ 169 | 'oauth2' => new Map{ 170 | 'type' => 'oauth2', 171 | 'flows' => new Map{ 172 | 'authorizationCode' => new Map{ 173 | 'authorizationUrl' => URL.getOrgDomainUrl().toExternalForm() + '/services/oauth2/authorize', 174 | 'tokenUrl' => URL.getOrgDomainUrl().toExternalForm() + '/services/oauth2/token', 175 | 'scopes' => new Map{ 176 | 'api' => 'Access and manage your data (api)', 177 | 'refresh_token' => 'Allow access to your data via the Web (refresh_token)', 178 | 'offline_access' => 'Access your data anytime (offline_access)' 179 | } 180 | } 181 | } 182 | } 183 | }, 184 | 'schemas' => batchSchemas 185 | }; 186 | 187 | // Add the components section to the spec 188 | combinedSpec.put('components', components); 189 | 190 | // Apply security at the root level 191 | combinedSpec.put('security', new List>>{ 192 | new Map>{ 193 | 'oauth2' => new List{'api'} 194 | } 195 | }); 196 | 197 | System.debug('Combined OpenAPI specification complete'); 198 | if (combinedSpec.containsKey('components')) { 199 | Map comps = (Map)combinedSpec.get('components'); 200 | if (comps.containsKey('schemas')) { 201 | Map schemas = (Map)comps.get('schemas'); 202 | System.debug('Number of schemas in final spec: ' + schemas.size()); 203 | System.debug('Schema names: ' + String.join(new List(schemas.keySet()), ', ')); 204 | } else { 205 | System.debug('No schemas section in components!'); 206 | } 207 | } else { 208 | System.debug('No components section in spec!'); 209 | } 210 | 211 | // Store the final spec 212 | OpenAPIParser.storeAsStaticResource(combinedSpec, 'OPENAPI_SPEC'); 213 | } 214 | 215 | /** 216 | * Process a single class to extract paths and methods 217 | * @param apexClass The Apex class to process 218 | * @return Map with paths and class info 219 | */ 220 | private Map processClass(ApexClass apexClass) { 221 | String className = apexClass.Name; 222 | String classBody = apexClass.Body; 223 | 224 | Map result = new Map(); 225 | 226 | // Extract RestResource URL mapping if present 227 | String baseUrlMapping = OpenAPIParser.extractRestResourceUrlMapping(classBody); 228 | 229 | // Extract class-level OpenAPI annotations 230 | Pattern classPattern = Pattern.compile('/\\*\\*\\s*\\n\\s*\\*\\s*@openapi\\s*\\n\\s*\\*\\s*@title\\s+(.*?)\\n\\s*\\*\\s*@description\\s+(.*?)\\n\\s*\\*\\s*@version\\s+(.*?)\\n\\s*\\*/'); 231 | Matcher classMatcher = classPattern.matcher(classBody); 232 | 233 | if (classMatcher.find()) { 234 | Map info = new Map{ 235 | 'title' => classMatcher.group(1).trim(), 236 | 'description' => classMatcher.group(2).trim(), 237 | 'version' => classMatcher.group(3).trim() 238 | }; 239 | result.put('info', info); 240 | } 241 | 242 | // Extract method-level OpenAPI annotations 243 | Map paths = new Map(); 244 | 245 | // Find all method documentation blocks 246 | Pattern methodPattern = Pattern.compile('/\\*\\*\\s*\\n(\\s*\\*\\s*@[^\\n]*\\n)*\\s*\\*/\\s*\\n*\\s*@(Http\\w+)\\s+.*?\\s+(\\w+)\\s*\\([^)]*\\)'); 247 | Matcher methodMatcher = methodPattern.matcher(classBody); 248 | 249 | while (methodMatcher.find()) { 250 | String methodDoc = methodMatcher.group(0); 251 | String httpMethod = methodMatcher.group(2).toLowerCase().replace('http', ''); 252 | String methodName = methodMatcher.group(3); 253 | 254 | // Extract path parameters 255 | List> methodParams = OpenAPIParser.extractParameters(methodDoc); 256 | List pathParams = new List(); 257 | 258 | for (Map param : methodParams) { 259 | if (param.get('in') == 'path') { 260 | pathParams.add((String)param.get('name')); 261 | } 262 | } 263 | 264 | // Construct the path 265 | String path = OpenAPIParser.constructApiPath(baseUrlMapping, httpMethod, methodName, pathParams); 266 | 267 | // Parse method info 268 | Map pathInfo = OpenAPIParser.parseMethod(methodDoc); 269 | 270 | if (!paths.containsKey(path)) { 271 | paths.put(path, new Map()); 272 | } 273 | ((Map)paths.get(path)).put(httpMethod, pathInfo); 274 | } 275 | 276 | result.put('paths', paths); 277 | return result; 278 | } 279 | } -------------------------------------------------------------------------------- /force-app/main/default/classes/OpenAPIParserBatch.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 58.0 4 | Active 5 | -------------------------------------------------------------------------------- /force-app/main/default/classes/OpenAPIParserModular.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Modular OpenAPI parser for extremely large APIs 3 | * Splits the API documentation into modules based on tags/categories 4 | */ 5 | public with sharing class OpenAPIParserModular { 6 | 7 | /** 8 | * Generates modular OpenAPI specifications based on API tags 9 | * Each API tag gets its own static resource 10 | */ 11 | public static void generateModularSpecifications() { 12 | // First, gather all the API tags used in the system 13 | Set allTags = getAllApiTags(); 14 | System.debug('Found ' + allTags.size() + ' distinct API tags'); 15 | 16 | // Create a module specification for each tag 17 | for (String tag : allTags) { 18 | String safeName = tag.replaceAll('[^a-zA-Z0-9]', '_'); 19 | String resourceName = 'OPENAPI_' + safeName.toUpperCase(); 20 | 21 | // Start a batch job for each module with the tag filter 22 | OpenAPIParserModularBatch batch = new OpenAPIParserModularBatch(tag, resourceName); 23 | Database.executeBatch(batch); 24 | } 25 | 26 | // Also create a master index that references all modules 27 | createMasterIndex(allTags); 28 | } 29 | 30 | /** 31 | * Gets all API tags used in the system 32 | */ 33 | private static Set getAllApiTags() { 34 | Set tags = new Set(); 35 | 36 | // Find all classes with OpenAPI annotations - can't filter on Body in SOQL 37 | List classes = [ 38 | SELECT Id, Name, Body 39 | FROM ApexClass 40 | ]; 41 | 42 | Pattern tagPattern = Pattern.compile('@tag\\s+(.*?)(?=\\n|$)'); 43 | Pattern openApiPattern = Pattern.compile('@openapi'); 44 | 45 | for (ApexClass cls : classes) { 46 | // First check if this class has OpenAPI annotations 47 | Matcher openApiMatcher = openApiPattern.matcher(cls.Body); 48 | if (!openApiMatcher.find()) { 49 | continue; // Skip this class if it doesn't have OpenAPI annotations 50 | } 51 | 52 | // Now look for tags 53 | Matcher tagMatcher = tagPattern.matcher(cls.Body); 54 | while (tagMatcher.find()) { 55 | String tag = tagMatcher.group(1).trim(); 56 | tags.add(tag); 57 | } 58 | } 59 | 60 | return tags; 61 | } 62 | 63 | /** 64 | * Creates a master index of all API modules 65 | */ 66 | private static void createMasterIndex(Set tags) { 67 | Map masterSpec = new Map(); 68 | 69 | // Standard OpenAPI boilerplate 70 | masterSpec.put('openapi', '3.0.0'); 71 | masterSpec.put('info', new Map{ 72 | 'title' => 'API Documentation Index', 73 | 'description' => 'Master index of all API modules', 74 | 'version' => '1.0.0' 75 | }); 76 | 77 | // Create an empty paths object (required by OpenAPI spec) 78 | masterSpec.put('paths', new Map()); 79 | 80 | // Reference each module in the components section 81 | Map components = new Map{ 82 | 'externalDocs' => new Map() 83 | }; 84 | 85 | for (String tag : tags) { 86 | String safeName = tag.replaceAll('[^a-zA-Z0-9]', '_'); 87 | String resourceName = 'OPENAPI_' + safeName.toUpperCase(); 88 | 89 | // Add a reference to this module 90 | components.put(tag, new Map{ 91 | 'description' => 'API documentation for ' + tag, 92 | 'externalValue' => '{!URLFOR($Resource.' + resourceName + ')}' 93 | }); 94 | } 95 | 96 | masterSpec.put('components', components); 97 | 98 | // Store the master index 99 | System.enqueueJob(new OpenAPIStorageQueueable(masterSpec, 'OPENAPI_MASTER_INDEX')); 100 | } 101 | } -------------------------------------------------------------------------------- /force-app/main/default/classes/OpenAPIParserModular.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 58.0 4 | Active 5 | -------------------------------------------------------------------------------- /force-app/main/default/classes/OpenAPIParserModularBatch.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Batch class for generating modular OpenAPI specs by tag 3 | * For extremely large APIs, each tag gets its own specification 4 | */ 5 | public class OpenAPIParserModularBatch implements Database.Batchable, Database.Stateful { 6 | 7 | private String apiTag; 8 | private String resourceName; 9 | private Map combinedSpec; 10 | private Map combinedPaths; 11 | 12 | /** 13 | * Constructor for creating a module for a specific API tag 14 | * @param apiTag The API tag to filter by 15 | * @param resourceName The name for the resulting Static Resource 16 | */ 17 | public OpenAPIParserModularBatch(String apiTag, String resourceName) { 18 | this.apiTag = apiTag; 19 | this.resourceName = resourceName; 20 | this.combinedSpec = new Map(); 21 | this.combinedPaths = new Map(); 22 | 23 | // Initialize OpenAPI spec 24 | this.combinedSpec.put('openapi', '3.0.0'); 25 | this.combinedSpec.put('info', new Map{ 26 | 'title' => apiTag + ' API', 27 | 'description' => 'API documentation for ' + apiTag, 28 | 'version' => '1.0.0' 29 | }); 30 | 31 | // Reset the schema registry for a fresh start 32 | OpenAPIParser.resetSchemaRegistry(); 33 | } 34 | 35 | /** 36 | * Start method to query classes without filtering on Body (which isn't supported in SOQL) 37 | */ 38 | public Database.QueryLocator start(Database.BatchableContext bc) { 39 | // We can't filter on Body in SOQL, so we'll get all classes and filter in execute 40 | return Database.getQueryLocator( 41 | 'SELECT Id, Name, Body FROM ApexClass' 42 | ); 43 | } 44 | 45 | /** 46 | * Execute method that processes each batch 47 | */ 48 | public void execute(Database.BatchableContext bc, List scope) { 49 | List classes = (List)scope; 50 | System.debug('Processing batch of ' + classes.size() + ' classes for tag: ' + apiTag); 51 | 52 | List filteredClasses = new List(); 53 | Pattern openApiPattern = Pattern.compile('@openapi'); 54 | Pattern tagPattern = Pattern.compile('@tag\\s+' + Pattern.quote(apiTag)); 55 | 56 | // First filter classes to only those with our specific tag 57 | for (ApexClass apexClass : classes) { 58 | String body = apexClass.Body; 59 | // Check if this class has both OpenAPI annotations and our specific tag 60 | Matcher openApiMatcher = openApiPattern.matcher(body); 61 | if (!openApiMatcher.find()) { 62 | continue; 63 | } 64 | 65 | Matcher tagMatcher = tagPattern.matcher(body); 66 | if (!tagMatcher.find()) { 67 | continue; 68 | } 69 | 70 | filteredClasses.add(apexClass); 71 | } 72 | 73 | System.debug('Filtered down to ' + filteredClasses.size() + ' classes with tag: ' + apiTag); 74 | 75 | for (ApexClass apexClass : filteredClasses) { 76 | // Check remaining governors before processing each class 77 | if (Limits.getHeapSize() >= Limits.getLimitHeapSize() * 0.9 || 78 | Limits.getQueries() >= Limits.getLimitQueries() - 5) { 79 | System.debug('Approaching governor limits, stopping batch processing early'); 80 | break; 81 | } 82 | 83 | System.debug('Processing class: ' + apexClass.Name); 84 | 85 | // Parse the class specifically looking for methods with our tag 86 | Map classSpec = parseClassForTag(apexClass, apiTag); 87 | 88 | if (classSpec != null && classSpec.containsKey('paths')) { 89 | // Merge paths 90 | Map paths = (Map)classSpec.get('paths'); 91 | for (String path : paths.keySet()) { 92 | if (!combinedPaths.containsKey(path)) { 93 | combinedPaths.put(path, paths.get(path)); 94 | } else { 95 | // Merge HTTP methods for the same path 96 | Map existingMethods = (Map)combinedPaths.get(path); 97 | Map newMethods = (Map)paths.get(path); 98 | for (String method : newMethods.keySet()) { 99 | existingMethods.put(method, newMethods.get(method)); 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * Finish method that finalizes and stores the module 109 | */ 110 | public void finish(Database.BatchableContext bc) { 111 | // Add the paths to the spec 112 | combinedSpec.put('paths', combinedPaths); 113 | 114 | // Add servers section with the org's instance URL 115 | String baseUrl = URL.getOrgDomainUrl().toExternalForm() + '/services/apexrest'; 116 | combinedSpec.put('servers', new List>{ 117 | new Map{ 118 | 'url' => baseUrl, 119 | 'description' => 'Salesforce Apex REST API' 120 | } 121 | }); 122 | 123 | // Add the single tag that this module is for 124 | combinedSpec.put('tags', new List>{ 125 | new Map{ 126 | 'name' => apiTag, 127 | 'description' => 'Endpoints for ' + apiTag 128 | } 129 | }); 130 | 131 | // Add components section with schemas from the registry and security schemes 132 | Map components = new Map{ 133 | 'securitySchemes' => new Map{ 134 | 'oauth2' => new Map{ 135 | 'type' => 'oauth2', 136 | 'flows' => new Map{ 137 | 'authorizationCode' => new Map{ 138 | 'authorizationUrl' => URL.getOrgDomainUrl().toExternalForm() + '/services/oauth2/authorize', 139 | 'tokenUrl' => URL.getOrgDomainUrl().toExternalForm() + '/services/oauth2/token', 140 | 'scopes' => new Map{ 141 | 'api' => 'Access and manage your data (api)' 142 | } 143 | } 144 | } 145 | } 146 | } 147 | }; 148 | 149 | // Add schemas from the registry 150 | Map> schemaRegistry = OpenAPIParser.getSchemaRegistry(); 151 | System.debug('Schema registry for ' + apiTag + ' contains ' + schemaRegistry.size() + ' schemas'); 152 | if (!schemaRegistry.isEmpty()) { 153 | // Using direct assignment to ensure we get the full registry 154 | components.put('schemas', schemaRegistry); 155 | System.debug('Added schemas to components section for ' + apiTag); 156 | } else { 157 | System.debug('WARNING: Schema registry is empty for ' + apiTag + ' despite having schema references'); 158 | } 159 | 160 | // Debug the components section 161 | System.debug('Components section for ' + apiTag + ': ' + JSON.serializePretty(components)); 162 | 163 | combinedSpec.put('components', components); 164 | 165 | // Apply security at the root level 166 | combinedSpec.put('security', new List>>{ 167 | new Map>{ 168 | 'oauth2' => new List{'api'} 169 | } 170 | }); 171 | 172 | System.debug('Finished creating modular spec for tag: ' + apiTag); 173 | OpenAPIParser.storeAsStaticResource(combinedSpec, resourceName); 174 | } 175 | 176 | /** 177 | * Parses a class looking only for methods with the specified tag 178 | */ 179 | private Map parseClassForTag(ApexClass apexClass, String tagToFind) { 180 | Map result = new Map(); 181 | Map paths = new Map(); 182 | 183 | String classBody = apexClass.Body; 184 | 185 | // Extract RestResource URL mapping if present 186 | String baseUrlMapping = OpenAPIParser.extractRestResourceUrlMapping(classBody); 187 | 188 | // Find all method documentation blocks 189 | Pattern methodPattern = Pattern.compile('/\\*\\*\\s*\\n(\\s*\\*\\s*@[^\\n]*\\n)*\\s*\\*/\\s*\\n*\\s*@(Http\\w+)\\s+.*?\\s+(\\w+)\\s*\\([^)]*\\)'); 190 | Matcher methodMatcher = methodPattern.matcher(classBody); 191 | 192 | while (methodMatcher.find()) { 193 | String methodDoc = methodMatcher.group(0); 194 | 195 | // Check if this method has our tag 196 | if (!methodDoc.contains('@tag ' + tagToFind)) { 197 | continue; 198 | } 199 | 200 | String httpMethod = methodMatcher.group(2).toLowerCase().replace('http', ''); 201 | String methodName = methodMatcher.group(3); 202 | 203 | // Extract path parameters using the shared utility method 204 | List> parameters = OpenAPIParser.extractParameters(methodDoc); 205 | List pathParams = new List(); 206 | for (Map param : parameters) { 207 | if (param.get('in') == 'path') { 208 | pathParams.add((String)param.get('name')); 209 | } 210 | } 211 | 212 | // Use shared utility method to construct the path 213 | String path = OpenAPIParser.constructApiPath(baseUrlMapping, httpMethod, methodName, pathParams); 214 | 215 | // Use OpenAPIParser's parseMethod to get the details 216 | Map methodInfo = OpenAPIParser.parseMethod(methodDoc); 217 | 218 | if (!paths.containsKey(path)) { 219 | paths.put(path, new Map()); 220 | } 221 | ((Map)paths.get(path)).put(httpMethod, methodInfo); 222 | } 223 | 224 | if (!paths.isEmpty()) { 225 | result.put('paths', paths); 226 | } 227 | 228 | return result; 229 | } 230 | } -------------------------------------------------------------------------------- /force-app/main/default/classes/OpenAPIParserModularBatch.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 58.0 4 | Active 5 | -------------------------------------------------------------------------------- /force-app/main/default/classes/OpenAPIStorageQueueable.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * Queueable class for storing OpenAPI specifications as a Static Resource 3 | * Handles the HTTP callout outside of batch context to avoid mixed DML errors 4 | */ 5 | public class OpenAPIStorageQueueable implements Queueable, Database.AllowsCallouts { 6 | 7 | private Map spec; 8 | private String resourceName; 9 | 10 | /** 11 | * Constructor 12 | * @param spec The OpenAPI specification to store 13 | * @param resourceName The name of the Static Resource 14 | */ 15 | public OpenAPIStorageQueueable(Map spec, String resourceName) { 16 | this.spec = spec; 17 | this.resourceName = resourceName; 18 | } 19 | 20 | /** 21 | * Execute method for the queueable 22 | */ 23 | public void execute(QueueableContext context) { 24 | try { 25 | // Store the specification 26 | storeAsStaticResource(spec, resourceName); 27 | System.debug('Successfully stored OpenAPI spec as Static Resource: ' + resourceName); 28 | } catch (Exception e) { 29 | System.debug(LoggingLevel.ERROR, 'Error storing OpenAPI spec: ' + e.getMessage()); 30 | System.debug(LoggingLevel.ERROR, e.getStackTraceString()); 31 | } 32 | } 33 | 34 | /** 35 | * Stores the OpenAPI specification as a Static Resource 36 | * @param spec The OpenAPI specification to store 37 | * @param resourceName The name of the Static Resource 38 | */ 39 | private void storeAsStaticResource(Map spec, String resourceName) { 40 | try { 41 | // Convert spec to JSON string 42 | String jsonSpec = JSON.serializePretty(spec); 43 | 44 | // First try to get the existing Static Resource 45 | HttpRequest getReq = new HttpRequest(); 46 | getReq.setEndpoint(URL.getOrgDomainUrl().toExternalForm() + '/services/data/v62.0/tooling/query?q=SELECT+Id+FROM+StaticResource+WHERE+Name=\'' + resourceName + '\''); 47 | getReq.setMethod('GET'); 48 | getReq.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId()); 49 | 50 | Http http = new Http(); 51 | HttpResponse getRes = http.send(getReq); 52 | 53 | String resourceId = null; 54 | if (getRes.getStatusCode() == 200) { 55 | Map queryResult = (Map)JSON.deserializeUntyped(getRes.getBody()); 56 | List records = (List)queryResult.get('records'); 57 | if (!records.isEmpty()) { 58 | Map record = (Map)records[0]; 59 | resourceId = (String)record.get('Id'); 60 | } 61 | } 62 | 63 | // Create or update the Static Resource 64 | HttpRequest req = new HttpRequest(); 65 | if (resourceId != null) { 66 | // Update existing resource 67 | req.setEndpoint(URL.getOrgDomainUrl().toExternalForm() + '/services/data/v62.0/tooling/sobjects/StaticResource/' + resourceId); 68 | req.setMethod('PATCH'); 69 | } else { 70 | // Create new resource 71 | req.setEndpoint(URL.getOrgDomainUrl().toExternalForm() + '/services/data/v62.0/tooling/sobjects/StaticResource'); 72 | req.setMethod('POST'); 73 | } 74 | 75 | req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId()); 76 | req.setHeader('Content-Type', 'application/json'); 77 | 78 | // Create the Static Resource body with correct field names 79 | Map body = new Map{ 80 | 'Name' => resourceName, 81 | 'ContentType' => 'application/json', 82 | 'CacheControl' => 'Public', 83 | 'Body' => EncodingUtil.base64Encode(Blob.valueOf(jsonSpec)) 84 | }; 85 | 86 | req.setBody(JSON.serialize(body)); 87 | 88 | HttpResponse res = http.send(req); 89 | 90 | if (res.getStatusCode() != 201 && res.getStatusCode() != 204) { 91 | throw new OpenAPIParser.OpenAPIParserException('Failed to ' + (resourceId != null ? 'update' : 'create') + 92 | ' Static Resource: ' + res.getBody()); 93 | } 94 | 95 | System.debug('Successfully ' + (resourceId != null ? 'updated' : 'created') + ' static resource ' + resourceName); 96 | } catch (Exception e) { 97 | System.debug(LoggingLevel.ERROR, 'Error storing OpenAPI spec: ' + e.getMessage()); 98 | System.debug(LoggingLevel.ERROR, e.getStackTraceString()); 99 | throw e; // Re-throw to make sure the error is properly reported 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /force-app/main/default/classes/OpenAPIStorageQueueable.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 58.0 4 | Active 5 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/swaggerViewer/swaggerViewer.css: -------------------------------------------------------------------------------- 1 | .swagger-iframe { 2 | width: 100%; 3 | height: 800px; 4 | border: none; 5 | margin-top: 1rem; 6 | } 7 | 8 | .swagger-container { 9 | width: 100%; 10 | min-height: 800px; 11 | background: white; 12 | border-radius: 0.25rem; 13 | overflow: hidden; 14 | } -------------------------------------------------------------------------------- /force-app/main/default/lwc/swaggerViewer/swaggerViewer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/swaggerViewer/swaggerViewer.js: -------------------------------------------------------------------------------- 1 | import { LightningElement } from 'lwc'; 2 | import { loadScript, loadStyle } from 'lightning/platformResourceLoader'; 3 | import SWAGGER_UI from '@salesforce/resourceUrl/swaggerui'; 4 | import OPENAPI_SPEC from '@salesforce/resourceUrl/OPENAPI_SPEC'; 5 | 6 | export default class SwaggerViewer extends LightningElement { 7 | swaggerInitialized = false; 8 | 9 | renderedCallback() { 10 | if (this.swaggerInitialized) { 11 | return; 12 | } 13 | this.swaggerInitialized = true; 14 | 15 | console.log('Starting Swagger UI initialization...'); 16 | 17 | // Step 1: Load the OpenAPI spec 18 | fetch(OPENAPI_SPEC) 19 | .then(response => { 20 | console.log('OpenAPI spec fetch response:', response.status); 21 | return response.json(); 22 | }) 23 | .then(spec => { 24 | console.log('OpenAPI spec loaded successfully'); 25 | // Step 2: Load Swagger UI CSS 26 | return loadStyle(this, SWAGGER_UI + '/swagger-ui.css') 27 | .then(() => { 28 | console.log('Swagger UI CSS loaded successfully'); 29 | // Step 3: Load Swagger UI JS 30 | return loadScript(this, SWAGGER_UI + '/swagger-ui-bundle.js') 31 | .then(() => { 32 | console.log('Swagger UI Bundle loaded successfully'); 33 | return loadScript(this, SWAGGER_UI + '/swagger-ui-standalone-preset.js'); 34 | }); 35 | }) 36 | .then(() => { 37 | console.log('Swagger UI Standalone Preset loaded successfully'); 38 | return spec; 39 | }); 40 | }) 41 | .then(spec => { 42 | console.log('Initializing Swagger UI with spec'); 43 | const container = this.template.querySelector('.swagger-container'); 44 | console.log('Container found:', container ? 'yes' : 'no'); 45 | 46 | if (!container) { 47 | throw new Error('Swagger container element not found'); 48 | } 49 | 50 | try { 51 | // Create a new div for React to render into 52 | const reactRoot = document.createElement('div'); 53 | reactRoot.id = 'swagger-ui-root'; 54 | container.appendChild(reactRoot); 55 | 56 | // Determine the correct redirect URL based on context 57 | const isLightning = window.location.hostname.includes('lightning.force.com'); 58 | const isSite = window.location.hostname.includes('my.site.com'); 59 | 60 | let oauth2RedirectUrl; 61 | if (isLightning) { 62 | oauth2RedirectUrl = `${window.location.origin}/lightning/n/SwaggerUI`; 63 | } else if (isSite) { 64 | oauth2RedirectUrl = `${window.location.origin}/api/docs`; 65 | } else { 66 | oauth2RedirectUrl = `${window.location.origin}/api/oauth2-redirect.html`; 67 | } 68 | 69 | console.log('OAuth2 Redirect URL:', oauth2RedirectUrl); 70 | 71 | // Initialize Swagger UI 72 | window.SwaggerUIBundle({ 73 | spec: spec, 74 | dom_id: '#swagger-ui-root', 75 | deepLinking: true, 76 | presets: [ 77 | window.SwaggerUIBundle.presets.apis, 78 | window.SwaggerUIStandalonePreset 79 | ], 80 | plugins: [ 81 | window.SwaggerUIBundle.plugins.DownloadUrl 82 | ], 83 | layout: "StandaloneLayout", 84 | oauth2RedirectUrl: oauth2RedirectUrl, 85 | onComplete: () => { 86 | console.log('Swagger UI initialization complete'); 87 | // Add OAuth configuration 88 | window.ui.initOAuth({ 89 | scopes: 'offline_access refresh_token api', 90 | usePkceWithAuthorizationCodeGrant: true, 91 | redirectUrl: oauth2RedirectUrl 92 | }); 93 | } 94 | }); 95 | console.log('Swagger UI initialized successfully'); 96 | } catch (error) { 97 | console.error('Error during Swagger UI initialization:', error.message); 98 | throw error; 99 | } 100 | }) 101 | .catch(error => { 102 | console.error('Error in Swagger UI initialization:', error.message || 'Unknown error'); 103 | if (error.stack) { 104 | console.error('Stack trace:', error.stack); 105 | } 106 | }); 107 | } 108 | } -------------------------------------------------------------------------------- /force-app/main/default/lwc/swaggerViewer/swaggerViewer.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 58.0 4 | true 5 | Swagger UI Viewer 6 | Displays API documentation using Swagger UI 7 | 8 | lightning__AppPage 9 | lightning__RecordPage 10 | lightning__HomePage 11 | lightningCommunity__Page 12 | lightningCommunity__Default 13 | 14 | -------------------------------------------------------------------------------- /force-app/main/default/remoteSiteSettings/editor_swagger.remoteSite-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | true 5 | https://editor.swagger.io 6 | 7 | -------------------------------------------------------------------------------- /force-app/main/default/staticresources/swaggerui.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public 4 | application/x-zip-compressed 5 | 6 | -------------------------------------------------------------------------------- /force-app/main/default/staticresources/swaggerui/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nimba-Solutions/OpenAPI-Unlocked/f737b6dddc5fa2b6edd36ba49b417eee813e00ec/force-app/main/default/staticresources/swaggerui/favicon-16x16.png -------------------------------------------------------------------------------- /force-app/main/default/staticresources/swaggerui/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nimba-Solutions/OpenAPI-Unlocked/f737b6dddc5fa2b6edd36ba49b417eee813e00ec/force-app/main/default/staticresources/swaggerui/favicon-32x32.png -------------------------------------------------------------------------------- /force-app/main/default/staticresources/swaggerui/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | overflow: -moz-scrollbars-vertical; 4 | overflow-y: scroll; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | background: #fafafa; 16 | } 17 | -------------------------------------------------------------------------------- /force-app/main/default/staticresources/swaggerui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /force-app/main/default/staticresources/swaggerui/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Swagger UI: OAuth2 Redirect 5 | 6 | 7 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /force-app/main/default/staticresources/swaggerui/swagger-initializer.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | // 3 | 4 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container 5 | window.ui = SwaggerUIBundle({ 6 | url: "https://petstore.swagger.io/v2/swagger.json", 7 | dom_id: '#swagger-ui', 8 | deepLinking: true, 9 | presets: [ 10 | SwaggerUIBundle.presets.apis, 11 | SwaggerUIStandalonePreset 12 | ], 13 | plugins: [ 14 | SwaggerUIBundle.plugins.DownloadUrl 15 | ], 16 | layout: "StandaloneLayout" 17 | }); 18 | 19 | // 20 | }; 21 | -------------------------------------------------------------------------------- /orgs/beta.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "OpenAPI-Unlocked - Beta Test Org", 3 | "edition": "Developer", 4 | "settings": { 5 | "lightningExperienceSettings": { 6 | "enableS1DesktopEnabled": true 7 | }, 8 | "chatterSettings": { 9 | "enableChatter": true 10 | }, 11 | "userManagementSettings": { 12 | "enableNewProfileUI": true 13 | }, 14 | "securitySettings": { 15 | "enableAdminLoginAsAnyUser": true, 16 | "sessionSettings": { 17 | "forceRelogin": false 18 | } 19 | } } 20 | } -------------------------------------------------------------------------------- /orgs/dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "OpenAPI-Unlocked - Dev Org", 3 | "edition": "Developer", 4 | "features": [ 5 | "Communities", 6 | "DebugApex" 7 | ], 8 | "settings": { 9 | "communitiesSettings": { 10 | "enableNetworksEnabled": true, 11 | "enableOotbProfExtUserOpsEnable": true 12 | }, 13 | "experienceBundleSettings": { 14 | "enableExperienceBundleMetadata": true 15 | }, 16 | "lightningExperienceSettings": { 17 | "enableS1DesktopEnabled": true 18 | }, 19 | "chatterSettings": { 20 | "enableChatter": true 21 | }, 22 | "userManagementSettings": { 23 | "enableNewProfileUI": true 24 | }, 25 | "securitySettings": { 26 | "enableAdminLoginAsAnyUser": true, 27 | "sessionSettings": { 28 | "forceRelogin": false 29 | } 30 | }, 31 | "emailTemplateSettings": { 32 | "enableTemplateEnhancedFolderPref": true 33 | }, 34 | "languageSettings": { 35 | "enableTranslationWorkbench": true 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /orgs/feature.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "OpenAPI-Unlocked - Feature Test Org", 3 | "edition": "Developer", 4 | "settings": { 5 | "lightningExperienceSettings": { 6 | "enableS1DesktopEnabled": true 7 | }, 8 | "chatterSettings": { 9 | "enableChatter": true 10 | }, 11 | "userManagementSettings": { 12 | "enableNewProfileUI": true 13 | }, 14 | "securitySettings": { 15 | "enableAdminLoginAsAnyUser": true, 16 | "sessionSettings": { 17 | "forceRelogin": false 18 | } 19 | }, 20 | "languageSettings": { 21 | "enableTranslationWorkbench": true 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /orgs/release.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "OpenAPI-Unlocked - Release Test Org", 3 | "edition": "Enterprise", 4 | "settings": { 5 | "lightningExperienceSettings": { 6 | "enableS1DesktopEnabled": true 7 | }, 8 | "chatterSettings": { 9 | "enableChatter": true 10 | }, 11 | "userManagementSettings": { 12 | "enableNewProfileUI": true 13 | }, 14 | "securitySettings": { 15 | "enableAdminLoginAsAnyUser": true, 16 | "sessionSettings": { 17 | "forceRelogin": false 18 | } 19 | } } 20 | } -------------------------------------------------------------------------------- /pmd-rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | PMD Rules Index 7 | 8 | 9 | -------------------------------------------------------------------------------- /robot/OpenAPI-Unlocked/tests/create_contact.robot: -------------------------------------------------------------------------------- 1 | *** Settings *** 2 | 3 | Resource cumulusci/robotframework/Salesforce.robot 4 | Library cumulusci.robotframework.PageObjects 5 | 6 | Suite Setup Open Test Browser 7 | Suite Teardown Delete Records and Close Browser 8 | 9 | 10 | *** Test Cases *** 11 | 12 | Via API 13 | ${first_name} = Get fake data first_name 14 | ${last_name} = Get fake data last_name 15 | 16 | ${contact_id} = Salesforce Insert Contact 17 | ... FirstName=${first_name} 18 | ... LastName=${last_name} 19 | 20 | &{contact} = Salesforce Get Contact ${contact_id} 21 | Validate Contact ${contact_id} ${first_name} ${last_name} 22 | 23 | Via UI 24 | ${first_name} = Get fake data first_name 25 | ${last_name} = Get fake data last_name 26 | 27 | Go to page Home Contact 28 | Click Object Button New 29 | Wait for modal New Contact 30 | 31 | Populate Form 32 | ... First Name=${first_name} 33 | ... Last Name=${last_name} 34 | Click Modal Button Save 35 | 36 | Wait Until Modal Is Closed 37 | 38 | ${contact_id} = Get Current Record Id 39 | Store Session Record Contact ${contact_id} 40 | Validate Contact ${contact_id} ${first_name} ${last_name} 41 | 42 | 43 | *** Keywords *** 44 | 45 | Validate Contact 46 | [Arguments] ${contact_id} ${first_name} ${last_name} 47 | [Documentation] 48 | ... Given a contact id, validate that the contact has the 49 | ... expected first and last name both through the detail page in 50 | ... the UI and via the API. 51 | 52 | # Validate via UI 53 | Go to page Detail Contact ${contact_id} 54 | Page Should Contain ${first_name} ${last_name} 55 | 56 | # Validate via API 57 | &{contact} = Salesforce Get Contact ${contact_id} 58 | Should Be Equal ${first_name} ${contact}[FirstName] 59 | Should Be Equal ${last_name} ${contact}[LastName] 60 | -------------------------------------------------------------------------------- /scripts/GenerateSpec.apex: -------------------------------------------------------------------------------- 1 | // This script is used to generate the OpenAPI spec from the Salesforce classes 2 | // The `generate_spec` task in cumulusci.yml exposes it to useful contexts such as CICD pipelines. 3 | OpenAPIParser.parseClasses(); -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | {"packageDirectories": [{"path": "force-app", "default": true}], "namespace": null, "sourceApiVersion": "63.0"} -------------------------------------------------------------------------------- /tasks/openapi_spec.py: -------------------------------------------------------------------------------- 1 | from cumulusci.core.exceptions import TaskOptionsError 2 | from cumulusci.tasks.salesforce import BaseSalesforceApiTask 3 | from cumulusci.utils import inject_namespace 4 | 5 | class GenerateOpenAPISpec(BaseSalesforceApiTask): 6 | """Generates OpenAPI specification from Apex classes.""" 7 | 8 | task_options = { 9 | "version": { 10 | "description": "Version number for the OpenAPI spec", 11 | "required": False, 12 | }, 13 | "title": { 14 | "description": "Title for the OpenAPI spec", 15 | "required": False, 16 | }, 17 | "description": { 18 | "description": "Description for the OpenAPI spec", 19 | "required": False, 20 | }, 21 | } 22 | 23 | def _run_task(self): 24 | # Get the version from options or project config 25 | version = self.options.get("version") 26 | if not version: 27 | version = self.project_config.project__package__version 28 | if not version: 29 | version = "1.0.0" # Fallback default 30 | 31 | # Build the SpecConfig constructor string 32 | title = self.options.get("title", "Salesforce REST API") 33 | description = self.options.get("description", "Combined REST API documentation") 34 | 35 | # Prepare the Apex code with proper string escaping 36 | apex = f""" 37 | OpenAPIParser.SpecConfig config = new OpenAPIParser.SpecConfig('{title}', '{description}', '{version}'); 38 | OpenAPIParser.parseClasses(config); 39 | """ 40 | 41 | # Execute the anonymous Apex 42 | self.logger.info(f"Generating OpenAPI spec with version {version}") 43 | result = self.tooling._call_salesforce( 44 | method="GET", 45 | url=f"{self.tooling.base_url}executeAnonymous", 46 | params={"anonymousBody": apex}, 47 | ) 48 | 49 | # Check the result 50 | anon_results = result.json() 51 | if not anon_results["compiled"]: 52 | raise TaskOptionsError( 53 | f"Compilation error: {anon_results['compileProblem']} at line {anon_results['line']}" 54 | ) 55 | if not anon_results["success"]: 56 | raise TaskOptionsError( 57 | f"Execution error: {anon_results['exceptionMessage']}\n{anon_results['exceptionStackTrace']}" 58 | ) 59 | 60 | self.logger.info("OpenAPI spec generated successfully") -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperienceConfigs/API1.digitalExperienceConfig-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | api 6 | 7 | site/API1 8 | 9 | -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/API1.digitalExperience-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | sfdc_cms__collection 8 | ENABLED 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Check_Password/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "Check_Password", 3 | "type" : "sfdc_cms__route", 4 | "path" : "routes" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Check_Password/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__route", 3 | "title" : "Check Password", 4 | "contentBody" : { 5 | "activeViewId" : "checkPasswordResetEmail", 6 | "configurationTags" : [ ], 7 | "pageAccess" : "UseParent", 8 | "routeType" : "check-password", 9 | "urlPrefix" : "CheckPasswordResetEmail" 10 | }, 11 | "urlName" : "check-password" 12 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Error/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "Error", 3 | "type" : "sfdc_cms__route", 4 | "path" : "routes" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Error/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__route", 3 | "title" : "Error", 4 | "contentBody" : { 5 | "activeViewId" : "error", 6 | "configurationTags" : [ ], 7 | "pageAccess" : "UseParent", 8 | "routeType" : "error", 9 | "urlPrefix" : "error" 10 | }, 11 | "urlName" : "error" 12 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Forgot_Password/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "Forgot_Password", 3 | "type" : "sfdc_cms__route", 4 | "path" : "routes" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Forgot_Password/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__route", 3 | "title" : "Forgot Password", 4 | "contentBody" : { 5 | "activeViewId" : "forgotPassword", 6 | "configurationTags" : [ ], 7 | "pageAccess" : "UseParent", 8 | "routeType" : "forgot-password", 9 | "urlPrefix" : "ForgotPassword" 10 | }, 11 | "urlName" : "forgot-password" 12 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Home/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "Home", 3 | "type" : "sfdc_cms__route", 4 | "path" : "routes" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Home/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__route", 3 | "title" : "Home", 4 | "contentBody" : { 5 | "activeViewId" : "home", 6 | "configurationTags" : [ ], 7 | "pageAccess" : "UseParent", 8 | "routeType" : "home", 9 | "urlPrefix" : "" 10 | }, 11 | "urlName" : "home" 12 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Login/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "Login", 3 | "type" : "sfdc_cms__route", 4 | "path" : "routes" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Login/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__route", 3 | "title" : "Login", 4 | "contentBody" : { 5 | "activeViewId" : "login", 6 | "configurationTags" : [ ], 7 | "pageAccess" : "UseParent", 8 | "routeType" : "login-main", 9 | "urlPrefix" : "login" 10 | }, 11 | "urlName" : "login" 12 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/News_Detail__c/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "News_Detail__c", 3 | "type" : "sfdc_cms__route", 4 | "path" : "routes" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/News_Detail__c/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__route", 3 | "title" : "News Detail", 4 | "contentBody" : { 5 | "activeViewId" : "newsDetail", 6 | "configurationTags" : [ ], 7 | "pageAccess" : "UseParent", 8 | "routeType" : "managed-content-sfdc_cms__news", 9 | "urlPrefix" : "news" 10 | }, 11 | "urlName" : "news-detail" 12 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Register/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "Register", 3 | "type" : "sfdc_cms__route", 4 | "path" : "routes" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Register/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__route", 3 | "title" : "Register", 4 | "contentBody" : { 5 | "activeViewId" : "register", 6 | "configurationTags" : [ ], 7 | "pageAccess" : "UseParent", 8 | "routeType" : "self-register", 9 | "urlPrefix" : "SelfRegister" 10 | }, 11 | "urlName" : "register" 12 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Service_Not_Available/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "Service_Not_Available", 3 | "type" : "sfdc_cms__route", 4 | "path" : "routes" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Service_Not_Available/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__route", 3 | "title" : "Service Not Available", 4 | "contentBody" : { 5 | "activeViewId" : "serviceNotAvailable", 6 | "configurationTags" : [ "allow-in-static-site" ], 7 | "pageAccess" : "UseParent", 8 | "routeType" : "service-not-available", 9 | "urlPrefix" : "service-not-available" 10 | }, 11 | "urlName" : "service-not-available" 12 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Too_Many_Requests/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "Too_Many_Requests", 3 | "type" : "sfdc_cms__route", 4 | "path" : "routes" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/Too_Many_Requests/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__route", 3 | "title" : "Too Many Requests", 4 | "contentBody" : { 5 | "activeViewId" : "tooManyRequests", 6 | "configurationTags" : [ "too-many-requests", "allow-in-static-site" ], 7 | "pageAccess" : "UseParent", 8 | "routeType" : "too-many-requests", 9 | "urlPrefix" : "too-many-requests" 10 | }, 11 | "urlName" : "too-many-requests" 12 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/docs__c/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "docs__c", 3 | "type" : "sfdc_cms__route", 4 | "path" : "routes" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__route/docs__c/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__route", 3 | "title" : "Docs", 4 | "contentBody" : { 5 | "activeViewId" : "Docs", 6 | "configurationTags" : [ ], 7 | "pageAccess" : "UseParent", 8 | "routeType" : "custom-docs", 9 | "urlPrefix" : "docs" 10 | }, 11 | "urlName" : "docs" 12 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__site/API1/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "API1", 3 | "type" : "sfdc_cms__site", 4 | "path" : "" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__site/API1/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__site", 3 | "title" : "API", 4 | "contentBody" : { 5 | "authenticationType" : "AUTHENTICATED" 6 | }, 7 | "urlName" : "api" 8 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/Docs/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "Docs", 3 | "type" : "sfdc_cms__view", 4 | "path" : "views" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/Docs/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__view", 3 | "title" : "Docs", 4 | "contentBody" : { 5 | "component" : { 6 | "children" : [ { 7 | "children" : [ { 8 | "attributes" : { 9 | "backgroundImageConfig" : "", 10 | "backgroundImageOverlay" : "rgba(0,0,0,0)", 11 | "componentSpacerSize" : "", 12 | "layoutDirectionDesktop" : "row", 13 | "layoutDirectionMobile" : "column", 14 | "layoutDirectionTablet" : "column", 15 | "maxContentWidth" : "", 16 | "sectionColumnGutterWidth" : "", 17 | "sectionConfig" : "{\"UUID\":\"93f34f76-d4b1-4605-b5d8-8508d6154c4f\",\"columns\":[{\"UUID\":\"919f0dab-226e-4964-a2f1-8438b2943f8e\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":[]}]}", 18 | "sectionMinHeight" : "", 19 | "sectionVerticalAlign" : "flex-start" 20 | }, 21 | "children" : [ { 22 | "id" : "919f0dab-226e-4964-a2f1-8438b2943f8e", 23 | "name" : "col1", 24 | "title" : "Column 1", 25 | "type" : "region" 26 | } ], 27 | "definition" : "community_layout:section", 28 | "id" : "93f34f76-d4b1-4605-b5d8-8508d6154c4f", 29 | "type" : "component" 30 | } ], 31 | "id" : "79f5c95b-604d-4951-8cc5-a5c995831ad2", 32 | "name" : "content", 33 | "title" : "Content", 34 | "type" : "region" 35 | }, { 36 | "children" : [ { 37 | "attributes" : { 38 | "customHeadTags" : "", 39 | "description" : "", 40 | "pageTitle" : "Docs", 41 | "recordId" : "{!recordId}" 42 | }, 43 | "definition" : "community_builder:seoAssistant", 44 | "id" : "df2a445c-3501-49ab-9d00-f4e4783c3739", 45 | "type" : "component" 46 | } ], 47 | "id" : "30ac26e3-d429-47e7-b05b-4e394bf9cbf4", 48 | "name" : "sfdcHiddenRegion", 49 | "title" : "sfdcHiddenRegion", 50 | "type" : "region" 51 | } ], 52 | "definition" : "community_layout:sldsFlexibleLayout", 53 | "id" : "7944ced5-98ea-4b58-9c9b-69f5724148fb", 54 | "type" : "component" 55 | }, 56 | "dataProviders" : [ ], 57 | "themeLayoutType" : "Inner", 58 | "viewType" : "custom-docs" 59 | }, 60 | "urlName" : "docs" 61 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/checkPasswordResetEmail/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "checkPasswordResetEmail", 3 | "type" : "sfdc_cms__view", 4 | "path" : "views" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/checkPasswordResetEmail/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__view", 3 | "title" : "Check Password", 4 | "contentBody" : { 5 | "component" : { 6 | "children" : [ { 7 | "children" : [ { 8 | "attributes" : { 9 | "backgroundImageConfig" : "", 10 | "backgroundImageOverlay" : "rgba(0,0,0,0)", 11 | "componentSpacerSize" : "", 12 | "layoutDirectionDesktop" : "row", 13 | "layoutDirectionMobile" : "column", 14 | "layoutDirectionTablet" : "column", 15 | "maxContentWidth" : "", 16 | "sectionColumnGutterWidth" : "", 17 | "sectionConfig" : "{\"UUID\":\"d70bedec-86ed-4881-90dc-2246e4a4d821\",\"columns\":[{\"UUID\":\"c3802a16-7757-4cc8-bd9b-29ab962a5e14\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}", 18 | "sectionMinHeight" : "", 19 | "sectionVerticalAlign" : "flex-start" 20 | }, 21 | "children" : [ { 22 | "children" : [ { 23 | "attributes" : { 24 | "alignment" : "left", 25 | "imageInfo" : "", 26 | "imageInfoMobile" : "", 27 | "logoWidth" : 250 28 | }, 29 | "definition" : "dxp_content_layout:siteLogo", 30 | "id" : "32f369c0-2632-4c8f-92c9-77f65701f81b", 31 | "type" : "component" 32 | }, { 33 | "attributes" : { 34 | "checkEmailMessage" : "Check the email account associated with your username for the link to reset your password. If you didn't get an email, check your Spam folder. Or contact your administrator.", 35 | "returnButtonLabel" : "Back to login", 36 | "titleLabel" : "Now check your email" 37 | }, 38 | "definition" : "community_login:checkEmail", 39 | "id" : "ae12a988-4dd9-4903-92e0-e14c7a714da4", 40 | "type" : "component" 41 | } ], 42 | "id" : "c3802a16-7757-4cc8-bd9b-29ab962a5e14", 43 | "name" : "col1", 44 | "title" : "Column 1", 45 | "type" : "region" 46 | } ], 47 | "definition" : "community_layout:section", 48 | "id" : "d70bedec-86ed-4881-90dc-2246e4a4d821", 49 | "type" : "component" 50 | } ], 51 | "id" : "f6506656-5c98-4a12-ac32-0a358419585a", 52 | "name" : "content", 53 | "title" : "Content", 54 | "type" : "region" 55 | }, { 56 | "children" : [ { 57 | "attributes" : { 58 | "customHeadTags" : "", 59 | "description" : "", 60 | "pageTitle" : "Check Password", 61 | "recordId" : "{!recordId}" 62 | }, 63 | "definition" : "community_builder:seoAssistant", 64 | "id" : "119d5e8f-0d50-4b15-b601-582637187985", 65 | "type" : "component" 66 | } ], 67 | "id" : "a862cf5b-084d-46ab-8ece-7b4a97b7eafc", 68 | "name" : "sfdcHiddenRegion", 69 | "title" : "sfdcHiddenRegion", 70 | "type" : "region" 71 | } ], 72 | "definition" : "community_layout:sldsFlexibleLayout", 73 | "id" : "9ada0af3-3b12-4019-bed0-0c53a73db7d6", 74 | "type" : "component" 75 | }, 76 | "dataProviders" : [ ], 77 | "themeLayoutType" : "Inner", 78 | "viewType" : "check-password" 79 | }, 80 | "urlName" : "check-password" 81 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/error/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "error", 3 | "type" : "sfdc_cms__view", 4 | "path" : "views" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/error/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__view", 3 | "title" : "Error", 4 | "contentBody" : { 5 | "component" : { 6 | "children" : [ { 7 | "children" : [ { 8 | "attributes" : { 9 | "backgroundImageConfig" : "", 10 | "backgroundImageOverlay" : "rgba(0,0,0,0)", 11 | "componentSpacerSize" : "", 12 | "layoutDirectionDesktop" : "row", 13 | "layoutDirectionMobile" : "column", 14 | "layoutDirectionTablet" : "column", 15 | "maxContentWidth" : "", 16 | "sectionColumnGutterWidth" : "", 17 | "sectionConfig" : "{\"UUID\":\"a9834574-f7b0-4436-b6fa-4f3836deaecb\",\"columns\":[{\"UUID\":\"df8a47b7-0153-4615-9643-f3f0a113522e\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}", 18 | "sectionMinHeight" : "", 19 | "sectionVerticalAlign" : "flex-start" 20 | }, 21 | "children" : [ { 22 | "children" : [ { 23 | "attributes" : { 24 | "imageInfos" : "", 25 | "richTextValue" : "

Invalid Page

" 26 | }, 27 | "definition" : "community_builder:richTextEditor", 28 | "id" : "60834bdf-d985-4d82-868d-3739fc66aafb", 29 | "type" : "component" 30 | } ], 31 | "id" : "df8a47b7-0153-4615-9643-f3f0a113522e", 32 | "name" : "col1", 33 | "title" : "Column 1", 34 | "type" : "region" 35 | } ], 36 | "definition" : "community_layout:section", 37 | "id" : "a9834574-f7b0-4436-b6fa-4f3836deaecb", 38 | "type" : "component" 39 | } ], 40 | "id" : "8fcaa285-972b-4988-9e31-b8731acd1d40", 41 | "name" : "content", 42 | "title" : "Content", 43 | "type" : "region" 44 | }, { 45 | "children" : [ { 46 | "attributes" : { 47 | "customHeadTags" : "", 48 | "description" : "", 49 | "pageTitle" : "Error", 50 | "recordId" : "{!recordId}" 51 | }, 52 | "definition" : "community_builder:seoAssistant", 53 | "id" : "40335107-5ea8-4ee4-85f7-7dbd4694a566", 54 | "type" : "component" 55 | } ], 56 | "id" : "bda9397c-8b25-4731-b6fa-2a979c09f45b", 57 | "name" : "sfdcHiddenRegion", 58 | "title" : "sfdcHiddenRegion", 59 | "type" : "region" 60 | } ], 61 | "definition" : "community_layout:sldsFlexibleLayout", 62 | "id" : "c10e47ff-457b-413c-8d02-fd595c40a54d", 63 | "type" : "component" 64 | }, 65 | "dataProviders" : [ ], 66 | "themeLayoutType" : "Inner", 67 | "viewType" : "error" 68 | }, 69 | "urlName" : "error" 70 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/forgotPassword/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "forgotPassword", 3 | "type" : "sfdc_cms__view", 4 | "path" : "views" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/forgotPassword/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__view", 3 | "title" : "Forgot Password", 4 | "contentBody" : { 5 | "component" : { 6 | "children" : [ { 7 | "children" : [ { 8 | "attributes" : { 9 | "backgroundImageConfig" : "", 10 | "backgroundImageOverlay" : "rgba(0,0,0,0)", 11 | "componentSpacerSize" : "", 12 | "layoutDirectionDesktop" : "row", 13 | "layoutDirectionMobile" : "column", 14 | "layoutDirectionTablet" : "column", 15 | "maxContentWidth" : "", 16 | "sectionColumnGutterWidth" : "", 17 | "sectionConfig" : "{\"UUID\":\"f32ee183-a004-42b7-896c-ac66debc7fd3\",\"columns\":[{\"UUID\":\"8c55e83d-04a8-4020-9ac8-ae4e43fdd248\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}", 18 | "sectionMinHeight" : "", 19 | "sectionVerticalAlign" : "flex-start" 20 | }, 21 | "children" : [ { 22 | "children" : [ { 23 | "attributes" : { 24 | "alignment" : "left", 25 | "imageInfo" : "", 26 | "imageInfoMobile" : "", 27 | "logoWidth" : 250 28 | }, 29 | "definition" : "dxp_content_layout:siteLogo", 30 | "id" : "9011399e-eab4-4e9f-bf01-4f3771a2bdb1", 31 | "type" : "component" 32 | }, { 33 | "attributes" : { 34 | "cancelButtonLabel" : "Cancel", 35 | "checkEmailUrl" : "./CheckPasswordResetEmail", 36 | "instructionsLabel" : "To reset your password, enter your username. We'll send a reset-password link to the email address associated with your account.", 37 | "submitButtonLabel" : "Reset", 38 | "titleLabel" : "Forgot your password?", 39 | "usernameLabel" : "Username" 40 | }, 41 | "definition" : "community_login:forgotPassword", 42 | "id" : "3640a2cf-3fc7-425b-8138-1e1a8b9a425c", 43 | "type" : "component" 44 | } ], 45 | "id" : "8c55e83d-04a8-4020-9ac8-ae4e43fdd248", 46 | "name" : "col1", 47 | "title" : "Column 1", 48 | "type" : "region" 49 | } ], 50 | "definition" : "community_layout:section", 51 | "id" : "f32ee183-a004-42b7-896c-ac66debc7fd3", 52 | "type" : "component" 53 | } ], 54 | "id" : "42591273-b012-4d2f-bf91-7697f218dc81", 55 | "name" : "content", 56 | "title" : "Content", 57 | "type" : "region" 58 | }, { 59 | "children" : [ { 60 | "attributes" : { 61 | "customHeadTags" : "", 62 | "description" : "", 63 | "pageTitle" : "Forgot Password", 64 | "recordId" : "{!recordId}" 65 | }, 66 | "definition" : "community_builder:seoAssistant", 67 | "id" : "48ccce54-8cec-47f6-b9eb-c5a17741b9a3", 68 | "type" : "component" 69 | } ], 70 | "id" : "f3107990-1715-4970-b5b1-e1d06f22ab33", 71 | "name" : "sfdcHiddenRegion", 72 | "title" : "sfdcHiddenRegion", 73 | "type" : "region" 74 | } ], 75 | "definition" : "community_layout:sldsFlexibleLayout", 76 | "id" : "fff6ecb9-3d23-43f3-be21-9e3d8db0057e", 77 | "type" : "component" 78 | }, 79 | "dataProviders" : [ ], 80 | "themeLayoutType" : "Inner", 81 | "viewType" : "forgot-password" 82 | }, 83 | "urlName" : "forgot-password" 84 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/home/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "home", 3 | "type" : "sfdc_cms__view", 4 | "path" : "views" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/home/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__view", 3 | "title" : "Home", 4 | "contentBody" : { 5 | "component" : { 6 | "children" : [ { 7 | "children" : [ { 8 | "attributes" : { 9 | "backgroundImageConfig" : "", 10 | "backgroundImageOverlay" : "rgba(0,0,0,0)", 11 | "componentSpacerSize" : "", 12 | "layoutDirectionDesktop" : "row", 13 | "layoutDirectionMobile" : "column", 14 | "layoutDirectionTablet" : "column", 15 | "maxContentWidth" : "", 16 | "sectionColumnGutterWidth" : "", 17 | "sectionConfig" : "{\"UUID\":\"04af775e-9bd4-4dda-8ab2-6d807b2f462b\",\"columns\":[{\"UUID\":\"38dc555d-49f2-43fd-9bbd-5ccc71bced80\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}", 18 | "sectionMinHeight" : "", 19 | "sectionVerticalAlign" : "flex-start" 20 | }, 21 | "children" : [ { 22 | "children" : [ { 23 | "attributes" : { 24 | "imageInfos" : "", 25 | "richTextValue" : "

Start Building Your Page

Drag and drop a component into the content slots.
" 26 | }, 27 | "definition" : "community_builder:htmlEditor", 28 | "id" : "b9f0f231-c21f-4f32-a322-40588ce78a4f", 29 | "type" : "component" 30 | } ], 31 | "id" : "38dc555d-49f2-43fd-9bbd-5ccc71bced80", 32 | "name" : "col1", 33 | "title" : "Column 1", 34 | "type" : "region" 35 | } ], 36 | "definition" : "community_layout:section", 37 | "id" : "04af775e-9bd4-4dda-8ab2-6d807b2f462b", 38 | "type" : "component" 39 | } ], 40 | "id" : "d913ae03-06b3-42b8-b9b7-3bde71b76c27", 41 | "name" : "content", 42 | "title" : "Content", 43 | "type" : "region" 44 | }, { 45 | "children" : [ { 46 | "attributes" : { 47 | "customHeadTags" : "", 48 | "description" : "", 49 | "pageTitle" : "Home", 50 | "recordId" : "{!recordId}" 51 | }, 52 | "definition" : "community_builder:seoAssistant", 53 | "id" : "5ee966a6-561d-4219-9153-ab9277fe4fec", 54 | "type" : "component" 55 | } ], 56 | "id" : "a82c5d8d-3c19-4ccb-925b-d3928fc8f1e4", 57 | "name" : "sfdcHiddenRegion", 58 | "title" : "sfdcHiddenRegion", 59 | "type" : "region" 60 | } ], 61 | "definition" : "community_layout:sldsFlexibleLayout", 62 | "id" : "367ab337-429c-440f-9a04-8d255179673b", 63 | "type" : "component" 64 | }, 65 | "dataProviders" : [ ], 66 | "themeLayoutType" : "Inner", 67 | "viewType" : "home" 68 | }, 69 | "urlName" : "home" 70 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/login/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "login", 3 | "type" : "sfdc_cms__view", 4 | "path" : "views" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/login/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__view", 3 | "title" : "Login", 4 | "contentBody" : { 5 | "component" : { 6 | "children" : [ { 7 | "children" : [ { 8 | "attributes" : { 9 | "backgroundImageConfig" : "", 10 | "backgroundImageOverlay" : "rgba(0,0,0,0)", 11 | "componentSpacerSize" : "", 12 | "layoutDirectionDesktop" : "row", 13 | "layoutDirectionMobile" : "column", 14 | "layoutDirectionTablet" : "column", 15 | "maxContentWidth" : "", 16 | "sectionColumnGutterWidth" : "", 17 | "sectionConfig" : "{\"UUID\":\"cda2faff-5377-4d78-95a2-4aeb66fbf959\",\"columns\":[{\"UUID\":\"979ce9e0-4027-46db-8f3d-39bc9ddeed12\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}", 18 | "sectionMinHeight" : "", 19 | "sectionVerticalAlign" : "flex-start" 20 | }, 21 | "children" : [ { 22 | "children" : [ { 23 | "attributes" : { 24 | "alignment" : "left", 25 | "imageInfo" : "", 26 | "imageInfoMobile" : "", 27 | "logoWidth" : 250 28 | }, 29 | "definition" : "dxp_content_layout:siteLogo", 30 | "id" : "11aea50b-20a1-4e93-8c28-dd74482af240", 31 | "type" : "component" 32 | }, { 33 | "attributes" : { 34 | "forgotPasswordLabel" : "Forgot your password?", 35 | "forgotPasswordUrl" : "/ForgotPassword", 36 | "loginButtonLabel" : "Log In", 37 | "passwordLabel" : "Password", 38 | "selfRegisterLabel" : "Not a member?", 39 | "selfRegisterUrl" : "/SelfRegister", 40 | "startUrl" : "", 41 | "usernameLabel" : "Username" 42 | }, 43 | "definition" : "community_login:loginForm", 44 | "id" : "ebd16036-669f-469d-afe1-5b80d6e84e18", 45 | "type" : "component" 46 | }, { 47 | "attributes" : { 48 | "employeeLoginLinkLabel" : "Are you an employee? Log in" 49 | }, 50 | "definition" : "community_login:employeeLoginLink", 51 | "id" : "c0bd017a-c2d5-45dd-91b8-3883cda2e103", 52 | "type" : "component" 53 | } ], 54 | "id" : "979ce9e0-4027-46db-8f3d-39bc9ddeed12", 55 | "name" : "col1", 56 | "title" : "Column 1", 57 | "type" : "region" 58 | } ], 59 | "definition" : "community_layout:section", 60 | "id" : "cda2faff-5377-4d78-95a2-4aeb66fbf959", 61 | "type" : "component" 62 | } ], 63 | "id" : "12aab164-ee76-412f-8817-9c558f2f2c95", 64 | "name" : "content", 65 | "title" : "Content", 66 | "type" : "region" 67 | }, { 68 | "children" : [ { 69 | "attributes" : { 70 | "customHeadTags" : "", 71 | "description" : "", 72 | "pageTitle" : "Login", 73 | "recordId" : "{!recordId}" 74 | }, 75 | "definition" : "community_builder:seoAssistant", 76 | "id" : "d7e00602-1690-496d-b18f-10b87d17feb1", 77 | "type" : "component" 78 | } ], 79 | "id" : "68bb93a0-5029-4f9a-9fcf-d83640ccc7a2", 80 | "name" : "sfdcHiddenRegion", 81 | "title" : "sfdcHiddenRegion", 82 | "type" : "region" 83 | } ], 84 | "definition" : "community_layout:sldsFlexibleLayout", 85 | "id" : "ac4a37a9-f895-444d-b444-503cfcb1117a", 86 | "type" : "component" 87 | }, 88 | "dataProviders" : [ ], 89 | "themeLayoutType" : "Inner", 90 | "viewType" : "login-main" 91 | }, 92 | "urlName" : "login" 93 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/newsDetail/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "newsDetail", 3 | "type" : "sfdc_cms__view", 4 | "path" : "views" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/newsDetail/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__view", 3 | "title" : "News Detail", 4 | "contentBody" : { 5 | "component" : { 6 | "children" : [ { 7 | "children" : [ { 8 | "attributes" : { 9 | "backgroundImageConfig" : "", 10 | "backgroundImageOverlay" : "rgba(0,0,0,0)", 11 | "componentSpacerSize" : "", 12 | "layoutDirectionDesktop" : "row", 13 | "layoutDirectionMobile" : "column", 14 | "layoutDirectionTablet" : "column", 15 | "maxContentWidth" : "", 16 | "sectionColumnGutterWidth" : "", 17 | "sectionConfig" : "{\"UUID\":\"6f0e7814-5625-4d94-948d-c91265aa4620\",\"columns\":[{\"UUID\":\"7d242fc0-8472-40f8-9788-f95703797ee5\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":[]}]}", 18 | "sectionMinHeight" : "", 19 | "sectionVerticalAlign" : "flex-start" 20 | }, 21 | "children" : [ { 22 | "id" : "7d242fc0-8472-40f8-9788-f95703797ee5", 23 | "name" : "col1", 24 | "title" : "Column 1", 25 | "type" : "region" 26 | } ], 27 | "definition" : "community_layout:section", 28 | "id" : "6f0e7814-5625-4d94-948d-c91265aa4620", 29 | "type" : "component" 30 | } ], 31 | "id" : "d1c85c8b-de49-40c9-ab40-592d538233ef", 32 | "name" : "content", 33 | "title" : "Content", 34 | "type" : "region" 35 | }, { 36 | "children" : [ { 37 | "attributes" : { 38 | "customHeadTags" : "", 39 | "description" : "", 40 | "pageTitle" : "{!Content.contentTypeLabel}.{!Content.title}", 41 | "recordId" : "{!recordId}" 42 | }, 43 | "definition" : "community_builder:seoAssistant", 44 | "id" : "76d63a5c-0c5f-440a-971f-953ad29698f8", 45 | "type" : "component" 46 | } ], 47 | "id" : "1a9e4f57-3f41-4bc1-8323-671671aceedb", 48 | "name" : "sfdcHiddenRegion", 49 | "title" : "sfdcHiddenRegion", 50 | "type" : "region" 51 | } ], 52 | "definition" : "community_layout:sldsFlexibleLayout", 53 | "id" : "e1e8b206-595c-45fd-b24c-eb7c95d56bce", 54 | "type" : "component" 55 | }, 56 | "dataProviders" : [ ], 57 | "themeLayoutType" : "Inner", 58 | "viewType" : "managed-content-sfdc_cms__news" 59 | }, 60 | "urlName" : "news-detail" 61 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/register/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "register", 3 | "type" : "sfdc_cms__view", 4 | "path" : "views" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/register/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__view", 3 | "title" : "Register", 4 | "contentBody" : { 5 | "component" : { 6 | "children" : [ { 7 | "children" : [ { 8 | "attributes" : { 9 | "backgroundImageConfig" : "", 10 | "backgroundImageOverlay" : "rgba(0,0,0,0)", 11 | "componentSpacerSize" : "", 12 | "layoutDirectionDesktop" : "row", 13 | "layoutDirectionMobile" : "column", 14 | "layoutDirectionTablet" : "column", 15 | "maxContentWidth" : "", 16 | "sectionColumnGutterWidth" : "", 17 | "sectionConfig" : "{\"UUID\":\"736ddc6f-b584-44f5-99f5-2a425b3d94c6\",\"columns\":[{\"UUID\":\"94193e85-f401-4e3d-ad5b-a39feffbaa69\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}", 18 | "sectionMinHeight" : "", 19 | "sectionVerticalAlign" : "flex-start" 20 | }, 21 | "children" : [ { 22 | "children" : [ { 23 | "attributes" : { 24 | "alignment" : "left", 25 | "imageInfo" : "", 26 | "imageInfoMobile" : "", 27 | "logoWidth" : 250 28 | }, 29 | "definition" : "dxp_content_layout:siteLogo", 30 | "id" : "c6d94efc-439b-4341-976b-6eb7a5dd94ac", 31 | "type" : "component" 32 | }, { 33 | "attributes" : { 34 | "cancelLinkLabel" : "Already have an account?", 35 | "confirmPasswordLabel" : "Confirm Password", 36 | "emailLabel" : "Email", 37 | "firstnameLabel" : "First Name", 38 | "includePasswordField" : true, 39 | "lastnameLabel" : "Last Name", 40 | "passwordLabel" : "Create Password", 41 | "regConfirmUrl" : "./CheckPasswordResetEmail", 42 | "submitButtonLabel" : "Sign Up" 43 | }, 44 | "definition" : "community_login:selfRegister", 45 | "id" : "9908ef53-d1ea-4ee0-9d2d-e876015942ae", 46 | "type" : "component" 47 | } ], 48 | "id" : "94193e85-f401-4e3d-ad5b-a39feffbaa69", 49 | "name" : "col1", 50 | "title" : "Column 1", 51 | "type" : "region" 52 | } ], 53 | "definition" : "community_layout:section", 54 | "id" : "736ddc6f-b584-44f5-99f5-2a425b3d94c6", 55 | "type" : "component" 56 | } ], 57 | "id" : "a5d8c5f0-6d8b-4313-bfb5-0c460cd9d719", 58 | "name" : "content", 59 | "title" : "Content", 60 | "type" : "region" 61 | }, { 62 | "children" : [ { 63 | "attributes" : { 64 | "customHeadTags" : "", 65 | "description" : "", 66 | "pageTitle" : "Register", 67 | "recordId" : "{!recordId}" 68 | }, 69 | "definition" : "community_builder:seoAssistant", 70 | "id" : "cfa7bd3d-8cda-464b-a81d-dff347b29a50", 71 | "type" : "component" 72 | } ], 73 | "id" : "c439b01f-0e3f-43bf-b0bd-c85864157556", 74 | "name" : "sfdcHiddenRegion", 75 | "title" : "sfdcHiddenRegion", 76 | "type" : "region" 77 | } ], 78 | "definition" : "community_layout:sldsFlexibleLayout", 79 | "id" : "11ac3b96-36ea-4743-89ba-c18689fe1089", 80 | "type" : "component" 81 | }, 82 | "dataProviders" : [ ], 83 | "themeLayoutType" : "Inner", 84 | "viewType" : "self-register" 85 | }, 86 | "urlName" : "register" 87 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/serviceNotAvailable/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "serviceNotAvailable", 3 | "type" : "sfdc_cms__view", 4 | "path" : "views" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/serviceNotAvailable/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__view", 3 | "title" : "Service Not Available", 4 | "contentBody" : { 5 | "component" : { 6 | "children" : [ { 7 | "children" : [ { 8 | "attributes" : { 9 | "backgroundImageConfig" : "", 10 | "backgroundImageOverlay" : "rgba(0,0,0,0)", 11 | "componentSpacerSize" : "", 12 | "layoutDirectionDesktop" : "row", 13 | "layoutDirectionMobile" : "column", 14 | "layoutDirectionTablet" : "column", 15 | "maxContentWidth" : "", 16 | "sectionColumnGutterWidth" : "", 17 | "sectionConfig" : "{\"UUID\":\"af3973a3-af18-479a-97dd-1f3168af47e3\",\"columns\":[{\"UUID\":\"e43b1ce2-599b-4618-aedf-d0c63074d82f\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}", 18 | "sectionMinHeight" : "", 19 | "sectionVerticalAlign" : "flex-start" 20 | }, 21 | "children" : [ { 22 | "children" : [ { 23 | "attributes" : { 24 | "imageInfos" : "", 25 | "richTextValue" : "
\n\t
\n
\n
\n\t

Looks like the site is temporarily unavailable

\n\t
\n\t

Please try again in a bit.

\n
" 26 | }, 27 | "definition" : "community_builder:htmlEditor", 28 | "id" : "34dd318d-7597-450d-b8b8-0d7f376215c8", 29 | "type" : "component" 30 | } ], 31 | "id" : "e43b1ce2-599b-4618-aedf-d0c63074d82f", 32 | "name" : "col1", 33 | "title" : "Column 1", 34 | "type" : "region" 35 | } ], 36 | "definition" : "community_layout:section", 37 | "id" : "af3973a3-af18-479a-97dd-1f3168af47e3", 38 | "type" : "component" 39 | } ], 40 | "id" : "de357c71-eb03-4baa-97c5-88941fb689b6", 41 | "name" : "content", 42 | "title" : "Content", 43 | "type" : "region" 44 | } ], 45 | "definition" : "community_layout:sldsFlexibleLayout", 46 | "id" : "14862ef9-7b2a-41bc-8f69-4223048cd95c", 47 | "type" : "component" 48 | }, 49 | "dataProviders" : [ ], 50 | "themeLayoutType" : "ServiceNotAvailable", 51 | "viewType" : "service-not-available" 52 | }, 53 | "urlName" : "service-not-available" 54 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/tooManyRequests/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiName" : "tooManyRequests", 3 | "type" : "sfdc_cms__view", 4 | "path" : "views" 5 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/digitalExperiences/site/API1/sfdc_cms__view/tooManyRequests/content.json: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "sfdc_cms__view", 3 | "title" : "Too Many Requests", 4 | "contentBody" : { 5 | "component" : { 6 | "children" : [ { 7 | "children" : [ { 8 | "attributes" : { 9 | "backgroundImageConfig" : "", 10 | "backgroundImageOverlay" : "rgba(0,0,0,0)", 11 | "componentSpacerSize" : "", 12 | "layoutDirectionDesktop" : "row", 13 | "layoutDirectionMobile" : "column", 14 | "layoutDirectionTablet" : "column", 15 | "maxContentWidth" : "", 16 | "sectionColumnGutterWidth" : "", 17 | "sectionConfig" : "{\"UUID\":\"fcfe4f78-7db1-40ad-b72e-edb3e64a7333\",\"columns\":[{\"UUID\":\"9366ffad-a852-4530-ab5d-feb746f07967\",\"columnName\":\"Column 1\",\"columnKey\":\"col1\",\"columnWidth\":\"12\",\"seedComponents\":null}]}", 18 | "sectionMinHeight" : "", 19 | "sectionVerticalAlign" : "flex-start" 20 | }, 21 | "children" : [ { 22 | "children" : [ { 23 | "attributes" : { 24 | "imageInfos" : "", 25 | "richTextValue" : "
\n\t
\n
\n
\n\t

Looks like the site is experiencing higher than usual demand…

\n\t

Don't go anywhere. We'll redirect you in a moment.

\n
" 26 | }, 27 | "definition" : "community_builder:htmlEditor", 28 | "id" : "8a044e56-8ab8-4c83-8dea-5092a6872671", 29 | "type" : "component" 30 | }, { 31 | "attributes" : { }, 32 | "definition" : "experience_availability:autoRefresh", 33 | "id" : "f54ccc2a-19e6-4e85-a593-ae8ea1561914", 34 | "type" : "component" 35 | } ], 36 | "id" : "9366ffad-a852-4530-ab5d-feb746f07967", 37 | "name" : "col1", 38 | "title" : "Column 1", 39 | "type" : "region" 40 | } ], 41 | "definition" : "community_layout:section", 42 | "id" : "fcfe4f78-7db1-40ad-b72e-edb3e64a7333", 43 | "type" : "component" 44 | } ], 45 | "id" : "2f10388a-b6d2-42f0-a8d5-1f2871779211", 46 | "name" : "content", 47 | "title" : "Content", 48 | "type" : "region" 49 | } ], 50 | "definition" : "community_layout:sldsFlexibleLayout", 51 | "id" : "a8df05b3-185d-48d2-ae6d-3ef3d04e0512", 52 | "type" : "component" 53 | }, 54 | "dataProviders" : [ ], 55 | "themeLayoutType" : "ServiceNotAvailable", 56 | "viewType" : "too-many-requests" 57 | }, 58 | "urlName" : "too-many-requests" 59 | } -------------------------------------------------------------------------------- /unpackaged/experience/src/navigationMenus/SFDC_Default_Navigation_API.navigationMenu-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | API 4 | Network 5 | 6 | 7 | 8 | 1 9 | true 10 | ShowMoreTopics 11 | NavigationalTopic 12 | 13 | 14 | -------------------------------------------------------------------------------- /unpackaged/experience/src/networkBranding/cbAPI.networkBranding: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /unpackaged/experience/src/networkBranding/cbAPI.networkBranding-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | API 4 | #1797C0 5 | #FFFFFF 6 | #B1BAC1 7 | #222222 8 | #51606E 9 | #DDE4E9 10 | #222222 11 | #51606E 12 | #FFFFFF 13 | 14 | -------------------------------------------------------------------------------- /unpackaged/experience/src/networks/API.network-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | false 5 | unfiled$public/CommunityChangePasswordEmailTemplate 6 | 7 | true 8 | david.bingham@nimbasolutions.com 9 | API 10 | false 11 | true 12 | false 13 | true 14 | false 15 | true 16 | false 17 | false 18 | false 19 | false 20 | false 21 | false 22 | true 23 | false 24 | true 25 | false 26 | false 27 | false 28 | true 29 | true 30 | true 31 | false 32 | false 33 | unfiled$public/CommunityForgotPasswordEmailTemplate 34 | false 35 | unfiled$public/CommunityHeadlessForgotPasswordTemplate 36 | unfiled$public/CommunityHeadlessRegistrationTemplate 37 | 38 | Admin 39 | 40 | 41 | Standard 42 | Designer 43 | Designer 44 | Designer 45 | Designer 46 | 47 | API1 48 | false 49 | true 50 | API 51 | NotArchived 52 | UnderConstruction 53 | 54 | home 55 | Chatter 56 | 57 | apivforcesite 58 | unfiled$public/CommunityWelcomeEmailTemplate 59 | 60 | -------------------------------------------------------------------------------- /unpackaged/experience/src/sites/API.site-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | false 5 | false 6 | false 7 | false 8 | false 9 | true 10 | false 11 | CommunitiesLogin 12 | BandwidthExceeded 13 | true 14 | true 15 | SameOriginOnly 16 | true 17 | true 18 | FileNotFound 19 | Exception 20 | InMaintenance 21 | CommunitiesLanding 22 | API 23 | false 24 | true 25 | CommunitiesSelfReg 26 | test-jg8f7vyl22ir@example.com 27 | test-jg8f7vyl22ir@example.com 28 | ChatterNetwork 29 | apivforcesite 30 | 31 | -------------------------------------------------------------------------------- /unpackaged/experience/users/guest.json: -------------------------------------------------------------------------------- 1 | { 2 | "allOrNone": false, 3 | "compositeRequest": [ 4 | { 5 | "method": "GET", 6 | "url": "/services/data/v60.0/query/?q=SELECT+Id+FROM+PermissionSet+WHERE+Name+IN+('OpenAPI_Guest_API')+ORDER+BY+Name", 7 | "referenceId": "permissionSets" 8 | }, 9 | { 10 | "method": "GET", 11 | "url": "/services/data/v60.0/query/?q=SELECT+Id+FROM+User+WHERE+Profile.Name+IN+('api Profile')+LIMIT+1", 12 | "referenceId": "guest_user" 13 | }, 14 | { 15 | "method": "POST", 16 | "url": "/services/data/v60.0/sobjects/PermissionSetAssignment", 17 | "referenceId": "guest_user_permission_set_assignment", 18 | "body": { 19 | "PermissionSetId": "@{permissionSets.records[0].Id}", 20 | "AssigneeId": "@{guest_user.records[0].Id}" 21 | } 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /unpackaged/experience/users/owner.json: -------------------------------------------------------------------------------- 1 | { 2 | "compositeRequest": [ 3 | { 4 | "method": "POST", 5 | "url": "/services/data/v63.0/sobjects/User", 6 | "referenceId": "owner", 7 | "body": { 8 | "Alias": "apiowner", 9 | "Email": "api.owner@example.com", 10 | "FirstName": "API", 11 | "LastName": "Owner", 12 | "Username": "api.owner@example.com", 13 | "ProfileId": "@{adminProfile.Id}", 14 | "TimeZoneSidKey": "America/Los_Angeles", 15 | "LocaleSidKey": "en_US", 16 | "EmailEncodingKey": "UTF-8", 17 | "LanguageLocaleKey": "en_US" 18 | } 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /unpackaged/post/classes/AccountAPI.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * @title Account Management API 4 | * @description API for managing Account records 5 | * @version 1.0.0 6 | */ 7 | @RestResource(urlMapping='/account/*') 8 | global with sharing class AccountAPI { 9 | 10 | /** 11 | * @openapi 12 | * @operation getAccount 13 | * @summary Get an account by ID 14 | * @description Retrieves an account record by its ID 15 | * @tag Account 16 | * @param id path string Account ID 17 | * @response 200 {description: "Account retrieved successfully", type: "Account"} 18 | * @response 404 {description: "Account not found"} 19 | */ 20 | @HttpGet 21 | global static Account getAccount() { 22 | RestRequest req = RestContext.request; 23 | String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1); 24 | 25 | List accounts = [SELECT Id, Name, Industry, Phone FROM Account WHERE Id = :accountId]; 26 | if (accounts.isEmpty()) { 27 | RestContext.response.statusCode = 404; 28 | return null; 29 | } 30 | 31 | return accounts[0]; 32 | } 33 | 34 | /** 35 | * @openapi 36 | * @operation createAccount 37 | * @summary Create a new account 38 | * @description Creates a new account record 39 | * @tag Account 40 | * @requestBody {description: "Account details", required: true, content: {application/json: {schema: {type: "object", properties: {Name: {type: "string"}, Industry: {type: "string"}, Phone: {type: "string"}}}}}} 41 | * @response 201 {description: "Account created successfully", type: "Account"} 42 | * @response 400 {description: "Invalid input"} 43 | */ 44 | @HttpPost 45 | global static Account createAccount() { 46 | RestRequest req = RestContext.request; 47 | Account account = (Account)JSON.deserialize(req.requestBody.toString(), Account.class); 48 | 49 | try { 50 | insert account; 51 | RestContext.response.statusCode = 201; 52 | return account; 53 | } catch (Exception e) { 54 | RestContext.response.statusCode = 400; 55 | return null; 56 | } 57 | } 58 | 59 | /** 60 | * @openapi 61 | * @operation updateAccount 62 | * @summary Update an account 63 | * @description Updates an existing account record 64 | * @tag Account 65 | * @param id path string Account ID 66 | * @requestBody {description: "Updated account details", required: true, content: {application/json: {schema: {type: "object", properties: {Name: {type: "string"}, Industry: {type: "string"}, Phone: {type: "string"}}}}}} 67 | * @response 200 {description: "Account updated successfully", type: "Account"} 68 | * @response 404 {description: "Account not found"} 69 | */ 70 | @HttpPut 71 | global static Account updateAccount() { 72 | RestRequest req = RestContext.request; 73 | String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1); 74 | Account account = (Account)JSON.deserialize(req.requestBody.toString(), Account.class); 75 | account.Id = accountId; 76 | 77 | try { 78 | update account; 79 | return account; 80 | } catch (Exception e) { 81 | RestContext.response.statusCode = 404; 82 | return null; 83 | } 84 | } 85 | 86 | /** 87 | * @openapi 88 | * @operation deleteAccount 89 | * @summary Delete an account 90 | * @description Deletes an account record 91 | * @tag Account 92 | * @param id path string Account ID 93 | * @response 204 {description: "Account deleted successfully"} 94 | * @response 404 {description: "Account not found"} 95 | */ 96 | @HttpDelete 97 | global static void deleteAccount() { 98 | RestRequest req = RestContext.request; 99 | String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1); 100 | 101 | try { 102 | delete [SELECT Id FROM Account WHERE Id = :accountId]; 103 | RestContext.response.statusCode = 204; 104 | } catch (Exception e) { 105 | RestContext.response.statusCode = 404; 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /unpackaged/post/classes/AccountAPI.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /unpackaged/post/classes/ContactAPI.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * @title Contact Management API 4 | * @description API for managing Contact records 5 | * @version 1.0.0 6 | */ 7 | @RestResource(urlMapping='/contact/*') 8 | global with sharing class ContactAPI { 9 | 10 | /** 11 | * @openapi 12 | * @operation getContact 13 | * @summary Get a contact by ID 14 | * @description Retrieves a contact record by its ID 15 | * @tag Contact 16 | * @param id path string Contact ID 17 | * @response 200 {description: "Contact retrieved successfully", type: "Contact"} 18 | * @response 404 {description: "Contact not found"} 19 | */ 20 | @HttpGet 21 | global static Contact getContact() { 22 | RestRequest req = RestContext.request; 23 | String contactId = req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1); 24 | 25 | List contacts = [SELECT Id, FirstName, LastName, Email, Phone FROM Contact WHERE Id = :contactId]; 26 | if (contacts.isEmpty()) { 27 | RestContext.response.statusCode = 404; 28 | return null; 29 | } 30 | 31 | return contacts[0]; 32 | } 33 | 34 | /** 35 | * @openapi 36 | * @operation createContact 37 | * @summary Create a new contact 38 | * @description Creates a new contact record 39 | * @tag Contact 40 | * @requestBody {description: "Contact details", required: true, content: {application/json: {schema: {type: "object", properties: {FirstName: {type: "string"}, LastName: {type: "string"}, Email: {type: "string"}, Phone: {type: "string"}}}}}} 41 | * @response 201 {description: "Contact created successfully", type: "Contact"} 42 | * @response 400 {description: "Invalid input"} 43 | */ 44 | @HttpPost 45 | global static Contact createContact() { 46 | RestRequest req = RestContext.request; 47 | Contact contact = (Contact)JSON.deserialize(req.requestBody.toString(), Contact.class); 48 | 49 | try { 50 | insert contact; 51 | RestContext.response.statusCode = 201; 52 | return contact; 53 | } catch (Exception e) { 54 | RestContext.response.statusCode = 400; 55 | return null; 56 | } 57 | } 58 | 59 | /** 60 | * @openapi 61 | * @operation updateContact 62 | * @summary Update a contact 63 | * @description Updates an existing contact record 64 | * @tag Contact 65 | * @param id path string Contact ID 66 | * @requestBody {description: "Updated contact details", required: true, content: {application/json: {schema: {type: "object", properties: {FirstName: {type: "string"}, LastName: {type: "string"}, Email: {type: "string"}, Phone: {type: "string"}}}}}} 67 | * @response 200 {description: "Contact updated successfully", type: "Contact"} 68 | * @response 404 {description: "Contact not found"} 69 | */ 70 | @HttpPut 71 | global static Contact updateContact() { 72 | RestRequest req = RestContext.request; 73 | String contactId = req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1); 74 | Contact contact = (Contact)JSON.deserialize(req.requestBody.toString(), Contact.class); 75 | contact.Id = contactId; 76 | 77 | try { 78 | update contact; 79 | return contact; 80 | } catch (Exception e) { 81 | RestContext.response.statusCode = 404; 82 | return null; 83 | } 84 | } 85 | 86 | /** 87 | * @openapi 88 | * @operation deleteContact 89 | * @summary Delete a contact 90 | * @description Deletes a contact record 91 | * @tag Contact 92 | * @param id path string Contact ID 93 | * @response 204 {description: "Contact deleted successfully"} 94 | * @response 404 {description: "Contact not found"} 95 | */ 96 | @HttpDelete 97 | global static void deleteContact() { 98 | RestRequest req = RestContext.request; 99 | String contactId = req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1); 100 | 101 | try { 102 | delete [SELECT Id FROM Contact WHERE Id = :contactId]; 103 | RestContext.response.statusCode = 204; 104 | } catch (Exception e) { 105 | RestContext.response.statusCode = 404; 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /unpackaged/post/classes/ContactAPI.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /unpackaged/post/classes/TestAPI.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @openapi 3 | * @title Test API 4 | * @description API for testing various property patterns 5 | * @version 1.0.0 6 | */ 7 | @RestResource(urlMapping='/test/*') 8 | global with sharing class TestAPI { 9 | 10 | /** 11 | * @openapi 12 | * @operation getTest 13 | * @summary Get test data 14 | * @description Retrieves test data with various property patterns 15 | * @tag Test 16 | * @security oauth2 read:test 17 | * @response 200 {description: "Test data retrieved successfully", type: "TestAPI.TestResponse"} 18 | */ 19 | @HttpGet 20 | global static TestResponse getTest() { 21 | TestResponse response = new TestResponse(); 22 | response.name = 'Test'; 23 | response.count = 42; 24 | response.isActive = true; 25 | response.nested = new NestedClass(); 26 | response.nested.value = 'Nested'; 27 | return response; 28 | } 29 | 30 | /** 31 | * @openapi 32 | * @operation createTest 33 | * @summary Create test data 34 | * @description Creates test data with various property patterns 35 | * @tag Test 36 | * @security oauth2 write:test 37 | * @requestBody {description: "Test data to create", required: true, type: "TestAPI.TestRequest"} 38 | * @response 201 {description: "Test data created successfully", type: "TestAPI.TestResponse"} 39 | */ 40 | @HttpPost 41 | global static TestResponse createTest() { 42 | RestRequest req = RestContext.request; 43 | TestRequest request = (TestRequest)JSON.deserialize(req.requestBody.toString(), TestRequest.class); 44 | 45 | TestResponse response = new TestResponse(); 46 | response.name = request.name; 47 | response.count = request.count; 48 | response.isActive = request.isActive; 49 | response.nested = request.nested; 50 | 51 | return response; 52 | } 53 | 54 | // Test response class with various property patterns 55 | global class TestResponse { 56 | @AuraEnabled public String name; 57 | public Integer count; 58 | global Boolean isActive; 59 | public NestedClass nested; 60 | } 61 | 62 | // Test request class with various property patterns 63 | global class TestRequest { 64 | @AuraEnabled public String name; 65 | public Integer count; 66 | global Boolean isActive; 67 | public NestedClass nested; 68 | } 69 | 70 | // Nested class to test inner class handling 71 | global class NestedClass { 72 | @AuraEnabled public String value; 73 | public Integer num; 74 | } 75 | } -------------------------------------------------------------------------------- /unpackaged/post/classes/TestAPI.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 63.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /unpackaged/pre/connectedApps/OpenAPIUnlocked.connectedApp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | hello@nimbasolutions.com 4 | 5 | 6 | ORG_DOMAIN/services/oauth2/success 7 | ORG_DOMAIN/lightning/n/SwaggerUI 8 | false 9 | false 10 | true 11 | true 12 | false 13 | false 14 | false 15 | false 16 | false 17 | true 18 | false 19 | false 20 | Api 21 | Full 22 | RefreshToken 23 | 24 | 25 | BYPASS 26 | false 27 | infinite 28 | 29 | 30 | -------------------------------------------------------------------------------- /unpackaged/pre/staticresources/OPENAPI_SPEC.json: -------------------------------------------------------------------------------- 1 | { 2 | "security": [ 3 | { 4 | "oauth2": [ 5 | "api" 6 | ] 7 | } 8 | ], 9 | "components": { 10 | "schemas": { 11 | "TestAPI.TestRequest": { 12 | "properties": { 13 | "nested": { 14 | "type": "string" 15 | }, 16 | "isActive": { 17 | "type": "boolean" 18 | }, 19 | "count": { 20 | "type": "number" 21 | }, 22 | "name": { 23 | "type": "string" 24 | } 25 | }, 26 | "type": "object" 27 | }, 28 | "TestAPI.TestResponse": { 29 | "properties": { 30 | "nested": { 31 | "type": "string" 32 | }, 33 | "isActive": { 34 | "type": "boolean" 35 | }, 36 | "count": { 37 | "type": "number" 38 | }, 39 | "name": { 40 | "type": "string" 41 | } 42 | }, 43 | "type": "object" 44 | }, 45 | "Contact": { 46 | "properties": { 47 | "BuyerAttributes": { 48 | "description": "Buyer Attributes", 49 | "type": "string" 50 | }, 51 | "ContactSource": { 52 | "description": "Creation Source", 53 | "type": "string" 54 | }, 55 | "GenderIdentity": { 56 | "description": "Gender Identity", 57 | "type": "string" 58 | }, 59 | "Pronouns": { 60 | "description": "Pronouns", 61 | "type": "string" 62 | }, 63 | "IndividualId": { 64 | "description": "Individual ID", 65 | "type": "string" 66 | }, 67 | "CleanStatus": { 68 | "description": "Clean Status", 69 | "type": "string" 70 | }, 71 | "JigsawContactId": { 72 | "description": "Jigsaw Contact ID", 73 | "type": "string" 74 | }, 75 | "Jigsaw": { 76 | "description": "Data.com Key", 77 | "type": "string" 78 | }, 79 | "PhotoUrl": { 80 | "description": "Photo URL", 81 | "type": "string" 82 | }, 83 | "IsEmailBounced": { 84 | "description": "Is Email Bounced", 85 | "type": "boolean" 86 | }, 87 | "EmailBouncedDate": { 88 | "description": "Email Bounced Date", 89 | "type": "string" 90 | }, 91 | "EmailBouncedReason": { 92 | "description": "Email Bounced Reason", 93 | "type": "string" 94 | }, 95 | "LastReferencedDate": { 96 | "description": "Last Referenced Date", 97 | "type": "string" 98 | }, 99 | "LastViewedDate": { 100 | "description": "Last Viewed Date", 101 | "type": "string" 102 | }, 103 | "LastCUUpdateDate": { 104 | "description": "Last Stay-in-Touch Save Date", 105 | "type": "string" 106 | }, 107 | "LastCURequestDate": { 108 | "description": "Last Stay-in-Touch Request Date", 109 | "type": "string" 110 | }, 111 | "LastActivityDate": { 112 | "description": "Last Activity", 113 | "type": "string" 114 | }, 115 | "SystemModstamp": { 116 | "description": "System Modstamp", 117 | "type": "string" 118 | }, 119 | "LastModifiedById": { 120 | "description": "Last Modified By ID", 121 | "type": "string" 122 | }, 123 | "LastModifiedDate": { 124 | "description": "Last Modified Date", 125 | "type": "string" 126 | }, 127 | "CreatedById": { 128 | "description": "Created By ID", 129 | "type": "string" 130 | }, 131 | "CreatedDate": { 132 | "description": "Created Date", 133 | "type": "string" 134 | }, 135 | "DoNotCall": { 136 | "description": "Do Not Call", 137 | "type": "boolean" 138 | }, 139 | "HasOptedOutOfFax": { 140 | "description": "Fax Opt Out", 141 | "type": "boolean" 142 | }, 143 | "HasOptedOutOfEmail": { 144 | "description": "Email Opt Out", 145 | "type": "boolean" 146 | }, 147 | "OwnerId": { 148 | "description": "Owner ID", 149 | "type": "string" 150 | }, 151 | "Description": { 152 | "description": "Contact Description", 153 | "type": "string" 154 | }, 155 | "Birthdate": { 156 | "description": "Birthdate", 157 | "type": "string" 158 | }, 159 | "LeadSource": { 160 | "description": "Lead Source", 161 | "type": "string" 162 | }, 163 | "AssistantName": { 164 | "description": "Assistant's Name", 165 | "type": "string" 166 | }, 167 | "Department": { 168 | "description": "Department", 169 | "type": "string" 170 | }, 171 | "Title": { 172 | "description": "Title", 173 | "type": "string" 174 | }, 175 | "Email": { 176 | "description": "Email", 177 | "type": "string" 178 | }, 179 | "ReportsToId": { 180 | "description": "Reports To ID", 181 | "type": "string" 182 | }, 183 | "AssistantPhone": { 184 | "description": "Asst. Phone", 185 | "type": "string" 186 | }, 187 | "OtherPhone": { 188 | "description": "Other Phone", 189 | "type": "string" 190 | }, 191 | "HomePhone": { 192 | "description": "Home Phone", 193 | "type": "string" 194 | }, 195 | "MobilePhone": { 196 | "description": "Mobile Phone", 197 | "type": "string" 198 | }, 199 | "Fax": { 200 | "description": "Business Fax", 201 | "type": "string" 202 | }, 203 | "Phone": { 204 | "description": "Business Phone", 205 | "type": "string" 206 | }, 207 | "MailingAddress": { 208 | "description": "Mailing Address", 209 | "type": "string" 210 | }, 211 | "MailingGeocodeAccuracy": { 212 | "description": "Mailing Geocode Accuracy", 213 | "type": "string" 214 | }, 215 | "MailingLongitude": { 216 | "description": "Mailing Longitude", 217 | "type": "number" 218 | }, 219 | "MailingLatitude": { 220 | "description": "Mailing Latitude", 221 | "type": "number" 222 | }, 223 | "MailingCountry": { 224 | "description": "Mailing Country", 225 | "type": "string" 226 | }, 227 | "MailingPostalCode": { 228 | "description": "Mailing Zip/Postal Code", 229 | "type": "string" 230 | }, 231 | "MailingState": { 232 | "description": "Mailing State/Province", 233 | "type": "string" 234 | }, 235 | "MailingCity": { 236 | "description": "Mailing City", 237 | "type": "string" 238 | }, 239 | "MailingStreet": { 240 | "description": "Mailing Street", 241 | "type": "string" 242 | }, 243 | "OtherAddress": { 244 | "description": "Other Address", 245 | "type": "string" 246 | }, 247 | "OtherGeocodeAccuracy": { 248 | "description": "Other Geocode Accuracy", 249 | "type": "string" 250 | }, 251 | "OtherLongitude": { 252 | "description": "Other Longitude", 253 | "type": "number" 254 | }, 255 | "OtherLatitude": { 256 | "description": "Other Latitude", 257 | "type": "number" 258 | }, 259 | "OtherCountry": { 260 | "description": "Other Country", 261 | "type": "string" 262 | }, 263 | "OtherPostalCode": { 264 | "description": "Other Zip/Postal Code", 265 | "type": "string" 266 | }, 267 | "OtherState": { 268 | "description": "Other State/Province", 269 | "type": "string" 270 | }, 271 | "OtherCity": { 272 | "description": "Other City", 273 | "type": "string" 274 | }, 275 | "OtherStreet": { 276 | "description": "Other Street", 277 | "type": "string" 278 | }, 279 | "Name": { 280 | "description": "Full Name", 281 | "type": "string" 282 | }, 283 | "Salutation": { 284 | "description": "Salutation", 285 | "type": "string" 286 | }, 287 | "FirstName": { 288 | "description": "First Name", 289 | "type": "string" 290 | }, 291 | "LastName": { 292 | "description": "Last Name", 293 | "type": "string" 294 | }, 295 | "AccountId": { 296 | "description": "Account ID", 297 | "type": "string" 298 | }, 299 | "MasterRecordId": { 300 | "description": "Master Record ID", 301 | "type": "string" 302 | }, 303 | "IsDeleted": { 304 | "description": "Deleted", 305 | "type": "boolean" 306 | }, 307 | "Id": { 308 | "description": "Contact ID", 309 | "type": "string" 310 | } 311 | }, 312 | "type": "object" 313 | }, 314 | "Account": { 315 | "properties": { 316 | "Tier": { 317 | "description": "Einstein Account Tier", 318 | "type": "string" 319 | }, 320 | "OperatingHoursId": { 321 | "description": "Operating Hour ID", 322 | "type": "string" 323 | }, 324 | "DandbCompanyId": { 325 | "description": "D&B Company ID", 326 | "type": "string" 327 | }, 328 | "SicDesc": { 329 | "description": "SIC Description", 330 | "type": "string" 331 | }, 332 | "YearStarted": { 333 | "description": "Year Started", 334 | "type": "string" 335 | }, 336 | "NaicsDesc": { 337 | "description": "NAICS Description", 338 | "type": "string" 339 | }, 340 | "NaicsCode": { 341 | "description": "NAICS Code", 342 | "type": "string" 343 | }, 344 | "Tradestyle": { 345 | "description": "Tradestyle", 346 | "type": "string" 347 | }, 348 | "DunsNumber": { 349 | "description": "D-U-N-S Number", 350 | "type": "string" 351 | }, 352 | "AccountSource": { 353 | "description": "Account Source", 354 | "type": "string" 355 | }, 356 | "CleanStatus": { 357 | "description": "Clean Status", 358 | "type": "string" 359 | }, 360 | "JigsawCompanyId": { 361 | "description": "Jigsaw Company ID", 362 | "type": "string" 363 | }, 364 | "Jigsaw": { 365 | "description": "Data.com Key", 366 | "type": "string" 367 | }, 368 | "LastReferencedDate": { 369 | "description": "Last Referenced Date", 370 | "type": "string" 371 | }, 372 | "LastViewedDate": { 373 | "description": "Last Viewed Date", 374 | "type": "string" 375 | }, 376 | "LastActivityDate": { 377 | "description": "Last Activity", 378 | "type": "string" 379 | }, 380 | "SystemModstamp": { 381 | "description": "System Modstamp", 382 | "type": "string" 383 | }, 384 | "LastModifiedById": { 385 | "description": "Last Modified By ID", 386 | "type": "string" 387 | }, 388 | "LastModifiedDate": { 389 | "description": "Last Modified Date", 390 | "type": "string" 391 | }, 392 | "CreatedById": { 393 | "description": "Created By ID", 394 | "type": "string" 395 | }, 396 | "CreatedDate": { 397 | "description": "Created Date", 398 | "type": "string" 399 | }, 400 | "OwnerId": { 401 | "description": "Owner ID", 402 | "type": "string" 403 | }, 404 | "Site": { 405 | "description": "Account Site", 406 | "type": "string" 407 | }, 408 | "Rating": { 409 | "description": "Account Rating", 410 | "type": "string" 411 | }, 412 | "Description": { 413 | "description": "Account Description", 414 | "type": "string" 415 | }, 416 | "TickerSymbol": { 417 | "description": "Ticker Symbol", 418 | "type": "string" 419 | }, 420 | "Ownership": { 421 | "description": "Ownership", 422 | "type": "string" 423 | }, 424 | "NumberOfEmployees": { 425 | "description": "Employees", 426 | "type": "number" 427 | }, 428 | "AnnualRevenue": { 429 | "description": "Annual Revenue", 430 | "type": "number" 431 | }, 432 | "Industry": { 433 | "description": "Industry", 434 | "type": "string" 435 | }, 436 | "Sic": { 437 | "description": "SIC Code", 438 | "type": "string" 439 | }, 440 | "PhotoUrl": { 441 | "description": "Photo URL", 442 | "type": "string" 443 | }, 444 | "Website": { 445 | "description": "Website", 446 | "type": "string" 447 | }, 448 | "AccountNumber": { 449 | "description": "Account Number", 450 | "type": "string" 451 | }, 452 | "Fax": { 453 | "description": "Account Fax", 454 | "type": "string" 455 | }, 456 | "Phone": { 457 | "description": "Account Phone", 458 | "type": "string" 459 | }, 460 | "ShippingAddress": { 461 | "description": "Shipping Address", 462 | "type": "string" 463 | }, 464 | "ShippingGeocodeAccuracy": { 465 | "description": "Shipping Geocode Accuracy", 466 | "type": "string" 467 | }, 468 | "ShippingLongitude": { 469 | "description": "Shipping Longitude", 470 | "type": "number" 471 | }, 472 | "ShippingLatitude": { 473 | "description": "Shipping Latitude", 474 | "type": "number" 475 | }, 476 | "ShippingCountry": { 477 | "description": "Shipping Country", 478 | "type": "string" 479 | }, 480 | "ShippingPostalCode": { 481 | "description": "Shipping Zip/Postal Code", 482 | "type": "string" 483 | }, 484 | "ShippingState": { 485 | "description": "Shipping State/Province", 486 | "type": "string" 487 | }, 488 | "ShippingCity": { 489 | "description": "Shipping City", 490 | "type": "string" 491 | }, 492 | "ShippingStreet": { 493 | "description": "Shipping Street", 494 | "type": "string" 495 | }, 496 | "BillingAddress": { 497 | "description": "Billing Address", 498 | "type": "string" 499 | }, 500 | "BillingGeocodeAccuracy": { 501 | "description": "Billing Geocode Accuracy", 502 | "type": "string" 503 | }, 504 | "BillingLongitude": { 505 | "description": "Billing Longitude", 506 | "type": "number" 507 | }, 508 | "BillingLatitude": { 509 | "description": "Billing Latitude", 510 | "type": "number" 511 | }, 512 | "BillingCountry": { 513 | "description": "Billing Country", 514 | "type": "string" 515 | }, 516 | "BillingPostalCode": { 517 | "description": "Billing Zip/Postal Code", 518 | "type": "string" 519 | }, 520 | "BillingState": { 521 | "description": "Billing State/Province", 522 | "type": "string" 523 | }, 524 | "BillingCity": { 525 | "description": "Billing City", 526 | "type": "string" 527 | }, 528 | "BillingStreet": { 529 | "description": "Billing Street", 530 | "type": "string" 531 | }, 532 | "ParentId": { 533 | "description": "Parent Account ID", 534 | "type": "string" 535 | }, 536 | "Type": { 537 | "description": "Account Type", 538 | "type": "string" 539 | }, 540 | "Name": { 541 | "description": "Account Name", 542 | "type": "string" 543 | }, 544 | "MasterRecordId": { 545 | "description": "Master Record ID", 546 | "type": "string" 547 | }, 548 | "IsDeleted": { 549 | "description": "Deleted", 550 | "type": "boolean" 551 | }, 552 | "Id": { 553 | "description": "Account ID", 554 | "type": "string" 555 | } 556 | }, 557 | "type": "object" 558 | } 559 | }, 560 | "securitySchemes": { 561 | "oauth2": { 562 | "flows": { 563 | "authorizationCode": { 564 | "scopes": { 565 | "offline_access": "Access your data anytime (offline_access)", 566 | "refresh_token": "Allow access to your data via the Web (refresh_token)", 567 | "api": "Access and manage your data (api)" 568 | }, 569 | "tokenUrl": "ORG_DOMAIN/services/oauth2/token", 570 | "authorizationUrl": "ORG_DOMAIN/services/oauth2/authorize" 571 | } 572 | }, 573 | "type": "oauth2" 574 | } 575 | } 576 | }, 577 | "paths": { 578 | "/test": { 579 | "post": { 580 | "responses": { 581 | "201": { 582 | "content": { 583 | "application/json": { 584 | "schema": { 585 | "$ref": "#/components/schemas/TestAPI.TestResponse" 586 | } 587 | } 588 | }, 589 | "description": "Test data created successfully" 590 | } 591 | }, 592 | "requestBody": { 593 | "content": { 594 | "application/json": { 595 | "schema": { 596 | "$ref": "#/components/schemas/TestAPI.TestRequest" 597 | } 598 | } 599 | }, 600 | "description": "Test data to create", 601 | "required": true 602 | }, 603 | "security": [ 604 | { 605 | "oauth2": [ 606 | "write:test" 607 | ] 608 | } 609 | ], 610 | "tags": [ 611 | "Test" 612 | ], 613 | "description": "Creates test data with various property patterns", 614 | "summary": "Create test data", 615 | "operationId": "createTest" 616 | } 617 | }, 618 | "/test/getTest": { 619 | "get": { 620 | "responses": { 621 | "200": { 622 | "content": { 623 | "application/json": { 624 | "schema": { 625 | "$ref": "#/components/schemas/TestAPI.TestResponse" 626 | } 627 | } 628 | }, 629 | "description": "Test data retrieved successfully" 630 | } 631 | }, 632 | "security": [ 633 | { 634 | "oauth2": [ 635 | "read:test" 636 | ] 637 | } 638 | ], 639 | "tags": [ 640 | "Test" 641 | ], 642 | "description": "Retrieves test data with various property patterns", 643 | "summary": "Get test data", 644 | "operationId": "getTest" 645 | } 646 | }, 647 | "/contact": { 648 | "post": { 649 | "responses": { 650 | "400": { 651 | "description": "Invalid input" 652 | }, 653 | "201": { 654 | "content": { 655 | "application/json": { 656 | "schema": { 657 | "$ref": "#/components/schemas/Contact" 658 | } 659 | } 660 | }, 661 | "description": "Contact created successfully" 662 | } 663 | }, 664 | "tags": [ 665 | "Contact" 666 | ], 667 | "description": "Creates a new contact record", 668 | "summary": "Create a new contact", 669 | "operationId": "createContact" 670 | } 671 | }, 672 | "/contact/{id}": { 673 | "delete": { 674 | "responses": { 675 | "404": { 676 | "description": "Contact not found" 677 | }, 678 | "204": { 679 | "description": "Contact deleted successfully" 680 | } 681 | }, 682 | "parameters": [ 683 | { 684 | "schema": { 685 | "type": "string" 686 | }, 687 | "description": "Contact ID", 688 | "required": true, 689 | "in": "path", 690 | "name": "id" 691 | } 692 | ], 693 | "tags": [ 694 | "Contact" 695 | ], 696 | "description": "Deletes a contact record", 697 | "summary": "Delete a contact", 698 | "operationId": "deleteContact" 699 | }, 700 | "put": { 701 | "responses": { 702 | "404": { 703 | "description": "Contact not found" 704 | }, 705 | "200": { 706 | "content": { 707 | "application/json": { 708 | "schema": { 709 | "$ref": "#/components/schemas/Contact" 710 | } 711 | } 712 | }, 713 | "description": "Contact updated successfully" 714 | } 715 | }, 716 | "parameters": [ 717 | { 718 | "schema": { 719 | "type": "string" 720 | }, 721 | "description": "Contact ID", 722 | "required": true, 723 | "in": "path", 724 | "name": "id" 725 | } 726 | ], 727 | "tags": [ 728 | "Contact" 729 | ], 730 | "description": "Updates an existing contact record", 731 | "summary": "Update a contact", 732 | "operationId": "updateContact" 733 | }, 734 | "get": { 735 | "responses": { 736 | "404": { 737 | "description": "Contact not found" 738 | }, 739 | "200": { 740 | "content": { 741 | "application/json": { 742 | "schema": { 743 | "$ref": "#/components/schemas/Contact" 744 | } 745 | } 746 | }, 747 | "description": "Contact retrieved successfully" 748 | } 749 | }, 750 | "parameters": [ 751 | { 752 | "schema": { 753 | "type": "string" 754 | }, 755 | "description": "Contact ID", 756 | "required": true, 757 | "in": "path", 758 | "name": "id" 759 | } 760 | ], 761 | "tags": [ 762 | "Contact" 763 | ], 764 | "description": "Retrieves a contact record by its ID", 765 | "summary": "Get a contact by ID", 766 | "operationId": "getContact" 767 | } 768 | }, 769 | "/account": { 770 | "post": { 771 | "responses": { 772 | "400": { 773 | "description": "Invalid input" 774 | }, 775 | "201": { 776 | "content": { 777 | "application/json": { 778 | "schema": { 779 | "$ref": "#/components/schemas/Account" 780 | } 781 | } 782 | }, 783 | "description": "Account created successfully" 784 | } 785 | }, 786 | "tags": [ 787 | "Account" 788 | ], 789 | "description": "Creates a new account record", 790 | "summary": "Create a new account", 791 | "operationId": "createAccount" 792 | } 793 | }, 794 | "/account/{id}": { 795 | "delete": { 796 | "responses": { 797 | "404": { 798 | "description": "Account not found" 799 | }, 800 | "204": { 801 | "description": "Account deleted successfully" 802 | } 803 | }, 804 | "parameters": [ 805 | { 806 | "schema": { 807 | "type": "string" 808 | }, 809 | "description": "Account ID", 810 | "required": true, 811 | "in": "path", 812 | "name": "id" 813 | } 814 | ], 815 | "tags": [ 816 | "Account" 817 | ], 818 | "description": "Deletes an account record", 819 | "summary": "Delete an account", 820 | "operationId": "deleteAccount" 821 | }, 822 | "put": { 823 | "responses": { 824 | "404": { 825 | "description": "Account not found" 826 | }, 827 | "200": { 828 | "content": { 829 | "application/json": { 830 | "schema": { 831 | "$ref": "#/components/schemas/Account" 832 | } 833 | } 834 | }, 835 | "description": "Account updated successfully" 836 | } 837 | }, 838 | "parameters": [ 839 | { 840 | "schema": { 841 | "type": "string" 842 | }, 843 | "description": "Account ID", 844 | "required": true, 845 | "in": "path", 846 | "name": "id" 847 | } 848 | ], 849 | "tags": [ 850 | "Account" 851 | ], 852 | "description": "Updates an existing account record", 853 | "summary": "Update an account", 854 | "operationId": "updateAccount" 855 | }, 856 | "get": { 857 | "responses": { 858 | "404": { 859 | "description": "Account not found" 860 | }, 861 | "200": { 862 | "content": { 863 | "application/json": { 864 | "schema": { 865 | "$ref": "#/components/schemas/Account" 866 | } 867 | } 868 | }, 869 | "description": "Account retrieved successfully" 870 | } 871 | }, 872 | "parameters": [ 873 | { 874 | "schema": { 875 | "type": "string" 876 | }, 877 | "description": "Account ID", 878 | "required": true, 879 | "in": "path", 880 | "name": "id" 881 | } 882 | ], 883 | "tags": [ 884 | "Account" 885 | ], 886 | "description": "Retrieves an account record by its ID", 887 | "summary": "Get an account by ID", 888 | "operationId": "getAccount" 889 | } 890 | } 891 | }, 892 | "servers": [ 893 | { 894 | "description": "Salesforce Apex REST API", 895 | "url": "ORG_DOMAIN/services/apexrest" 896 | } 897 | ], 898 | "info": { 899 | "version": "1.0.0", 900 | "description": "Generated by OpenAPI Unlocked", 901 | "title": "Salesforce Apex REST API" 902 | }, 903 | "openapi": "3.0.0" 904 | } -------------------------------------------------------------------------------- /unpackaged/pre/staticresources/OPENAPI_SPEC.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Public 4 | application/json 5 | 6 | --------------------------------------------------------------------------------