diff --git a/addons/api/addon/services/scope.js b/addons/api/addon/services/scope.js new file mode 100644 index 000000000..e9088e40e --- /dev/null +++ b/addons/api/addon/services/scope.js @@ -0,0 +1,111 @@ +import Service from '@ember/service'; +import { tracked } from '@glimmer/tracking'; +import { computed } from '@ember/object'; + +/** + * This module includes two parts: + * + * - `Scope` class: a data structure representing an org/project tuple + * - `ScopeService`: manages the scope and exposes it to the application + */ + +/** + * Represents a _scope_, which is defined as an organization/project tuple. + * This class is intended as a simple data structure and is used by the + * scope service (see below). + * Immutable. + */ +class Scope { + /** + * @type {?object} + * @private + */ + #org = null; + + /** + * @type {?object} + * @private + */ + #project = null; + + /** + * Initializes an instance of `Scope` with optional org and/or project. + * @param {?object} org + * @param {?object} project + */ + constructor(org = null, project = null) { + if (org) this.#org = org; + if (project) this.#project = project; + } + + /** + * Org for this scope. Immutable. + * @type {?object} + */ + get org() { + return this.#org; + } + + /** + * Project for this scope. If org is null, so is project. + * @type {?object} + */ + get project() { + return this.org ? this.#project : null; + } + + /** + * Returns the JSON serialization of this scope. + * @return {?object} + */ + toJSON() { + const { org, project } = this; + return JSON.stringify({ org, project }); + } +} + +export { Scope }; + +/** + * The scope service manages and persists the current scope. Validating against + * available organizations and projects is outside the scope (tee hee) of this + * service because it is used by the adapter mixin to generate API paths, which + * could lead to problems were this service to attempt to fetch data. + * + * For now, should an invalid scope arise, it is left to the user to refresh, + * since they are likely to encounter 404s. This is an acceptable tradeoff for + * an initial release because invalid scopes are considered uncommon. + */ +export default class ScopeService extends Service { + /** + * The currently selected scope. + * @type {?Scope} + */ + @tracked scope = null; + + /** + * When setting org, `service.org = {...}`, a new scope is instantiated, + * since scopes are immutable. + * @type {?object} + */ + @computed('scope.org') + get org() { + return this.scope ? this.scope.org : null; + } + set org(org) { + this.scope = new Scope(org, this.project); + } + + /** + * When setting project, `service.project = {...}`, a new scope is + * instantiated, since scopes are immutable. + * @type {?object} + */ + @computed('scope.project') + get project() { + return this.scope ? this.scope.project : null; + } + set project(project) { + this.scope = new Scope(this.org, project); + } +} diff --git a/addons/api/app/services/scope.js b/addons/api/app/services/scope.js new file mode 100644 index 000000000..6c4b4d6c8 --- /dev/null +++ b/addons/api/app/services/scope.js @@ -0,0 +1 @@ +export { default, Scope } from 'api/services/scope'; diff --git a/addons/api/tests/dummy/app/routes/application.js b/addons/api/tests/dummy/app/routes/application.js new file mode 100644 index 000000000..62946d66d --- /dev/null +++ b/addons/api/tests/dummy/app/routes/application.js @@ -0,0 +1,10 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; + +export default class ApplicationRoute extends Route { + @service scope; + + afterModel() { + //console.log(this.scope); + } +} diff --git a/addons/api/tests/dummy/app/templates/application.hbs b/addons/api/tests/dummy/app/templates/application.hbs index 5230580f8..f8bc38e7b 100644 --- a/addons/api/tests/dummy/app/templates/application.hbs +++ b/addons/api/tests/dummy/app/templates/application.hbs @@ -1,3 +1,3 @@