Skip to content
This repository has been archived by the owner on Nov 28, 2022. It is now read-only.

Commit

Permalink
🎨 synchronous settings service
Browse files Browse the repository at this point in the history
no issue
- adds `settings` service that acts as a proxy to the singular settings model with methods to fetch and reload, also prevents accidentally loading only some settings types which has caused problems in the past
- updates app boot, signin, and signup processes to fetch settings ensuring that any part of the app can grab settings synchronously if needed
- removes `timeZone` service, it's no longer needed as we can grab `settings.activeTimezone` directly
- replaces all store queries for the settings model with appropriate `settings` methods
- refactors `apps/*` routes/controllers, they had become a little convoluted with the way they were dealing with settings and the new service helped to clean that up
  • Loading branch information
kevinansfield authored and acburdine committed Mar 20, 2017
1 parent bcbc9c2 commit 2f56152
Show file tree
Hide file tree
Showing 24 changed files with 205 additions and 241 deletions.
13 changes: 4 additions & 9 deletions app/components/gh-datetime-input.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Component from 'ember-component';
import RSVP from 'rsvp';
import injectService from 'ember-service/inject';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import {formatDate} from 'ghost-admin/utils/date-formatting';
Expand All @@ -13,21 +12,17 @@ export default Component.extend(InvokeActionMixin, {
inputClass: null,
inputId: null,
inputName: null,
timeZone: injectService(),
settings: injectService(),

didReceiveAttrs() {
let promises = {
datetime: RSVP.resolve(this.get('datetime') || moment.utc()),
blogTimezone: RSVP.resolve(this.get('timeZone.blogTimezone'))
};
let datetime = this.get('datetime') || moment.utc();
let blogTimezone = this.get('settings.activeTimezone');

if (!this.get('update')) {
throw new Error(`You must provide an \`update\` action to \`{{${this.templateName}}}\`.`);
}

RSVP.hash(promises).then((hash) => {
this.set('datetime', formatDate(hash.datetime || moment.utc(), hash.blogTimezone));
});
this.set('datetime', formatDate(datetime || moment.utc(), blogTimezone));
},

focusOut() {
Expand Down
123 changes: 61 additions & 62 deletions app/components/gh-post-settings-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default Component.extend(SettingsMenuMixin, {
notifications: injectService(),
slugGenerator: injectService(),
session: injectService(),
timeZone: injectService(),
settings: injectService(),

slugValue: boundOneWay('model.slug'),
metaTitleScratch: alias('model.metaTitleScratch'),
Expand Down Expand Up @@ -227,75 +227,74 @@ export default Component.extend(SettingsMenuMixin, {
}

// The user inputs a date which he expects to be in his timezone. Therefore, from now on
// we have to work with the timezone offset which we get from the timeZone Service.
this.get('timeZone.blogTimezone').then((blogTimezone) => {
let newPublishedAt = parseDateString(userInput, blogTimezone);
let publishedAtUTC = moment.utc(this.get('model.publishedAtUTC'));
let errMessage = '';
let newPublishedAtUTC;

// Clear previous errors
this.get('model.errors').remove('post-setting-date');

// Validate new Published date
if (!newPublishedAt.isValid()) {
errMessage = 'Published Date must be a valid date with format: '
+ 'DD MMM YY @ HH:mm (e.g. 6 Dec 14 @ 15:00)';
}
// we have to work with the timezone offset which we get from the settings Service.
let blogTimezone = this.get('settings.activeTimezone');
let newPublishedAt = parseDateString(userInput, blogTimezone);
let publishedAtUTC = moment.utc(this.get('model.publishedAtUTC'));
let errMessage = '';
let newPublishedAtUTC;

// Clear previous errors
this.get('model.errors').remove('post-setting-date');

// Validate new Published date
if (!newPublishedAt.isValid()) {
errMessage = 'Published Date must be a valid date with format: '
+ 'DD MMM YY @ HH:mm (e.g. 6 Dec 14 @ 15:00)';
}

// Date is a valid date, so now make it UTC
newPublishedAtUTC = moment.utc(newPublishedAt);

if (newPublishedAtUTC.diff(moment.utc(new Date()), 'hours', true) > 0) {

// Date is a valid date, so now make it UTC
newPublishedAtUTC = moment.utc(newPublishedAt);

if (newPublishedAtUTC.diff(moment.utc(new Date()), 'hours', true) > 0) {

// We have to check that the time from now is not shorter than 2 minutes,
// otherwise we'll have issues with the serverside scheduling procedure
if (newPublishedAtUTC.diff(moment.utc(new Date()), 'minutes', true) < 2) {
errMessage = 'Must be at least 2 minutes from now.';
} else {
// in case the post is already published and the user sets the date
// afterwards to a future time, we stop here, and he has to unpublish
// his post first
if (this.get('model.isPublished')) {
errMessage = 'Your post is already published.';
// this is the indicator for the different save button layout
this.set('timeScheduled', false);
}
// everything fine, we can start the schedule workflow and change
// the save buttons according to it
this.set('timeScheduled', true);
// We have to check that the time from now is not shorter than 2 minutes,
// otherwise we'll have issues with the serverside scheduling procedure
if (newPublishedAtUTC.diff(moment.utc(new Date()), 'minutes', true) < 2) {
errMessage = 'Must be at least 2 minutes from now.';
} else {
// in case the post is already published and the user sets the date
// afterwards to a future time, we stop here, and he has to unpublish
// his post first
if (this.get('model.isPublished')) {
errMessage = 'Your post is already published.';
// this is the indicator for the different save button layout
this.set('timeScheduled', false);
}
// if the post is already scheduled and the user changes the date back into the
// past, we'll set the status of the post back to draft, so he can start all over
// again
} else if (this.get('model.isScheduled')) {
this.set('model.status', 'draft');
// everything fine, we can start the schedule workflow and change
// the save buttons according to it
this.set('timeScheduled', true);
}
// if the post is already scheduled and the user changes the date back into the
// past, we'll set the status of the post back to draft, so he can start all over
// again
} else if (this.get('model.isScheduled')) {
this.set('model.status', 'draft');
}

// If errors, notify and exit.
if (errMessage) {
this.get('model.errors').add('post-setting-date', errMessage);
return;
}
// If errors, notify and exit.
if (errMessage) {
this.get('model.errors').add('post-setting-date', errMessage);
return;
}

// Do nothing if the user didn't actually change the date
if (publishedAtUTC && publishedAtUTC.isSame(newPublishedAtUTC)) {
return;
}
// Do nothing if the user didn't actually change the date
if (publishedAtUTC && publishedAtUTC.isSame(newPublishedAtUTC)) {
return;
}

// Validation complete
this.set('model.publishedAtUTC', newPublishedAtUTC);
// Validation complete
this.set('model.publishedAtUTC', newPublishedAtUTC);

// If this is a new post. Don't save the model. Defer the save
// to the user pressing the save button
if (this.get('model.isNew')) {
return;
}
// If this is a new post. Don't save the model. Defer the save
// to the user pressing the save button
if (this.get('model.isNew')) {
return;
}

this.get('model').save().catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
});
this.get('model').save().catch((error) => {
this.showError(error);
this.get('model').rollbackAttributes();
});
},

Expand Down
11 changes: 1 addition & 10 deletions app/components/gh-posts-list-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import $ from 'jquery';
import {isBlank} from 'ember-utils';

// ember-cli-shims doesn't export these
const {Handlebars, ObjectProxy, PromiseProxyMixin} = Ember;

const ObjectPromiseProxy = ObjectProxy.extend(PromiseProxyMixin);
const {Handlebars} = Ember;

export default Component.extend({
tagName: 'li',
Expand All @@ -25,7 +23,6 @@ export default Component.extend({
isScheduled: equal('post.status', 'scheduled'),

ghostPaths: injectService(),
timeZone: injectService(),

authorName: computed('post.author.name', 'post.author.email', function () {
return this.get('post.author.name') || this.get('post.author.email');
Expand All @@ -41,12 +38,6 @@ export default Component.extend({
return htmlSafe(`background-image: url(${safeUrl})`);
}),

blogTimezone: computed('timeZone.blogTimezone', function () {
return ObjectPromiseProxy.create({
promise: this.get('timeZone.blogTimezone')
});
}),

// HACK: this is intentionally awful due to time constraints
// TODO: find a better way to get an excerpt! :)
subText: computed('post.{html,metaDescription}', function () {
Expand Down
5 changes: 3 additions & 2 deletions app/controllers/settings/apps/amp.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import Controller from 'ember-controller';
import injectService from 'ember-service/inject';
import {task} from 'ember-concurrency';
import {alias} from 'ember-computed';

export default Controller.extend({
notifications: injectService(),
settings: injectService(),

// will be set by route
settings: null,
model: alias('settings.amp'),

save: task(function* () {
let amp = this.get('model');
Expand Down
10 changes: 0 additions & 10 deletions app/controllers/settings/apps/index.js

This file was deleted.

6 changes: 3 additions & 3 deletions app/controllers/settings/apps/slack.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import {empty} from 'ember-computed';
import injectService from 'ember-service/inject';
import {task} from 'ember-concurrency';
import {isInvalidError} from 'ember-ajax/errors';
import {alias} from 'ember-computed';

export default Controller.extend({
ghostPaths: injectService(),
ajax: injectService(),
notifications: injectService(),
settings: injectService(),

// will be set by route
settings: null,

model: alias('settings.slack.firstObject'),
testNotificationDisabled: empty('model.url'),

save: task(function* () {
Expand Down
16 changes: 12 additions & 4 deletions app/controllers/setup/two.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import RSVP from 'rsvp';
import injectService from 'ember-service/inject';
import injectController from 'ember-controller/inject';
import {isInvalidError} from 'ember-ajax/errors';

import ValidationEngine from 'ghost-admin/mixins/validation-engine';

const {Promise} = RSVP;
Expand All @@ -24,6 +23,7 @@ export default Controller.extend(ValidationEngine, {
application: injectController(),
config: injectService(),
session: injectService(),
settings: injectService(),
ajax: injectService(),

// ValidationEngine settings
Expand Down Expand Up @@ -79,14 +79,22 @@ export default Controller.extend(ValidationEngine, {
return this.sendImage(result.users[0])
.then(() => {
this.toggleProperty('submitting');
return this.transitionToRoute('setup.three');

// fetch settings for synchronous access before transitioning
return this.get('settings').fetch().then(() => {
return this.transitionToRoute('setup.three');
});
}).catch((resp) => {
this.toggleProperty('submitting');
this.get('notifications').showAPIError(resp, {key: 'setup.blog-details'});
});
} else {
this.toggleProperty('submitting');
return this.transitionToRoute('setup.three');

// fetch settings for synchronous access before transitioning
return this.get('settings').fetch().then(() => {
return this.transitionToRoute('setup.three');
});
}
},

Expand Down Expand Up @@ -151,7 +159,7 @@ export default Controller.extend(ValidationEngine, {
this.get('hasValidated').addObjects(['blogTitle', 'session']);

return this.validate().then(() => {
return this.store.queryRecord('setting', {type: 'blog,theme,private'})
return this.get('settings').fetch()
.then((settings) => {
settings.set('title', blogTitle);

Expand Down
8 changes: 7 additions & 1 deletion app/controllers/signin.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default Controller.extend(ValidationEngine, {
ghostPaths: injectService(),
notifications: injectService(),
session: injectService(),
settings: injectService(),
torii: injectService(),

flowErrors: '',
Expand All @@ -27,9 +28,14 @@ export default Controller.extend(ValidationEngine, {

authenticate: task(function* (authStrategy, authentication) {
try {
return yield this.get('session')
let authResult = yield this.get('session')
.authenticate(authStrategy, ...authentication);

// fetch settings for synchronous access
yield this.get('settings').fetch();

return authResult;

} catch (error) {
if (error && error.errors) {
// we don't get back an ember-data/ember-ajax error object
Expand Down
2 changes: 2 additions & 0 deletions app/helpers/gh-format-time-scheduled.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {helper} from 'ember-helper';

// TODO: this isn't used currently - safe to delete?

export function timeToSchedule(params) {
if (!params || !params.length) {
return;
Expand Down
1 change: 0 additions & 1 deletion app/models/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ export default Model.extend(Comparable, ValidationEngine, {

config: injectService(),
ghostPaths: injectService(),
timeZone: injectService(),
clock: injectService(),

absoluteUrl: computed('url', 'ghostPaths.url', 'config.blogUrl', function () {
Expand Down
15 changes: 12 additions & 3 deletions app/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mi
import ShortcutsRoute from 'ghost-admin/mixins/shortcuts-route';
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
import windowProxy from 'ghost-admin/utils/window-proxy';
import RSVP from 'rsvp';

function K() {
return this;
Expand All @@ -31,6 +32,7 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
dropdown: injectService(),
lazyLoader: injectService(),
notifications: injectService(),
settings: injectService(),
upgradeNotification: injectService(),

beforeModel() {
Expand All @@ -55,13 +57,20 @@ export default Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
authenticator.onOnline();
}

// return the feature loading promise so that we block until settings
// are loaded in order for synchronous access everywhere
return this.get('feature').fetch().then(() => {
let featurePromise = this.get('feature').fetch().then(() => {
if (this.get('feature.nightShift')) {
return this._setAdminTheme();
}
});

let settingsPromise = this.get('settings').fetch();

// return the feature/settings load promises so that we block until
// they are loaded to enable synchronous access everywhere
return RSVP.all([
featurePromise,
settingsPromise
]);
}
},

Expand Down
Loading

0 comments on commit 2f56152

Please sign in to comment.