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' }}
-
+
{{_ "branding_save" }}
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 @@
- {{_ "removeProxy_modalLabel" }}
+ {{_ "removeProxy_modalLabel_title" }}
- {{ connectedProxyBackends }} {{_ "removeProxy_modalBody" }}
+ {{_ "removeProxy_modalBody_warning" }}
+ {{_ "removeProxy_modalBody_text" count=connectedProxyBackends }}
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 @@
diff --git a/tests/data/settings.js b/tests/data/settings.js
new file mode 100644
index 0000000000..ddde489497
--- /dev/null
+++ b/tests/data/settings.js
@@ -0,0 +1,3 @@
+
+export const githubClientId = 'github-client-id';
+export const githubClientSecret = 'g1th&b53c43t';
diff --git a/tests/end-to-end/ui/03-user-creation.js b/tests/end-to-end/ui/03-user-creation.js
index 813800f059..a5770db78e 100644
--- a/tests/end-to-end/ui/03-user-creation.js
+++ b/tests/end-to-end/ui/03-user-creation.js
@@ -10,19 +10,36 @@ https://joinup.ec.europa.eu/community/eupl/og_page/european-union-public-licence
import signUpPage from '../../page-objects/signup.page';
import mainContent from '../../page-objects/main-content.page';
+import settingsPage from '../../page-objects/settings.page';
-import { username, email, password } from '../../data/user';
-
+import {
+ adminUsername,
+ adminEmail,
+ adminPassword,
+} from '../../data/user';
+import { githubClientId, githubClientSecret } from '../../data/settings';
describe('03 user creation', () => {
before(() => {
signUpPage.open();
});
- it('create user', () => {
- signUpPage.registerNewUser({ username, email, password });
+ it('create admin user', () => {
+ const adminCredentials = {
+ username: adminUsername,
+ email: adminEmail,
+ password: adminPassword,
+ };
+
+ // Create Admin user
+ signUpPage.registerNewUser(adminCredentials);
+
+ // Setup settings
+ signUpPage.gotModalToSetup();
+ settingsPage.setupGithub({ githubClientId, githubClientSecret });
mainContent.signOutLink.waitForExist(5000);
mainContent.signOutLink.isVisible().should.be.true;
+ mainContent.logOut();
});
});
diff --git a/tests/page-objects/settings.page.js b/tests/page-objects/settings.page.js
new file mode 100644
index 0000000000..476796ab46
--- /dev/null
+++ b/tests/page-objects/settings.page.js
@@ -0,0 +1,54 @@
+/* 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 */
+
+/* globals browser */
+
+import Page from './page';
+
+class SettingsPage extends Page {
+
+ // Left Menu
+ get buttonProfileLink () { return browser.element('#button-profile'); }
+ get buttonAccountLink () { return browser.element('#button-account'); }
+ get buttonBrandingLink () { return browser.element('#button-branding'); }
+ get buttonSettingsLink () { return browser.element('#button-settings'); }
+ get buttonProxiesLink () { return browser.element('#button-proxies'); }
+
+ // Profile
+ get username () { return browser.element('#username'); }
+ get company () { return browser.element('#company'); }
+ get updateButton () { return browser.element('#update-button'); }
+
+ // Account
+ get passwordOld () { return browser.element('#password-old'); }
+ get passwordNew () { return browser.element('#password-new'); }
+ get passwordNewAgain () { return browser.element('#password-new-again'); }
+ get submitPasswordButton () { return browser.element('#submit-password'); }
+
+ // Branding
+ get brandingUpdateButton () { return browser.element('#branding-update-button'); }
+ get brandingSaveButton () { return browser.element('#branding-save-button'); }
+
+ // Settings
+ get githubId () { return browser.element('#github-id'); }
+ get githubSecret () { return browser.element('#github-secret'); }
+ get saveSettingsButton () { return browser.element('#save-settings'); }
+
+ setupGithub ({ githubClientId, githubClientSecret }) {
+ this.githubId.waitForVisible(5000);
+
+ this.githubId.setValue(githubClientId);
+ this.githubSecret.setValue(githubClientSecret);
+
+ this.submit();
+ }
+
+ submit () {
+ this.saveSettingsButton.waitForVisible(5000);
+ this.saveSettingsButton.click();
+ }
+}
+
+module.exports = new SettingsPage();
diff --git a/tests/page-objects/signup.page.js b/tests/page-objects/signup.page.js
index 1fb83952aa..39df6bac06 100644
--- a/tests/page-objects/signup.page.js
+++ b/tests/page-objects/signup.page.js
@@ -24,6 +24,9 @@ class SignUpPage extends Page {
get passwordErrorField () { return this.errorFields[2]; }
get confirmPasswordErrorField () { return this.errorFields[3]; }
+ // Modal after first signup
+ get modalSettingsLink () { return browser.element('#setup-settings'); }
+
open () {
super.open();
this.pause();
@@ -40,6 +43,11 @@ class SignUpPage extends Page {
this.submit();
}
+ gotModalToSetup () {
+ this.modalSettingsLink.waitForVisible(5000);
+ this.modalSettingsLink.click();
+ }
+
submit () {
this.registerButton.waitForVisible(5000);
this.registerButton.click();
diff --git a/users/collection/server/api.js b/users/collection/server/api.js
index 9a1b0bf760..e0779c6963 100644
--- a/users/collection/server/api.js
+++ b/users/collection/server/api.js
@@ -17,6 +17,230 @@ import Organizations from '/organizations/collection';
// Npm packages imports
import _ from 'lodash';
+ApiV1.swagger.meta.paths['/login'].post = {
+ tags: [
+ ApiV1.swagger.tags.login,
+ ],
+ summary: 'Logging in.',
+ description: 'By giving existing username and password you get login credentials.',
+ produces: 'application/json',
+ parameters: [
+ ApiV1.swagger.params.userLogin,
+ ],
+ responses: {
+ 200: {
+ description: 'Success',
+ schema:
+ ApiV1.swagger.definitions.user_login_response,
+ },
+ 400: {
+ description: 'Bad query parameters',
+ },
+ 401: {
+ description: 'Unauthorized',
+ },
+ },
+};
+
+ApiV1.swagger.meta.paths['/users'].get = {
+ tags: [
+ ApiV1.swagger.tags.users,
+ ],
+ summary: 'List and search users.',
+ description: 'By passing options you can search users in system.',
+ produces: 'application/json',
+ parameters: [
+ ApiV1.swagger.params.x_user_id,
+ ApiV1.swagger.params.x_auth_token,
+ ApiV1.swagger.params.optionalSearch,
+ ApiV1.swagger.params.userOrganizationId,
+ ApiV1.swagger.params.skip,
+ ApiV1.swagger.params.limit,
+ ApiV1.swagger.params.sortBy,
+ ],
+ responses: {
+ 200: {
+ description: 'Success',
+ schema:
+ ApiV1.swagger.definitions.get_user_data,
+ },
+ 400: {
+ description: 'Bad query parameters',
+ },
+ 401: {
+ description: 'Authentication is required',
+ },
+ },
+};
+
+ApiV1.swagger.meta.paths['/users'].post = {
+ tags: [
+ ApiV1.swagger.tags.users,
+ ],
+ summary: 'Adds a new user.',
+ description: 'Adds a new user. On success, returns newly added object.',
+ produces: 'application/json',
+ parameters: [
+ ApiV1.swagger.params.userAddition,
+ ],
+ responses: {
+ 201: {
+ description: 'User successfully added',
+ schema:
+ ApiV1.swagger.definitions.post_user_response,
+ },
+ 400: {
+ description: 'Invalid input, object invalid',
+ },
+ 401: {
+ description: 'Authentication is required',
+ },
+ 409: {
+ description: 'User already exists',
+ },
+ },
+ security: [
+ {
+ userSecurityToken: [],
+ userId: [],
+ },
+ ],
+};
+
+
+ApiV1.swagger.meta.paths['/users/{id}'].get = {
+ tags: [
+ ApiV1.swagger.tags.users,
+ ],
+ summary: 'Search Users one by one with userID.',
+ description: 'Returns user data with given ID.',
+ produces: 'application/json',
+ parameters: [
+ ApiV1.swagger.params.x_user_id,
+ ApiV1.swagger.params.x_auth_token,
+ ApiV1.swagger.params.userId,
+ ],
+ responses: {
+ 200: {
+ description: 'Data of identified user.',
+ schema:
+ ApiV1.swagger.definitions.get_user_data,
+
+ },
+ 401: {
+ description: 'Authentication is required',
+ },
+ 403: {
+ description: 'User does not have permission.',
+ },
+ 404: {
+ description: 'No user found with given UserID.',
+ },
+ },
+};
+
+ApiV1.swagger.meta.paths['/users/{id}'].delete = {
+ tags: [
+ ApiV1.swagger.tags.users,
+ ],
+ summary: 'Delete Users one by one with userID.',
+ description: 'Deletes the identified User.',
+ parameters: [
+ ApiV1.swagger.params.x_user_id,
+ ApiV1.swagger.params.x_auth_token,
+ ApiV1.swagger.params.userId,
+ ],
+ responses: {
+ 200: {
+ description: 'User deleted.',
+ },
+ 400: {
+ description: 'Invalid input, invalid object',
+ },
+ 401: {
+ description: 'Authentication is required',
+ },
+ 403: {
+ description: 'User does not have permission',
+ },
+ 404: {
+ description: 'User not found',
+ },
+ },
+ security: [
+ {
+ userSecurityToken: [],
+ userId: [],
+ },
+ ],
+};
+
+ApiV1.swagger.meta.paths['/users/{id}'].put = {
+ tags: [
+ ApiV1.swagger.tags.users,
+ ],
+ summary: 'Update User\'s data.',
+ description: 'Updates data of a User indicated by user ID.',
+ parameters: [
+ ApiV1.swagger.params.x_user_id,
+ ApiV1.swagger.params.x_auth_token,
+ ApiV1.swagger.params.userId,
+ ApiV1.swagger.params.userUpdate,
+ ],
+ responses: {
+ 200: {
+ description: 'User successfully updated.',
+ },
+ 400: {
+ description: 'Invalid input, object invalid, Erroneous new password',
+ },
+ 401: {
+ description: 'Authentication is required',
+ },
+ 403: {
+ description: 'User does not have permission',
+ },
+ 404: {
+ description: 'No user found with given UserID',
+ },
+ },
+ security: [
+ {
+ userSecurityToken: [],
+ userId: [],
+ },
+ ],
+};
+
+ApiV1.swagger.meta.paths['/users/updates'].get = {
+ tags: [
+ ApiV1.swagger.tags.users,
+ ],
+ summary: 'List and search user based on addition date',
+ description: 'Returns users based on addition date',
+ produces: 'application/json',
+ parameters: [
+ ApiV1.swagger.params.x_user_id,
+ ApiV1.swagger.params.x_auth_token,
+ ApiV1.swagger.params.since,
+ ApiV1.swagger.params.userOrganizationId,
+ ApiV1.swagger.params.skip,
+ ApiV1.swagger.params.limit,
+ ],
+ responses: {
+ 200: {
+ description: 'success',
+ schema:
+ ApiV1.swagger.definitions.get_user_data,
+ },
+ 400: {
+ description: 'Bad query parameters',
+ },
+ 401: {
+ description: 'Authentication is required',
+ },
+ },
+};
// Generates: POST on /api/v1/users and GET, DELETE /api/v1/users/:id for
// Meteor.users collection
@@ -28,27 +252,6 @@ ApiV1.addCollection(Meteor.users, {
endpoints: {
getAll: {
authRequired: true,
- swagger: {
- tags: [
- ApiV1.swagger.tags.users,
- ],
- description: 'Returns users',
- parameters: [
- ApiV1.swagger.params.optionalSearch,
- ApiV1.swagger.params.organization_id,
- ApiV1.swagger.params.skip,
- ApiV1.swagger.params.limit,
- ApiV1.swagger.params.sort_by,
- ],
- responses: {
- 200: {
- description: 'success',
- },
- 400: {
- description: 'Bad query parameters',
- },
- },
- },
action () {
const queryParams = this.queryParams;
@@ -61,9 +264,9 @@ ApiV1.addCollection(Meteor.users, {
const requestorId = this.userId;
// Check if requestor is administrator
- const requestorHasRights = Roles.userIsInRole(requestorId, ['admin']);
+ const requestorIsAdmin = Roles.userIsInRole(requestorId, ['admin']);
- if (!requestorHasRights) {
+ if (!requestorIsAdmin) {
searchOnlyWithOwnId = true;
}
@@ -192,56 +395,82 @@ ApiV1.addCollection(Meteor.users, {
},
get: {
authRequired: true,
- swagger: {
- tags: [
- ApiV1.swagger.tags.users,
- ],
- description: 'Returns user with given ID.',
- parameters: [
- ApiV1.swagger.params.userId,
- ],
- responses: {
- 200: {
- description: 'One user.',
+ action () {
+ // Get requestor's id
+ const requestorId = this.userId;
+
+ const userIsGettingOwnAccount = this.urlParams.id === requestorId;
+
+ const userIsAdmin = Roles.userIsInRole(requestorId, ['admin']);
+
+ if (userIsGettingOwnAccount || userIsAdmin) {
+ // Get ID of User to be fetched
+ const userId = this.urlParams.id;
+
+ // Exclude password
+ const options = {};
+ const excludeFields = {};
+
+ excludeFields.services = 0;
+ options.fields = excludeFields;
+
+ // Check if user exists
+ const user = Meteor.users.findOne(userId, options);
+ if (user) {
+ // Array for Organization name and id
+ const orgDataList = [];
+ // Get user id
+ const userIdSearch = user._id;
+ // Find all Organizations, where User belongs to
+ const organizations = Organizations.find({
+ managerIds: userIdSearch,
+ }).fetch();
+ // If there are Users' Organizations
+ if (organizations.length > 0) {
+ // Loop through Users' Organizations
+ organizations.forEach((organization) => {
+ const orgData = {};
+ // Put Organization name and id into an object
+ orgData.organizationId = organization._id;
+ orgData.organizationName = organization.name;
+ // Add this Organization data into Users' organization data list
+ orgDataList.push(orgData);
+ });
+ // Add Organizations' information to Users' data
+ user.organization = orgDataList;
+ }
+ // Construct response
+ return {
+ statusCode: 200,
+ body: {
+ status: 'success',
+ data: user,
+ },
+ };
+ }
+
+ // User didn't exist
+ return {
+ statusCode: 404,
+ body: {
+ status: 'Fail',
+ message: 'No user found with given UserID',
+ },
+ };
+ }
+ return {
+ statusCode: 403,
+ body: {
+ status: 'Fail',
+ message: 'User does not have permission',
},
- },
+ };
},
+
},
post: {
- authRequired: true,
- roleRequired: ['admin'],
- swagger: {
- tags: [
- ApiV1.swagger.tags.users,
- ],
- description: 'Adds a new user. On success, returns newly added object.',
- parameters: [
- ApiV1.swagger.params.userId,
- ApiV1.swagger.params.userName,
- ApiV1.swagger.params.email,
- ApiV1.swagger.params.password,
- ],
- responses: {
- 201: {
- description: 'User successfully added',
- },
- 400: {
- description: 'Invalid input, object invalid',
- },
- 401: {
- description: 'Authentication is required',
- },
- 409: {
- description: 'User already exists',
- },
- },
- security: [
- {
- userSecurityToken: [],
- userId: [],
- },
- ],
- },
+ authRequired: false,
+ // roleRequired: ['admin'],
action () {
// Get data from body parameters
const bodyParams = this.bodyParams;
@@ -304,38 +533,6 @@ ApiV1.addCollection(Meteor.users, {
// Delete a user
delete: {
authRequired: true,
- swagger: {
- tags: [
- ApiV1.swagger.tags.users,
- ],
- description: 'Deletes the identified Organization from catalog.',
- parameters: [
- ApiV1.swagger.params.userId,
- ],
- responses: {
- 200: {
- description: 'User deleted.',
- },
- 400: {
- description: 'Invalid input, invalid object',
- },
- 401: {
- description: 'Authentication is required',
- },
- 403: {
- description: 'User does not have permission',
- },
- 404: {
- description: 'User not found',
- },
- },
- security: [
- {
- userSecurityToken: [],
- userId: [],
- },
- ],
- },
action () {
// Get requestor's id
const requestorId = this.userId;
@@ -350,6 +547,9 @@ ApiV1.addCollection(Meteor.users, {
// Check if user exists
const user = Meteor.users.findOne(userId);
if (user) {
+ // Remove user from all Organizations
+ Meteor.call('removeUserFromAllOrganizations', userId);
+
// Remove existing User account
Meteor.users.remove(user._id);
@@ -383,38 +583,6 @@ ApiV1.addCollection(Meteor.users, {
// Udpdate user data
put: {
authRequired: true,
- swagger: {
- tags: [
- ApiV1.swagger.tags.users,
- ],
- description: 'Update a User',
- parameters: [
- ApiV1.swagger.params.userId,
- ApiV1.swagger.params.userName,
- ApiV1.swagger.params.company,
- ApiV1.swagger.params.password,
- ],
- responses: {
- 200: {
- description: 'User successfully updated.',
- },
- 401: {
- description: 'Authentication is required',
- },
- 403: {
- description: 'User does not have permission',
- },
- 404: {
- description: 'No user found with given UserID',
- },
- },
- security: [
- {
- userSecurityToken: [],
- userId: [],
- },
- ],
- },
action () {
// Get requestor's id
const requestorId = this.userId;
@@ -550,26 +718,6 @@ ApiV1.addCollection(Meteor.users, {
ApiV1.addRoute('users/updates', {
get: {
roleRequired: ['admin'],
- swagger: {
- tags: [
- ApiV1.swagger.tags.users,
- ],
- description: 'Returns users',
- parameters: [
- ApiV1.swagger.params.since,
- ApiV1.swagger.params.organization_id,
- ApiV1.swagger.params.skip,
- ApiV1.swagger.params.limit,
- ],
- responses: {
- 200: {
- description: 'success',
- },
- 400: {
- description: 'Bad query parameters',
- },
- },
- },
action () {
let badQueryParameters = false;
// Read possible query parameters
diff --git a/yarn.lock b/yarn.lock
index 66b053af55..1ab50fc0c3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1300,6 +1300,10 @@ convert-source-map@^1.1.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
+cookie@0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+
cookiejar@^2.0.1, cookiejar@^2.0.6:
version "2.1.1"
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a"
@@ -2777,7 +2781,7 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
dependencies:
jsonify "~0.0.0"
-json-stringify-safe@~5.0.0, json-stringify-safe@~5.0.1:
+json-stringify-safe@5.0.1, json-stringify-safe@~5.0.0, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -2984,6 +2988,10 @@ lru-cache@2.6.x:
version "2.6.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5"
+lsmod@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b"
+
meteor-node-stubs@^0.2.5:
version "0.2.6"
resolved "https://registry.yarnpkg.com/meteor-node-stubs/-/meteor-node-stubs-0.2.6.tgz#dd43c9a8bc7793d80adc766d342410da6de909c5"
@@ -3555,6 +3563,21 @@ randombytes@^2.0.0, randombytes@^2.0.1:
version "2.0.3"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
+raven-js@^3.16.0:
+ version "3.16.0"
+ resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.16.0.tgz#a799da4fdd04c63943f67deb93daa0ecfe101eab"
+
+raven@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/raven/-/raven-2.1.0.tgz#1b624e56374d9c9d93c74448461a2a356ce37527"
+ dependencies:
+ cookie "0.3.1"
+ json-stringify-safe "5.0.1"
+ lsmod "1.0.0"
+ stack-trace "0.0.9"
+ timed-out "4.0.1"
+ uuid "3.0.0"
+
rc@^1.1.7:
version "1.2.1"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
@@ -4013,6 +4036,10 @@ stack-generator@^1.0.7:
dependencies:
stackframe "^1.0.2"
+stack-trace@0.0.9:
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695"
+
stackframe@^0.3.1, stackframe@~0.3:
version "0.3.1"
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4"
@@ -4257,7 +4284,7 @@ through2@2.0.1:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
-timed-out@^4.0.0:
+timed-out@4.0.1, timed-out@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
@@ -4406,9 +4433,9 @@ util@0.10.3, util@^0.10.3:
dependencies:
inherits "2.0.1"
-uuid@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"
+uuid@3.0.0, uuid@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728"
validator@^6.0.0:
version "6.3.0"