IGNITE-10914 Web Console: Fixed missing unique index on Accounts.
diff --git a/backend/app/schemas.js b/backend/app/schemas.js
index 2f6498f..fe3c637 100644
--- a/backend/app/schemas.js
+++ b/backend/app/schemas.js
@@ -37,7 +37,7 @@
const Account = new Schema({
firstName: String,
lastName: String,
- email: String,
+ email: {type: String, unique: true},
phone: String,
company: String,
country: String,
diff --git a/backend/migrations/1547440382485-account-make-email-unique.js b/backend/migrations/1547440382485-account-make-email-unique.js
new file mode 100644
index 0000000..787c767
--- /dev/null
+++ b/backend/migrations/1547440382485-account-make-email-unique.js
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const _ = require('lodash');
+
+const log = require('./migration-utils').log;
+
+function deduplicateAccounts(model) {
+ const accountsModel = model('Account');
+ const spaceModel = model('Space');
+
+ return accountsModel.aggregate([
+ {$group: {_id: '$email', count: {$sum: 1}}},
+ {$match: {count: {$gt: 1}}}
+ ]).exec()
+ .then((accounts) => _.map(accounts, '_id'))
+ .then((emails) => Promise.all(
+ _.map(emails, (email) => accountsModel.find({email}, {_id: 1, email: 1, lastActivity: 1, lastLogin: 1}).lean().exec())
+ ))
+ .then((promises) => {
+ const duplicates = _.flatMap(promises, (accounts) => _.sortBy(accounts, [(a) => a.lastActivity || '', 'lastLogin']).slice(0, -1));
+
+ if (_.isEmpty(duplicates))
+ log('Duplicates not found!');
+ else {
+ log(`Duplicates found: ${_.size(duplicates)}`);
+
+ _.forEach(duplicates, (dup) => log(` ID: ${dup._id}, e-mail: ${dup.email}`));
+ }
+
+ return _.map(duplicates, '_id');
+ })
+ .then((accountIds) => {
+ if (_.isEmpty(accountIds))
+ return Promise.resolve();
+
+ return spaceModel.find({owner: {$in: accountIds}}, {_id: 1}).lean().exec()
+ .then((spaces) => _.map(spaces, '_id'))
+ .then((spaceIds) =>
+ Promise.all([
+ model('Cluster').remove({space: {$in: spaceIds}}).exec(),
+ model('Cache').remove({space: {$in: spaceIds}}).exec(),
+ model('DomainModel').remove({space: {$in: spaceIds}}).exec(),
+ model('Igfs').remove({space: {$in: spaceIds}}).exec(),
+ model('Notebook').remove({space: {$in: spaceIds}}).exec(),
+ model('Activities').remove({owner: accountIds}).exec(),
+ model('Notifications').remove({owner: accountIds}).exec(),
+ spaceModel.remove({owner: accountIds}).exec(),
+ accountsModel.remove({_id: accountIds}).exec()
+ ])
+ )
+ .then(() => {
+ const conditions = _.map(accountIds, (accountId) => ({session: {$regex: `"${accountId}"`}}));
+
+ return accountsModel.db.collection('sessions').deleteMany({$or: conditions});
+ });
+ });
+}
+
+exports.up = function up(done) {
+ deduplicateAccounts((name) => this(name))
+ .then(() => this('Account').collection.createIndex({email: 1}, {unique: true, background: false}))
+ .then(() => done())
+ .catch(done);
+};
+
+exports.down = function down(done) {
+ log('Account migration can not be reverted');
+
+ done();
+};
diff --git a/frontend/app/components/list-editable/controller.ts b/frontend/app/components/list-editable/controller.ts
index e870e82..d4d9da6 100644
--- a/frontend/app/components/list-editable/controller.ts
+++ b/frontend/app/components/list-editable/controller.ts
@@ -43,8 +43,8 @@
}
ngModel: ListEditableNgModel<T>;
- hasItemView: boolean
- private _cache: Map<ID, T>
+ hasItemView: boolean;
+ private _cache: Map<ID, T>;
id(item: T | undefined, index: number): ID {
if (item && item._id)
@@ -61,6 +61,7 @@
this.ngModel.$isEmpty = (value) => {
return !Array.isArray(value) || !value.length;
};
+
this.ngModel.editListItem = (item) => {
this.$timeout(() => {
this.startEditView(this.id(item, this.ngModel.$viewValue.indexOf(item)));
@@ -69,6 +70,7 @@
this.ngModel.$validate();
});
};
+
this.ngModel.editListIndex = (index) => {
this.$timeout(() => {
this.startEditView(this.id(this.ngModel.$viewValue[index], index));
diff --git a/frontend/app/components/permanent-notifications/controller.ts b/frontend/app/components/permanent-notifications/controller.ts
index d672304..ff0b182 100644
--- a/frontend/app/components/permanent-notifications/controller.ts
+++ b/frontend/app/components/permanent-notifications/controller.ts
@@ -16,12 +16,14 @@
*/
export default class PermanentNotifications {
- static $inject = ['UserNotifications', '$rootScope', '$window']
+ static $inject = ['UserNotifications', '$rootScope', '$window'];
+
constructor(
private UserNotifications: unknown,
private $rootScope: ng.IRootScopeService,
private $window: ng.IWindowService
) {}
+
closeDemo() {
this.$window.close();
}
diff --git a/frontend/app/components/web-console-footer/controller.ts b/frontend/app/components/web-console-footer/controller.ts
index 0811748..0ca604c 100644
--- a/frontend/app/components/web-console-footer/controller.ts
+++ b/frontend/app/components/web-console-footer/controller.ts
@@ -18,9 +18,12 @@
import {default as Version} from '../../services/Version.service';
export default class WebConsoleFooter {
- static $inject = ['IgniteVersion', '$rootScope']
+ static $inject = ['IgniteVersion', '$rootScope'];
+
constructor(private Version: Version, private $root: ng.IRootScopeService) {}
- year = new Date().getFullYear()
+
+ year = new Date().getFullYear();
+
get userIsAuthorized() {
return !!this.$root.user;
}
diff --git a/frontend/app/components/web-console-header/components/user-menu/controller.ts b/frontend/app/components/web-console-header/components/user-menu/controller.ts
index 812d3b5..dc5768a 100644
--- a/frontend/app/components/web-console-header/components/user-menu/controller.ts
+++ b/frontend/app/components/web-console-header/components/user-menu/controller.ts
@@ -16,7 +16,8 @@
*/
export default class UserMenu {
- static $inject = ['$rootScope', 'IgniteUserbar', 'AclService', '$state', 'gettingStarted']
+ static $inject = ['$rootScope', 'IgniteUserbar', 'AclService', '$state', 'gettingStarted'];
+
constructor(
private $root: ng.IRootScopeService,
private IgniteUserbar: any,
@@ -24,6 +25,7 @@
private $state: any,
private gettingStarted: any
) {}
+
$onInit() {
this.items = [
{text: 'Profile', sref: 'base.settings.profile'},
@@ -47,6 +49,7 @@
this.$root.$on('user', _rebuildSettings);
}
+
get user() {
return this.$root.user;
}
diff --git a/frontend/app/components/web-console-header/components/web-console-header-content/controller.ts b/frontend/app/components/web-console-header/components/web-console-header-content/controller.ts
index 4345553..2ae9000 100644
--- a/frontend/app/components/web-console-header/components/web-console-header-content/controller.ts
+++ b/frontend/app/components/web-console-header/components/web-console-header-content/controller.ts
@@ -18,7 +18,8 @@
import {StateService} from '@uirouter/angularjs';
export default class WebConsoleHeaderContent {
- static $inject = ['$rootScope', '$state']
+ static $inject = ['$rootScope', '$state'];
+
constructor(
private $rootScope: ng.IRootScopeService,
private $state: StateService
diff --git a/frontend/app/components/web-console-sidebar/controller.ts b/frontend/app/components/web-console-sidebar/controller.ts
index bdf606b..02b21a1 100644
--- a/frontend/app/components/web-console-sidebar/controller.ts
+++ b/frontend/app/components/web-console-sidebar/controller.ts
@@ -18,12 +18,15 @@
import {AppStore, selectSidebarOpened} from '../../store';
export default class WebConsoleSidebar {
- static $inject = ['$rootScope', 'Store']
+ static $inject = ['$rootScope', 'Store'];
+
constructor(
private $rootScope: ng.IRootScopeService,
private store: AppStore
) {}
- sidebarOpened$ = this.store.state$.pipe(selectSidebarOpened())
+
+ sidebarOpened$ = this.store.state$.pipe(selectSidebarOpened());
+
get showNavigation(): boolean {
return !!this.$rootScope.user;
}
diff --git a/frontend/app/components/web-console-sidebar/web-console-sidebar-navigation/controller.ts b/frontend/app/components/web-console-sidebar/web-console-sidebar-navigation/controller.ts
index 8b5355a..3daafe8 100644
--- a/frontend/app/components/web-console-sidebar/web-console-sidebar-navigation/controller.ts
+++ b/frontend/app/components/web-console-sidebar/web-console-sidebar-navigation/controller.ts
@@ -18,7 +18,9 @@
import {AppStore, selectNavigationMenu} from '../../../store';
export default class WebConsoleSidebarNavigation {
- static $inject = ['Store']
+ static $inject = ['Store'];
+
constructor(private store: AppStore) {}
- menu$ = this.store.state$.pipe(selectNavigationMenu())
+
+ menu$ = this.store.state$.pipe(selectNavigationMenu());
}
diff --git a/frontend/app/components/web-console-sidebar/web-console-sidebar-overflow/controller.ts b/frontend/app/components/web-console-sidebar/web-console-sidebar-overflow/controller.ts
index d5403f4..6c2ebb9 100644
--- a/frontend/app/components/web-console-sidebar/web-console-sidebar-overflow/controller.ts
+++ b/frontend/app/components/web-console-sidebar/web-console-sidebar-overflow/controller.ts
@@ -18,13 +18,18 @@
import ResizeObserver from 'resize-observer-polyfill';
export default class WebCOnsoleSidebarOverflow {
- static $inject = ['$element', 'gridUtil', '$window']
+ static $inject = ['$element', 'gridUtil', '$window'];
+
constructor(private el: JQLite, private gridUtil: {getScrollbarWidth(): number}, private $win: ng.IWindowService) {}
- scrollEl!: JQLite
- resizeObserver: ResizeObserver
+
+ scrollEl!: JQLite;
+
+ resizeObserver: ResizeObserver;
+
$onInit() {
this.el.css('--scrollbar-width', this.gridUtil.getScrollbarWidth());
}
+
$postLink() {
this.scrollEl[0].addEventListener('scroll', this.onScroll, {passive: true});
this.resizeObserver = new ResizeObserver(() => this.applyStyles(this.scrollEl[0]));
diff --git a/frontend/app/configuration/components/page-configure-advanced/controller.ts b/frontend/app/configuration/components/page-configure-advanced/controller.ts
index 2efab10..cbeb57b 100644
--- a/frontend/app/configuration/components/page-configure-advanced/controller.ts
+++ b/frontend/app/configuration/components/page-configure-advanced/controller.ts
@@ -23,7 +23,7 @@
{ text: 'IGFS', sref: 'base.configuration.edit.advanced.igfs' }
];
- menuItems: Array<{text: string, sref: string}>
+ menuItems: Array<{text: string, sref: string}>;
$onInit() {
this.menuItems = this.constructor.menuItems;
diff --git a/frontend/app/configuration/services/ConfigChangesGuard.ts b/frontend/app/configuration/services/ConfigChangesGuard.ts
index a6735fb..70467f4 100644
--- a/frontend/app/configuration/services/ConfigChangesGuard.ts
+++ b/frontend/app/configuration/services/ConfigChangesGuard.ts
@@ -23,7 +23,7 @@
import 'jsondiffpatch/public/formatters-styles/html.css';
export class IgniteObjectDiffer<T> {
- diffPatcher: DiffPatcher
+ diffPatcher: DiffPatcher;
constructor() {
this.diffPatcher = new DiffPatcher({
diff --git a/frontend/app/configuration/services/ConfigureState.ts b/frontend/app/configuration/services/ConfigureState.ts
index db631b1..b2e10f7 100644
--- a/frontend/app/configuration/services/ConfigureState.ts
+++ b/frontend/app/configuration/services/ConfigureState.ts
@@ -19,7 +19,7 @@
import {tap, scan} from 'rxjs/operators';
export default class ConfigureState {
- actions$: Subject<{type: string}>
+ actions$: Subject<{type: string}>;
constructor() {
this.actions$ = new Subject();
diff --git a/frontend/app/store/effects/ui.ts b/frontend/app/store/effects/ui.ts
index 2a635d6..01f52ab 100644
--- a/frontend/app/store/effects/ui.ts
+++ b/frontend/app/store/effects/ui.ts
@@ -19,7 +19,7 @@
import {map} from 'rxjs/operators';
export class UIEffects {
- static $inject = ['Store']
+ static $inject = ['Store'];
constructor(private store: AppStore) {}
toggleQueriesNavItemEffect$ = this.store.actions$.pipe(