diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml new file mode 100644 index 00000000..97a7e455 --- /dev/null +++ b/.github/workflows/bump-version.yml @@ -0,0 +1,23 @@ +name: Bump version +on: + push: + branches: + - develop +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Bump version and push tag + id: tag_version + uses: mathieudutour/github-tag-action@v6.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Create a GitHub release + uses: ncipollo/release-action@v1 + with: + tag: ${{ steps.tag_version.outputs.new_tag }} + name: Release ${{ steps.tag_version.outputs.new_tag }} + body: ${{ steps.tag_version.outputs.changelog }} + +#### https://github.com/marketplace/actions/github-tag \ No newline at end of file diff --git a/.github/workflows/codesee-arch-diagram.yml b/.github/workflows/codesee-arch-diagram.yml deleted file mode 100644 index 78909632..00000000 --- a/.github/workflows/codesee-arch-diagram.yml +++ /dev/null @@ -1,90 +0,0 @@ -# This workflow was added by CodeSee. Learn more at https://codesee.io/ -on: - push: - branches: - - main - pull_request_target: - types: [opened, synchronize, reopened] - -name: CodeSee Map - -permissions: read-all - -jobs: - test_map_action: - runs-on: ubuntu-latest - continue-on-error: true - name: Run CodeSee Map Analysis - steps: - - name: checkout - id: checkout - uses: actions/checkout@v2 - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - - # codesee-detect-languages has an output with id languages. - - name: Detect Languages - id: detect-languages - uses: Codesee-io/codesee-detect-languages-action@latest - - - name: Configure JDK 16 - uses: actions/setup-java@v3 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).java }} - with: - java-version: '16' - distribution: 'zulu' - - # CodeSee Maps Go support uses a static binary so there's no setup step required. - - - name: Configure Node.js 14 - uses: actions/setup-node@v3 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).javascript }} - with: - node-version: '14' - - - name: Configure Python 3.x - uses: actions/setup-python@v2 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).python }} - with: - python-version: '3.10' - architecture: 'x64' - - - name: Configure Ruby '3.x' - uses: ruby/setup-ruby@v1 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).ruby }} - with: - ruby-version: '3.0' - - # We need the rust toolchain because it uses rustc and cargo to inspect the package - - name: Configure Rust 1.x stable - uses: actions-rs/toolchain@v1 - if: ${{ fromJSON(steps.detect-languages.outputs.languages).rust }} - with: - toolchain: stable - - - name: Generate Map - id: generate-map - uses: Codesee-io/codesee-map-action@latest - with: - step: map - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} - languages: ${{ steps.detect-languages.outputs.languages }} - - - name: Upload Map - id: upload-map - uses: Codesee-io/codesee-map-action@latest - with: - step: mapUpload - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} - - - name: Insights - id: insights - uses: Codesee-io/codesee-map-action@latest - with: - step: insights - api_token: ${{ secrets.CODESEE_ARCH_DIAG_API_TOKEN }} - github_ref: ${{ github.ref }} diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml deleted file mode 100644 index 0167ac2e..00000000 --- a/.github/workflows/npm-publish.yml +++ /dev/null @@ -1,51 +0,0 @@ -# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created -# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages - -name: Node.js Package - -on: - release: - types: [created] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16 - - run: yarn - - run: yarn build - - run: yarn test - - publish-npm: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16 - registry-url: https://registry.npmjs.org/ - - run: npm ci - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{secrets.npm_token}} - - publish-gpr: - needs: build - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16 - registry-url: https://npm.pkg.github.com/ - - run: npm ci - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/src/adapters/controllers/post-invoke-port.js b/src/adapters/controllers/post-invoke-port.js index 7897a61b..5d5f2242 100644 --- a/src/adapters/controllers/post-invoke-port.js +++ b/src/adapters/controllers/post-invoke-port.js @@ -13,6 +13,9 @@ export default function anyInvokePortFactory (invokePort) { const result = await invokePort({ port: httpRequest.params.port, args: httpRequest.body, + method: httpRequest.method, + headers: httpRequest.headers, + path: httpRequest.path, id: httpRequest.params.id || null }) diff --git a/src/adapters/datasources/datasource-mongodb.js b/src/adapters/datasources/datasource-mongodb.js index b8c2754a..7b557e59 100644 --- a/src/adapters/datasources/datasource-mongodb.js +++ b/src/adapters/datasources/datasource-mongodb.js @@ -6,12 +6,20 @@ const HIGHWATERMARK = 50 const mongodb = require('mongodb') const { MongoClient } = mongodb -const { Transform, Writable } = require('stream') -const qpm = require('query-params-mongo') -const processQuery = qpm() +const { DataSourceMemory } = require("./datasource-memory") +const { Transform, Writable } = require("stream") +const qpm = require("query-params-mongo") +const processQuery = qpm({ + autoDetect: [ + { valuePattern: /^null$/i, dataType: 'nullstring' } + ], + converters: { + nullstring: val=>{ return { $type: 10 } } // reference BSON datatypes https://www.mongodb.com/docs/manual/reference/bson-types/ + } +}) -const url = process.env.MONGODB_URL || 'mongodb://localhost:27017' -const configRoot = require('../../config').hostConfig +const url = process.env.MONGODB_URL || "mongodb://localhost:27017" +const configRoot = require("../../config").hostConfig const dsOptions = configRoot.adapters.datasources.DataSourceMongoDb.options || { runOffline: true, numConns: 2 @@ -238,7 +246,7 @@ export class DataSourceMongoDb extends DataSource { processOptions (param) { const { options = {}, query = {} } = param - return { ...options, ...processQuery(query) } + return { ...processQuery(query), ...options } // options must overwite the query not otherwise } /** diff --git a/src/aegis.js b/src/aegis.js index 324d56f3..4b882a8b 100644 --- a/src/aegis.js +++ b/src/aegis.js @@ -96,11 +96,13 @@ const router = { if (ctrl.ports) { for (const portName in ctrl.ports) { const port = ctrl.ports[portName] + const specPortMethods = port?.methods?.join('|') || "" + if (port.path) { routeOverrides.set(port.path, portName) } - if (checkAllowedMethods(ctrl, method)) { + if (checkAllowedMethods(ctrl, method) && (!port.methods || (specPortMethods.includes(method.toLowerCase()))) ) { routes.set(port.path || path(ctrl.endpoint), { [method]: adapter(ctrl.fn) }) @@ -131,6 +133,8 @@ const router = { } function makeRoutes () { + router.adminRoute(getConfig, http) + router.userRoutes(getRoutes) router.autoRoutes(endpoint, 'get', liveUpdate, http) router.autoRoutes(endpoint, 'get', getModels, http) router.autoRoutes(endpoint, 'post', postModels, http) @@ -146,8 +150,6 @@ function makeRoutes () { router.autoRoutes(endpointPortId, 'patch', anyInvokePorts, http, true) router.autoRoutes(endpointPortId, 'delete', anyInvokePorts, http, true) router.autoRoutes(endpointPortId, 'get', anyInvokePorts, http, true) - router.adminRoute(getConfig, http) - router.userRoutes(getRoutes) console.log(routes) } diff --git a/src/domain/use-cases/invoke-port.js b/src/domain/use-cases/invoke-port.js index 87a39020..97fdd395 100644 --- a/src/domain/use-cases/invoke-port.js +++ b/src/domain/use-cases/invoke-port.js @@ -33,7 +33,7 @@ export default function makeInvokePort ({ } /** * - * @param {{id:string,model:import('..').Model,args:string[],port:string}} input + * @param {{id:String,model:import('..').Model,args:String[],port:String,method:String,headers:Array}} input * @returns */ return async function invokePort (input) { @@ -41,25 +41,38 @@ export default function makeInvokePort ({ return threadpool.runJob(invokePort.name, input, modelName) } else { try { - const { id = null, port = null} = input; + let { id = null, port = null, method, path } = input; const service = await findModelService(id) - if (!service) { - throw new Error('could not find service') + throw new Error('could not find a service associated with given id') } - + + const specPorts = service.getPorts(); if(!port) { - const specPorts = service.getPorts(); - const path = context['requestContext'].getStore().get('path'); - const [ [ portName ] ] = Object.entries(specPorts).filter((port) => port[1].path === path); - if(!portName) { - throw new Error('no port specified'); - } - if(!service[portName]) { - throw new Error('no port found'); + for(const p of Object.entries(specPorts)) { + if(!p[1].path) { + continue; + } + if (pathsMatch(p[1].path, path)) { + port = p[0]; + break; + } } + } + + if(!port) { + throw new Error('the port is undefined') + } + if(!service[port]) { + throw new Error('the port or record ID is invalid') + } - return await service[portName](input); + const specPortMethods = specPorts[port]?.methods.join('|').toLowerCase(); + if (specPortMethods && !(specPortMethods.includes(method.toLowerCase()))) { + throw new Error('invalid method for given port'); + } + if (specPorts[port]?.path && !pathsMatch(specPorts[port].path, path)) { + throw new Error('invalid path for given port'); } return await service[port](input) @@ -68,4 +81,40 @@ export default function makeInvokePort ({ } } } +} + +// Performant way of checking if paths are the same +// given one path with params and one with the param pattern +// this accounts for route params +// since a route param can be anything, we can just compare +/// each path segment skipping over the param field +function pathsMatch(pathWithParamRegex, pathWithParams) { + const splitPathWithParams = pathWithParams.split('/'); + const splitPathWithParamRegex = pathWithParamRegex.split('/'); + + // We know if the length is different, the paths are different + if(splitPathWithParams.length !== splitPathWithParamRegex.length) { + return false; + } + + // we loop through the path with params and check if the path with param regex and the called path match + // if they do not match, we return false + // if we get to a segment with a route param we continue + // if we get to the end of the loop and all segments match, we return true + for (let index = 0; index < splitPathWithParams.length; index++) { + const param = splitPathWithParams[index]; + const paramRegex = splitPathWithParamRegex[index]; + + // regex path includes colon meaning route param so we continue + if(paramRegex.includes(':')) { + continue; + } + + // if not equal, we return false the paths don't match + if(param !== paramRegex) { + return false; + } + } + + return true; } \ No newline at end of file