From 2e7717f13d5fd058cd860f48cd71489b0b93a5db Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 26 Aug 2024 14:14:34 +0800 Subject: [PATCH] session and universe service improvements and more utils --- addon/services/current-user.js | 289 +++++++----------- addon/services/session.js | 89 +++--- addon/services/theme.js | 2 +- addon/services/universe.js | 5 +- addon/utils/get-model-save-permission.js | 7 + app/utils/get-model-save-permission.js | 1 + package.json | 2 +- .../utils/get-model-save-permission-test.js | 10 + 8 files changed, 164 insertions(+), 241 deletions(-) create mode 100644 addon/utils/get-model-save-permission.js create mode 100644 app/utils/get-model-save-permission.js create mode 100644 tests/unit/utils/get-model-save-permission-test.js diff --git a/addon/services/current-user.js b/addon/services/current-user.js index 3681df1..726b603 100644 --- a/addon/services/current-user.js +++ b/addon/services/current-user.js @@ -3,119 +3,36 @@ import Evented from '@ember/object/evented'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { dasherize } from '@ember/string'; -import { computed, get, action } from '@ember/object'; +import { computed, get } from '@ember/object'; import { isBlank } from '@ember/utils'; import { alias } from '@ember/object/computed'; import { storageFor } from 'ember-local-storage'; export default class CurrentUserService extends Service.extend(Evented) { - /** - * Inject the `session` service - * - * @var {Service} - */ @service session; - - /** - * Inject the `store` service - * - * @var {Service} - */ @service store; - - /** - * Inject the `fetch` service - * - * @var {Service} - */ @service fetch; - - /** - * Inject the `theme` service - * - * @var {Service} - */ @service theme; - - /** - * Inject the `notifications` service - * - * @var {Service} - */ @service notifications; + @service intl; - /** - * Property to hold loaded user. - * - * @var {UserModel|Object} - * @memberof CurrentUserService - */ - @tracked user = { - id: 'anon', - }; - - /** - * The current users permissions. - * - * @memberof CurrentUserService - */ + @tracked user = { id: 'anon' }; + @tracked company = {}; @tracked permissions = []; + @tracked organizations = []; + @tracked whoisData = {}; + @tracked locale = 'en-us'; - /** - * User options in localStorage - * - * @var StorageObject - */ @storageFor('user-options') options; - - /** - * Alias for current user id - * - * @var {String} - */ @alias('user.id') id; - - /** - * Alias for current user name - * - * @var {String} - */ @alias('user.name') name; - - /** - * Alias for current user phone - * - * @var {String} - */ @alias('user.phone') phone; - - /** - * Alias for current user phone - * - * @var {String} - */ @alias('user.email') email; - - /** - * Alias for current user's company id - * - * @var {String} - */ - @alias('user.company_uuid') companyId; - - /** - * Alias for if user is admin. - * - * @var {Boolean} - * @memberof CurrentUserService - */ + @alias('user.avatar_url') avatarUrl; @alias('user.is_admin') isAdmin; + @alias('user.company_uuid') companyId; + @alias('user.company_name') companyName; - /** - * The prefix for this user options - * - * @var {String} - */ @computed('id') get optionsPrefix() { return `${this.id}:`; } @@ -140,71 +57,115 @@ export default class CurrentUserService extends Service.extend(Evented) { return this.whois('country_code'); } - /** - * Loads the current authenticated user - * - * @return Promise|null - */ async load() { if (this.session.isAuthenticated) { - let user = await this.store.findRecord('user', 'me'); + const user = await this.store.findRecord('user', 'me'); this.set('user', user); this.trigger('user.loaded', user); // Set permissions this.permissions = this.getUserPermissions(user); + // Load preferences + await this.loadPreferences(); + return user; } return null; } - /** - * Resolves a user model. - * - * @return {Promise} - */ - @action promiseUser(options = {}) { + async promiseUser(options = {}) { const NoUserAuthenticatedError = new Error('Failed to authenticate user.'); + if (!this.session.isAuthenticated) { + throw NoUserAuthenticatedError; + } - return new Promise((resolve, reject) => { - if (this.session.isAuthenticated) { - try { - this.store.queryRecord('user', { me: true }).then((user) => { - // set the `current user` - this.set('user', user); - this.trigger('user.loaded', user); - - // Set permissions - this.permissions = this.getUserPermissions(user); - - // set environment from user option - this.theme.setEnvironment(); - - // @TODO Create an event dispatch for when an authenticated user is resolved from the server - if (typeof options?.onUserResolved === 'function') { - options.onUserResolved(user); - } - - resolve(user); - }); - } catch (error) { - reject(NoUserAuthenticatedError); - } - } else { - reject(NoUserAuthenticatedError); + try { + const user = await this.store.queryRecord('user', { me: true }); + + // Set current user + this.set('user', user); + this.trigger('user.loaded', user); + + // Set permissions + this.permissions = this.getUserPermissions(user); + + // Set environment from user option + this.theme.setEnvironment(); + + // Load user preferces + await this.loadPreferences(); + + // Optional callback + if (typeof options?.onUserResolved === 'function') { + options.onUserResolved(user); } - }); + + return user; + } catch (error) { + console.log(error.message); + throw error; + } + } + + async loadPreferences() { + await this.loadLocale(); + await this.loadWhois(); + await this.loadOrganizations(); + } + + async loadLocale() { + try { + const { locale } = await this.fetch.get('users/locale'); + this.setOption('locale', locale); + this.intl.setLocale(locale); + this.locale = locale; + + return locale; + } catch (error) { + this.notifications.serverError(error); + } + } + + async loadOrganizations() { + try { + const organizations = await this.fetch.get('auth/organizations', {}, { normalizeToEmberData: true, normalizeModelType: 'company' }); + this.setOption('organizations', organizations); + this.organizations = organizations; + + return organizations; + } catch (error) { + this.notifications.serverError(error); + } + } + + async loadWhois() { + this.fetch.shouldResetCache(); + + try { + const whois = await this.fetch.cachedGet( + 'lookup/whois', + {}, + { + expirationInterval: 60, + expirationIntervalUnit: 'minutes', + } + ); + this.setOption('whois', whois); + this.whoisData = whois; + + return whois; + } catch (error) { + this.notifications.serverError(error); + } + } + + getCompany() { + this.company = this.store.peekRecord('company', this.user.company_uuid); + return this.company; } - /** - * Gets all user permissions. - * - * @param {UserModel} user - * @return {Array} - * @memberof CurrentUserService - */ getUserPermissions(user) { const permissions = []; @@ -242,25 +203,11 @@ export default class CurrentUserService extends Service.extend(Evented) { return permissions; } - /** - * Alias to get a user's whois property - * - * @param {String} key - * @return {Mixed} - * @memberof CurrentUserService - */ - @action whois(key) { + whois(key) { return this.getWhoisProperty(key); } - /** - * Sets a user's option in local storage - * - * @param {String} key - * @param {Mixed} value - * @return {CurrentUserService} - */ - @action setOption(key, value) { + setOption(key, value) { key = `${this.optionsPrefix}${dasherize(key)}`; this.options.set(key, value); @@ -268,26 +215,14 @@ export default class CurrentUserService extends Service.extend(Evented) { return this; } - /** - * Retrieves a user option from local storage - * - * @param {String} key - * @return {Mixed} - */ - @action getOption(key, defaultValue = null) { + getOption(key, defaultValue = null) { key = `${this.optionsPrefix}${dasherize(key)}`; const value = this.options.get(key); return value !== undefined ? value : defaultValue; } - /** - * Retrieves a user option from local storage - * - * @param {String} key - * @return {Mixed} - */ - @action getWhoisProperty(prop) { + getWhoisProperty(prop) { const whois = this.getOption('whois'); if (!whois || typeof whois !== 'object') { @@ -297,23 +232,11 @@ export default class CurrentUserService extends Service.extend(Evented) { return get(whois, prop); } - /** - * Checks if an option exists in users local storage - * - * @param {String} key - * @return {Boolean} - */ - @action hasOption(key) { + hasOption(key) { return this.getOption(key) !== undefined; } - /** - * Checks if an option exists in users local storage - * - * @param {String} key - * @return {Boolean} - */ - @action filledOption(key) { + filledOption(key) { return !isBlank(this.getOption(key)); } } diff --git a/addon/services/session.js b/addon/services/session.js index ea8f061..4abd23a 100644 --- a/addon/services/session.js +++ b/addon/services/session.js @@ -5,25 +5,8 @@ import { later } from '@ember/runloop'; import getWithDefault from '../utils/get-with-default'; export default class SessionService extends SimpleAuthSessionService { - /** - * Inject the router service - * - * @var {Service} - */ @service router; - - /** - * Inject the current user service - * - * @var {Service} - */ @service currentUser; - - /** - * Inject the current user service - * - * @var {Service} - */ @service fetch; /** @@ -69,23 +52,22 @@ export default class SessionService extends SimpleAuthSessionService { } const loaderNode = this.showLoader('Starting session...'); - this.isLoaderNodeOpen = true; - this.router - .transitionTo(this.redirectTo) - .finally(() => { - later( - this, - () => { - // remove node from body - document.body.removeChild(loaderNode); - this.isLoaderNodeOpen = false; - }, - 600 * 6 - ); - }) - .catch((error) => console.log(error)); + try { + await this.router.transitionTo(this.redirectTo); + later( + this, + () => { + // remove node from body + document.body.removeChild(loaderNode); + this.isLoaderNodeOpen = false; + }, + 600 * 6 + ); + } catch (error) { + this.notifications.serverError(error); + } } /** @@ -113,31 +95,28 @@ export default class SessionService extends SimpleAuthSessionService { * @param {Transition} transition * @void */ - promiseCurrentUser(transition = null) { + async promiseCurrentUser(transition = null) { const invalidateWithLoader = this.invalidateWithLoader.bind(this); - return new Promise((resolve, reject) => { - return this.currentUser - .promiseUser() - .then((user) => { - if (!user) { - if (transition !== null) { - transition.abort(); - } - - reject(invalidateWithLoader('Session authentication failed...')); - } - - resolve(user); - }) - .catch((error) => { - if (transition !== null) { - transition.abort(); - } - - reject(invalidateWithLoader(error.message ?? `Session authentication failed...`)); - }); - }); + try { + const user = await this.currentUser.promiseUser(); + if (!user) { + if (transition) { + transition.abort(); + } + + await invalidateWithLoader('Session authentication failed...'); + throw new Error('Session authentication failed...'); + } + + return user; + } catch (error) { + if (transition) { + transition.abort(); + } + await invalidateWithLoader(error.message ?? 'Session authentication failed...'); + throw error; + } } /** diff --git a/addon/services/theme.js b/addon/services/theme.js index 5cd7c1e..b14d76b 100644 --- a/addon/services/theme.js +++ b/addon/services/theme.js @@ -213,7 +213,7 @@ export default class ThemeService extends Service { * @void */ setEnvironment() { - const isSandbox = this.currentUser.getOption('sandbox') || false; + const isSandbox = this.currentUser.getOption('sandbox', false); if (isSandbox) { document.body.classList.add('sandbox-console'); diff --git a/addon/services/universe.js b/addon/services/universe.js index 93cf420..0b49281 100644 --- a/addon/services/universe.js +++ b/addon/services/universe.js @@ -1006,6 +1006,7 @@ export default class UniverseService extends Service.extend(Evented) { const inlineClass = this._getOption(options, 'inlineClass', null); const wrapperClass = this._getOption(options, 'wrapperClass', null); const overwriteWrapperClass = this._getOption(options, 'overwriteWrapperClass', false); + const id = this._getOption(options, 'id', dasherize(title)); // dasherize route segments if (typeof route === 'string') { @@ -1015,9 +1016,11 @@ export default class UniverseService extends Service.extend(Evented) { .join('.'); } - // todo: create menu item class + // @todo: create menu item class const menuItem = { + id, title, + text: title, route, icon, priority, diff --git a/addon/utils/get-model-save-permission.js b/addon/utils/get-model-save-permission.js new file mode 100644 index 0000000..c312713 --- /dev/null +++ b/addon/utils/get-model-save-permission.js @@ -0,0 +1,7 @@ +import getModelName from './get-model-name'; + +export default function getModelSavePermission(service, model) { + const modelName = getModelName(model); + const ability = model && model.isNew ? 'create' : 'update'; + return `${service} ${ability} ${modelName}`; +} diff --git a/app/utils/get-model-save-permission.js b/app/utils/get-model-save-permission.js new file mode 100644 index 0000000..1600a24 --- /dev/null +++ b/app/utils/get-model-save-permission.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/ember-core/utils/get-model-save-permission'; diff --git a/package.json b/package.json index 214097a..1f3d84a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fleetbase/ember-core", - "version": "0.2.16", + "version": "0.2.17", "description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.", "keywords": [ "fleetbase-core", diff --git a/tests/unit/utils/get-model-save-permission-test.js b/tests/unit/utils/get-model-save-permission-test.js new file mode 100644 index 0000000..e015fe3 --- /dev/null +++ b/tests/unit/utils/get-model-save-permission-test.js @@ -0,0 +1,10 @@ +import getModelSavePermission from 'dummy/utils/get-model-save-permission'; +import { module, test } from 'qunit'; + +module('Unit | Utility | get-model-save-permission', function () { + // TODO: Replace this with your real tests. + test('it works', function (assert) { + let result = getModelSavePermission(); + assert.ok(result); + }); +});