diff --git a/.eslintrc b/.eslintrc index 78b57b072d..1a5791e1a2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -44,7 +44,7 @@ "no-underscore-dangle": [ 2, { - "allow": ["_id", "__", "_initialized", "_config"] + "allow": ["_id", "__", "_initialized", "_config", "_debug"] } ], "no-negated-condition": [ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..8fb83c6e90 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v4.8.3 diff --git a/.scripts/docker_build.sh b/.scripts/docker_build.sh index d9a6107e74..a476eacd58 100755 --- a/.scripts/docker_build.sh +++ b/.scripts/docker_build.sh @@ -7,7 +7,7 @@ set -ev -if [ "${TRAVIS_PULL_REQUEST}" = "false" ] +if [ "${TRAVIS_PULL_REQUEST}" = "false" -a "${TRAVIS_REPO_SLUG}" = "apinf/platform" ] then docker build -t apinf/platform:$DOCKER_TAG . docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" diff --git a/.scripts/run-chimp.js b/.scripts/run-chimp.js new file mode 100644 index 0000000000..4b3fb2d991 --- /dev/null +++ b/.scripts/run-chimp.js @@ -0,0 +1,107 @@ +/* Copyright 2017 Apinf Oy +This file is covered by the EUPL license. +You may obtain a copy of the licence at +https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11 */ + +// Inspired by https://goo.gl/Rbd2s2 + +var path = require('path'), + fs = require('fs'), + extend = require('util')._extend, + exec = require('child_process').exec, + processes = []; + +var baseDir = path.resolve(__dirname, '..'), + srcDir = path.resolve(baseDir); + +var appOptions = { + env: { + PORT: 3000, + ROOT_URL: 'http://localhost:3000' + } +}; + +/** + * Start a process on the O.S. + * Optionally start a child process if options.waitForMessage + * is set to an output message emitted by this process. + * + * @param {command, options} opts + * @param {*} callback + */ +function startProcess(opts, callback) { + var proc = exec( + opts.command, + opts.options + ); + + if (opts.waitForMessage) { + proc.stdout.on('data', function waitForMessage(data) { + if (data.toString().match(opts.waitForMessage)) { + if (callback) { + callback(); + } + } + }); + } + + if (!opts.silent) { + proc.stdout.pipe(process.stdout); + proc.stderr.pipe(process.stderr); + } + + if (opts.logFile) { + var logStream = fs.createWriteStream(opts.logFile, {flags: 'a'}); + proc.stdout.pipe(logStream); + proc.stderr.pipe(logStream); + } + + proc.on('close', function(code) { + console.log(opts.name, 'exited with code ' + code); + for (var i = 0; i < processes.length; i += 1) { + processes[i].kill(); + } + process.exit(code); + }); + processes.push(proc); +} + +/** + * Always run meteor in production-like environment + * since we do not want to reload changing files + * + * @param {*} callback + */ +function startApp(callback) { + startProcess({ + name: 'Meteor App', + command: 'meteor --production', + waitForMessage: appOptions.waitForMessage, + options: { + cwd: srcDir, + env: extend(appOptions.env, process.env) + } + }, callback); +} + +/** + * Start chimp + */ +function startChimp() { + startProcess({ + name: 'Chimp', + command: 'yarn run chimp-test' + }); +} + +/** + * Run meteor and then chimp after application has started + */ +function chimpNoMirror() { + appOptions.waitForMessage = 'App running at:'; + startApp(function() { + startChimp(); + }); +} + +chimpNoMirror(); \ No newline at end of file diff --git a/.scripts/start-xvfb.sh b/.scripts/start-xvfb.sh new file mode 100755 index 0000000000..6cfdf1536b --- /dev/null +++ b/.scripts/start-xvfb.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Copyright 2017 Apinf Oy +#This file is covered by the EUPL license. +#You may obtain a copy of the licence at +#https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11 + +set -ev + +if [ "${TRAVIS_OS_NAME}" = "linux" ]; then + sh -e /etc/init.d/xvfb start + sleep 3 +fi diff --git a/.travis.yml b/.travis.yml index 93de32696e..8cd393095d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,55 @@ sudo: false language: node_js node_js: - - "node" + - '4' + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + - sourceline: 'deb https://dl.yarnpkg.com/debian/ stable main' + key_url: 'https://dl.yarnpkg.com/debian/pubkey.gpg' + packages: + - g++-4.8 + - yarn + chrome: stable branches: only: - develop +before_cache: + - rm -f $HOME/.meteor/log/*.log + cache: yarn: true - -before_install: - - curl -o- -L https://yarnpkg.com/install.sh | bash - - export PATH=$HOME/.yarn/bin:$PATH + directories: + - $HOME/.meteor services: - docker +before_install: + # Install meteor locally on CI + - if [ ! -e "$HOME/.meteor/meteor" ]; then curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh; fi + before_script: + - yarn - yarn run lint + # Start X Virtual Frame Buffer for headless testing with real browsers + - ./.scripts/start-xvfb.sh + +install: + - export PATH="$HOME/.meteor:$PATH" script: + # Run meteor and chimp from node.js + - travis_retry yarn test + # Build docker image - ./.scripts/docker_build.sh + +env: + global: + - DISPLAY=:99.0 + - TEST_MODE: "true" + - CXX=g++-4.8 diff --git a/about/client/about.html b/about/client/about.html index a6767906da..6e13f0be0a 100644 --- a/about/client/about.html +++ b/about/client/about.html @@ -33,7 +33,7 @@

Apinf
- 0.46.0 + 0.47.0
API Umbrella diff --git a/apis/server/api.js b/apis/server/api.js index 617854898f..ac1c6fa0a4 100644 --- a/apis/server/api.js +++ b/apis/server/api.js @@ -23,6 +23,7 @@ ApiV1.addCollection(Apis, { tags: [ ApiV1.swagger.tags.api, ], + summary: 'List and search public APIs.', description: 'List and search public APIs.', parameters: [ ApiV1.swagger.params.optionalSearch, @@ -106,6 +107,7 @@ ApiV1.addCollection(Apis, { tags: [ ApiV1.swagger.tags.api, ], + summary: 'Fetch API with specified ID', description: 'Returns one API with specified ID or nothing if there is not match found', parameters: [ ApiV1.swagger.params.apiId, @@ -126,6 +128,7 @@ ApiV1.addCollection(Apis, { tags: [ ApiV1.swagger.tags.api, ], + summary: 'Add new API to catalog.', description: 'Adds an API to catalog. On success, returns newly added API object.', parameters: [ ApiV1.swagger.params.api, @@ -155,6 +158,7 @@ ApiV1.addCollection(Apis, { tags: [ ApiV1.swagger.tags.api, ], + summary: 'Update API', description: 'Update an API', parameters: [ ApiV1.swagger.params.apiId, @@ -232,6 +236,7 @@ ApiV1.addCollection(Apis, { tags: [ ApiV1.swagger.tags.api, ], + summary: 'Delete API.', description: 'Deletes the identified API from the system.', parameters: [ ApiV1.swagger.params.apiId, diff --git a/branding/client/branding.html b/branding/client/branding.html index b104476644..0d7da6ff47 100644 --- a/branding/client/branding.html +++ b/branding/client/branding.html @@ -26,8 +26,8 @@

type="update" collection=brandingCollection doc=branding }} - {{> afQuickField name='siteTitle' id="site-title" }} - {{> afQuickField name='siteSlogan' id="site-slogan" }} + {{> afQuickField name='siteTitle' id="branding-site-title" }} + {{> afQuickField name='siteSlogan' id="branding-site-slogan" }} {{> afQuickField name='colors' }} {{> afQuickField name='siteFooter' id="site-footer" }} {{> afQuickField name='footerCode' id="footer-code" }} @@ -46,7 +46,7 @@

{{> afQuickField name='homeCustomBlock' }}
-
@@ -62,7 +62,7 @@

{{> afQuickField name='footerCode' }} {{> afQuickField name='socialMedia' }}
-
diff --git a/core/capture_exception/client/error_handler.js b/core/capture_exception/client/error_handler.js new file mode 100644 index 0000000000..23cafe3f18 --- /dev/null +++ b/core/capture_exception/client/error_handler.js @@ -0,0 +1,36 @@ +/* Copyright 2017 Apinf Oy + This file is covered by the EUPL license. + You may obtain a copy of the licence at + https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11 */ + +// Meteor packages imports +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; + +// Npm packages imports +import Raven from 'raven-js'; + +// Set Global Error Handler +window.addEventListener('error', (e) => { + // Send error message and stack + Raven.captureException(e.message, { extra: { stack: e.error.stack } }); +}); + +// Set Global Error Handler on Promise rejection +window.addEventListener('unhandledrejection', (e) => { + // Send error message and stack + Raven.captureException(e.reason.message, { extra: { stack: e.reason.stack } }); +}); + +// Set Global Handler on Sign in +Accounts.onLogin(() => { + Raven.setUserContext({ + email: Meteor.user().emails[0].address, + id: Meteor.userId(), + }); +}); + +// Set Global Handler on Sign Out +Accounts.onLogout(() => { + Raven.setUserContext(); +}); diff --git a/core/capture_exception/client/start_up.js b/core/capture_exception/client/start_up.js new file mode 100644 index 0000000000..0c7f11145e --- /dev/null +++ b/core/capture_exception/client/start_up.js @@ -0,0 +1,27 @@ +/* Copyright 2017 Apinf Oy + This file is covered by the EUPL license. + You may obtain a copy of the licence at + https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11 */ + +// Meteor packages imports +import { Meteor } from 'meteor/meteor'; + +// Npm packages imports +import Raven from 'raven-js'; + +// APInf imports +import wrapMeteorDebug from './wrap_meteor_debug'; + +Meteor.startup(() => { + Meteor.call('getClientDsn', (err, result) => { + // The falsy result will turn off the Sentry app + const SENTRY_DSN = result || false; + + // Configure Raven capture tracking for Client side + Raven + .config(SENTRY_DSN) + .install(); + }); + + wrapMeteorDebug(); +}); diff --git a/core/capture_exception/client/wrap_meteor_debug.js b/core/capture_exception/client/wrap_meteor_debug.js new file mode 100644 index 0000000000..cc6e6b0bf8 --- /dev/null +++ b/core/capture_exception/client/wrap_meteor_debug.js @@ -0,0 +1,27 @@ +/* Copyright 2017 Apinf Oy + This file is covered by the EUPL license. + You may obtain a copy of the licence at + https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11 */ + +// Meteor packages imports +import { Meteor } from 'meteor/meteor'; + +// Npm packages imports +import Raven from 'raven-js'; + +export default function wrapMeteorDebug () { + // Expand Meteor._debug function - capture and log exception to Sentry before display error + const originalMeteorDebug = Meteor._debug; + + Meteor._debug = function (m, s) { + // Exception message contains two message. + // One of them is Exception Tracker which is not useful for debugging + // Don't send exception to Sentry which is related to Tracker exception + if (m !== 'Exception from Tracker recompute function:') { + // Provide message of error and stack + Raven.captureException(m, { extra: { stack: s } }); + } + // eslint-disable-next-line + return originalMeteorDebug.apply(this, arguments); + }; +} diff --git a/core/capture_exception/server/methods.js b/core/capture_exception/server/methods.js new file mode 100644 index 0000000000..fdd4decc5e --- /dev/null +++ b/core/capture_exception/server/methods.js @@ -0,0 +1,26 @@ +/* Copyright 2017 Apinf Oy + This file is covered by the EUPL license. + You may obtain a copy of the licence at + https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11 */ + +// Meteor packages imports +import { Meteor } from 'meteor/meteor'; + +Meteor.methods({ + getClientDsn () { + // Client side must have special public DSN which can be calculated from SENTRY_DSN + + // Get SENTRY_DNS from environment variables + const DSN = process.env.SENTRY_DSN || ''; + // Get position ':' + const twoSpot = DSN.lastIndexOf(':'); + // Get position '@' + const sign = DSN.lastIndexOf('@'); + + // Concat part before ':' and part after '@' including the sign + const publicDsn = DSN.slice(0, twoSpot) + DSN.slice(sign, DSN.length); + + // Return public key for DSN or empty line + return publicDsn; + }, +}); diff --git a/core/capture_exception/server/start_up.js b/core/capture_exception/server/start_up.js new file mode 100644 index 0000000000..de83086494 --- /dev/null +++ b/core/capture_exception/server/start_up.js @@ -0,0 +1,28 @@ +/* Copyright 2017 Apinf Oy + This file is covered by the EUPL license. + You may obtain a copy of the licence at + https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11 */ + +// Meteor packages imports +import { Meteor } from 'meteor/meteor'; + +// Npm packages imports +import Raven from 'raven'; + +// APInf imports +import wrapMethods from './wrap_meteor_methods'; + +Meteor.startup(() => { + // Get Sentry DNS from environment variable or set false + // Falsy value turns off the Sentry app + const SENTRY_DSN = process.env.SENTRY_DSN || false; + + // Configure Raven capture tracking for Server side + Raven.config(SENTRY_DSN, + { + // Capture unhandled promise rejections + captureUnhandledRejections: true, + }).install(); + + wrapMethods(); +}); diff --git a/core/capture_exception/server/wrap_meteor_methods.js b/core/capture_exception/server/wrap_meteor_methods.js new file mode 100644 index 0000000000..11117bd62d --- /dev/null +++ b/core/capture_exception/server/wrap_meteor_methods.js @@ -0,0 +1,40 @@ +/* Copyright 2017 Apinf Oy + This file is covered by the EUPL license. + You may obtain a copy of the licence at + https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence-eupl-v11 */ + +// Meteor packages imports +import { Meteor } from 'meteor/meteor'; + +// Npm packages imports +import Raven from 'raven'; +import _ from 'lodash'; + +const wrapMethodHanderForErrors = function (name, originalHandler, methodMap) { + methodMap[name] = function () { + try { + // eslint-disable-next-line + return originalHandler.apply(this, arguments); + } catch (ex) { + Raven.captureException(ex); + throw ex; + } + }; +}; + +// TODO: the same for Publish function +export default function wrapMethods () { + const originalMeteorMethods = Meteor.methods; + // Wrap future method handlers for capturing errors + Meteor.methods = function (methodMap) { + _.forEach(methodMap, (handler, name) => { + wrapMethodHanderForErrors(name, handler, methodMap); + }); + originalMeteorMethods(methodMap); + }; + + // Wrap existing method handlers for capturing errors + _.forEach(Meteor.default_server.method_handlers, (handler, name) => { + wrapMethodHanderForErrors(name, handler, Meteor.default_server.method_handlers); + }); +} diff --git a/core/lib/i18n/en.i18n.json b/core/lib/i18n/en.i18n.json index 2f2d3cdf4f..fc8be7ad24 100644 --- a/core/lib/i18n/en.i18n.json +++ b/core/lib/i18n/en.i18n.json @@ -181,8 +181,7 @@ "backlogItem_deleteButton_text": "Delete", "bookmark_buttonText_bookmark": "Bookmark", "bookmark_buttonText_unbookmark": "Unbookmark", - "branding_homeCustomBlock_helpIcon_text": "This field gives you the ability to add text and HTML/CSS custom code which will appear on the home page. - In addition you may add HTML content like images, social media plugins, or YouTube video. Bootstrap classes are also available, e.g. you may use .container to center your content on a page.", + "branding_homeCustomBlock_helpIcon_text": "This field gives you the ability to add text and HTML/CSS custom code which will appear on the home page. In addition you may add HTML content like images, social media plugins, or YouTube video. Bootstrap classes are also available, e.g. you may use .container to center your content on a page.", "branding_panel_title": "Project Branding", "branding_save": "Save", "branding_successMessage": "Branding saved", @@ -451,7 +450,6 @@ "organizationSettings_uploadLogo_heading": "Logo", "organizationSettings_uploadCover_heading": "Cover photo", "organizationSettings_uploadLogoText": "You can upload a logo for your organization. The logo will appear in the catalogue and on the single organization profile.", - "organizationSettings_uploadCover_heading": "Upload cover image", "organizationSettings_uploadCoverText": "You can upload a cover image for your organization.", "organizationSettingsDelete_buttonText_delete": "Delete", "organizationSettingsDelete_text_information": "The organization information will be removed and all APIs will be disconnected. This action cannot be undone.", @@ -550,10 +548,11 @@ "postItem_buttonText_delete": "Delete", "postItem_buttonText_edit": "Edit", "postItem_text_added": "Added", - "removeProxy_modalLabel": "Are you sure that you want to remove proxy?", - "removeProxy_modalBody": "proxy backends are connected to this proxy. Removing the proxy will mean that API calls made to any of the connected API backends will break.", - "removeProxy_confirmRemoveProxy": "Yes, remove", - "removeProxy_cancelRemove": "Cancel", + "removeProxy_modalLabel_title": "Removing Proxy", + "removeProxy_modalBody_warning": "Warning!", + "removeProxy_modalBody_text": "You are about to remove a proxy. There are currently __count__ APIs connected to this proxy. Removing the proxy would break all API calls made to the connected API backends. Are you sure you want to continue?", + "removeProxy_buttonText_confirmRemoveProxy": "Yes", + "removeProxy_buttonText_cancelRemove": "No", "removeProxy_errorMessage": "Error: Proxy removal failed", "requestsOverTimeChart_showingRecordsCount": "Showing records", "requestsOverTimeChart_title": "API Request Timeline", diff --git a/core/server/api.js b/core/server/api.js index dfd1eb9ed8..c20f2f10fa 100644 --- a/core/server/api.js +++ b/core/server/api.js @@ -26,6 +26,27 @@ ApiV1.swagger = { version: '1.0.0', title: 'Admin API', }, + paths: { + '/users': { + get: {}, + post: {}, + }, + + '/users/{id}': { + get: {}, + delete: {}, + put: {}, + }, + + '/users/updates': { + get: {}, + }, + + '/login': { + post: {}, + }, + + }, securityDefinitions: { userSecurityToken: { in: 'header', @@ -43,6 +64,7 @@ ApiV1.swagger = { api: 'APIs', organization: 'Organizations', users: 'Users', + login: 'Login', }, params: { api: { @@ -67,6 +89,12 @@ ApiV1.swagger = { required: true, type: 'string', }, + createdAt: { + name: 'createdAt', + in: 'body', + description: 'Date and time in ISODate format, e.g. "2012-07-14T01:00:00+01:00" ', + type: 'string', + }, email: { name: 'email', in: 'body', @@ -74,6 +102,14 @@ ApiV1.swagger = { required: true, type: 'string', }, + emailResponse: { + properties: { + name: 'address', + in: 'body', + description: 'Email address for user', + type: 'string', + }, + }, lifecycle: { name: 'lifecycle', in: 'query', @@ -130,13 +166,54 @@ ApiV1.swagger = { }, since: { name: 'since', - in: 'path', + in: 'query', description: 'Time frame in days', - required: true, + required: false, type: 'integer', format: 'int32', minimum: 1, }, + skip: { + name: 'skip', + in: 'query', + description: 'Number of records to skip for pagination.', + required: false, + type: 'integer', + format: 'int32', + minimum: 0, + }, + sortBy: { + name: 'sort_by', + in: 'query', + description: 'Criteria for sort ', + required: false, + type: 'string', + }, + userAddition: { + name: 'user', + in: 'body', + description: 'Data for adding a new User', + schema: { + $ref: '#/definitions/user_addition', + }, + }, + userLogin: { + name: 'user', + in: 'body', + description: 'User login data', + schema: { + $ref: '#/definitions/user_login', + }, + }, + userUpdate: { + name: 'user', + in: 'body', + description: 'Data for updating a User', + schema: { + $ref: '#/definitions/user_update', + }, + }, + userId: { name: 'id', in: 'path', @@ -144,22 +221,42 @@ ApiV1.swagger = { required: true, type: 'string', }, - userName: { + userIdResponse: { + name: '_id', + in: 'body', + description: 'ID of User', + required: true, + type: 'string', + }, + username: { name: 'username', in: 'body', description: 'Username', required: true, type: 'string', }, - skip: { - name: 'skip', + userOrganizationId: { + name: 'organization_id', in: 'query', - description: 'Number of records to skip for pagination.', + description: 'ID of Organization, that User belongs to', required: false, - type: 'integer', - format: 'int32', - minimum: 0, + type: 'string', }, + x_user_id: { + name: 'X-User-Id', + in: 'header', + description: 'User ID for authentication', + required: false, + type: 'string', + }, + x_auth_token: { + name: 'X-Auth-Token', + in: 'header', + description: 'Authentication Token', + required: false, + type: 'string', + }, + }, definitions: { // The schema defining the type used for the body parameter. @@ -243,6 +340,236 @@ ApiV1.swagger = { }, }, }, + + users: { + required: ['username', 'email', 'password'], + properties: { + username: { + type: 'string', + description: 'Username', + example: 'johndoe', + }, + email: { + type: 'string', + format: 'email', + description: 'E-mail address of user', + example: 'john.doe@ispname.com', + }, + password: { + type: 'string', + description: 'Password for user', + example: 'mypassword', + }, + company: { + type: 'string', + description: 'Company name of user', + example: 'My Company Ltd', + }, + }, + }, + user_addition: { + required: ['username', 'email', 'password'], + properties: { + username: { + type: 'string', + description: 'Username', + example: 'johndoe', + }, + email: { + type: 'string', + format: 'email', + description: 'E-mail address of user', + example: 'john.doe@ispname.com', + }, + password: { + type: 'string', + description: 'Password for user', + example: 'mypassword', + }, + }, + }, + user_login: { + required: ['username', 'password'], + properties: { + username: { + type: 'string', + description: 'Username', + example: 'johndoe', + }, + password: { + type: 'string', + description: 'Password for user', + example: 'mypassword', + }, + }, + }, + user_update: { + required: ['username', 'company', 'password'], + properties: { + username: { + type: 'string', + description: 'Username', + example: 'johndoe', + }, + company: { + type: 'string', + description: 'Name of company user belongs to', + example: 'Mighty API owners Ltd.', + }, + password: { + type: 'string', + description: 'Password for user', + example: 'mypassword', + }, + }, + }, + + get_user_profile: { + type: 'object', + properties: { + company: { + type: 'string', + example: 'Mighty API owners Ltd.', + }, + }, + }, + get_user_created_at: { + type: 'string', + example: '2012-07-14T01:00:00+01:00', + description: 'Dates and times are stored in ISODate format', + + }, + post_user_email_address: { + type: 'array', + items: { + type: 'object', + properties: { + address: { + type: 'string', + example: 'sam.won@apinf.io', + }, + verified: { + type: 'string', + example: 'false', + }, + }, + }, + }, + get_user_roles: { + type: 'array', + items: { + type: 'string', + example: 'manager', + }, + }, + get_user_data: { + type: 'object', + properties: { + status: { + type: 'string', + example: 'success', + }, + data: { + type: 'object', + properties: { + _id: { + type: 'string', + example: '7L4jNtdfNFGH3igPs', + }, + created_at: { + $ref: '#/definitions/get_user_created_at', + }, + username: { + type: 'string', + example: 'myusername', + }, + emails: { + $ref: '#/definitions/post_user_email_address', + }, + profile: { + $ref: '#/definitions/get_user_profile', + }, + roles: { + $ref: '#/definitions/get_user_roles', + }, + organization: { + $ref: '#/definitions/get_user_organization', + }, + }, + }, + }, + }, + get_user_organization: { + type: 'array', + items: { + type: 'object', + properties: { + organization_id: { + type: 'string', + example: 'eFsLsJH3JTos4HfLc', + }, + organization_name: { + type: 'string', + example: 'APInf oy', + }, + }, + }, + }, + + post_user_response: { + type: 'object', + properties: { + status: { + type: 'string', + example: 'success', + }, + data: { + type: 'object', + properties: { + _id: { + type: 'string', + example: '7L4jNtdfNFGH3igPs', + }, + created_at: { + $ref: '#/definitions/get_user_created_at', + }, + username: { + type: 'string', + example: 'myusername', + }, + emails: { + $ref: '#/definitions/post_user_email_address', + }, + profile: { + $ref: '#/definitions/get_user_profile', + }, + }, + }, + }, + }, + user_login_response: { + type: 'object', + properties: { + status: { + type: 'string', + example: 'success', + }, + data: { + type: 'object', + properties: { + authToken: { + type: 'string', + example: '7L4jNt-dfNFGH3igPslP5VMH0-hrnbMSFtmjfVOMm_zVg0yT8eGQ-', + }, + userId: { + type: 'string', + example: 'GFJzMtdzqEYgH8PHSQ-', + }, + }, + }, + }, + }, + }, }; diff --git a/docker/apinf/env.example b/docker/apinf/env.example index 1f84ee05cb..c80ac69280 100644 --- a/docker/apinf/env.example +++ b/docker/apinf/env.example @@ -1 +1,2 @@ ROOT_URL=https://example.com +SENTRY_DSN=https://public:private@sentry-dsn.url diff --git a/organizations/server/api.js b/organizations/server/api.js index b0912a89f3..b31f617399 100644 --- a/organizations/server/api.js +++ b/organizations/server/api.js @@ -19,6 +19,7 @@ ApiV1.addRoute('organizations', { tags: [ ApiV1.swagger.tags.organization, ], + summary: 'List and search organizations.', description: 'List and search organizations.', parameters: [ ApiV1.swagger.params.optionalSearch, @@ -91,6 +92,7 @@ ApiV1.addRoute('organizations', { tags: [ ApiV1.swagger.tags.organization, ], + summary: 'Add Organization to catalog.', description: 'Adds an Organization to catalog. On success, returns newly added object.', parameters: [ ApiV1.swagger.params.organization, @@ -158,6 +160,7 @@ ApiV1.addRoute('organizations/:id', { tags: [ ApiV1.swagger.tags.organization, ], + summary: 'Fetch Organization with specified ID', description: 'Returns one Organization with specified ID or nothing if not match found', parameters: [ ApiV1.swagger.params.organizationId, @@ -201,6 +204,7 @@ ApiV1.addRoute('organizations/:id', { tags: [ ApiV1.swagger.tags.organization, ], + summary: 'Update Organization', description: 'Update an Organization', parameters: [ ApiV1.swagger.params.organizationId, @@ -301,6 +305,7 @@ ApiV1.addRoute('organizations/:id', { tags: [ ApiV1.swagger.tags.organization, ], + summary: 'Delete identified Organization from catalog.', description: 'Deletes the identified Organization from catalog.', parameters: [ ApiV1.swagger.params.organizationId, diff --git a/organizations/server/methods.js b/organizations/server/methods.js index 3980d0994d..e45be462d1 100644 --- a/organizations/server/methods.js +++ b/organizations/server/methods.js @@ -100,6 +100,23 @@ Meteor.methods({ } ); }, + removeUserFromAllOrganizations (userId) { + // Make sure userId is an String + check(userId, String); + // Get list of Organizations where user is a manager + const organizations = Organizations.find({ + managerIds: userId, + }).fetch(); + + // If user is a manager in any Organization + if (organizations.length > 0) { + // Loop through Users' Organizations + organizations.forEach((organization) => { + // Remove user from organization manager list + Meteor.call('removeOrganizationManager', organization._id, userId); + }); + } + }, removeApiFromFeaturedList (organizationId, apiId) { // Make sure organizationId is an String check(organizationId, String); diff --git a/package.json b/package.json index 4e54ee4f86..6ec5a8f996 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "apinf", - "version": "0.46.0", + "version": "0.47.0", "description": "API management portal and proxy.", "main": "index.js", "directories": { @@ -27,6 +27,8 @@ "lodash": "^4.17.4", "meteor-node-stubs": "^0.2.5", "moment": "^2.17.1", + "raven": "^2.1.0", + "raven-js": "^3.16.0", "simple-statistics": "^2.2.0", "swagger-client": "^2.1.32", "swagger-parser": "^3.4.1", @@ -51,7 +53,7 @@ "chimp-watch": "chimp --ddp=http://localhost:3000 --mocha --path=tests/end-to-end --watch", "chimp-test": "chimp tests/chimp-config.js", "start": "meteor", - "test": "mocha ./.test/" + "test": "node .scripts/run-chimp.js" }, "repository": { "type": "git", diff --git a/proxies/client/remove/remove.html b/proxies/client/remove/remove.html index c51f242653..3693f7d3c0 100644 --- a/proxies/client/remove/remove.html +++ b/proxies/client/remove/remove.html @@ -23,24 +23,25 @@

diff --git a/setup/client/modal.html b/setup/client/modal.html index a5ae79087f..c92844e59a 100644 --- a/setup/client/modal.html +++ b/setup/client/modal.html @@ -21,10 +21,10 @@