From d90ed28940792b92f7ef56dc77302de10b7c2171 Mon Sep 17 00:00:00 2001 From: Aileen Nowak Date: Tue, 14 Jun 2016 10:11:59 +0200 Subject: [PATCH] Renaming date properties to contain `UTC` closes TryGhost/Ghost#6985 - renames all date properties to end with `UTC`: - `publishedAt` in `post` model - `createdAt` in `post`, `role`, `subscriber`, `tag` and `user` model - `updatedAt` in `post`, `role`, `subscriber`, `tag` and `user` model - `unsubscribedAt` in `subscriber` model - `lastLogin` in `user` model - adds `attrs` transforms in matching serializers `post`, `tag` and `user` - new serializers files for `subscribers` and `role` to add `attr` transforms - adds unit tests for all serializers - use two variables in `post-settings-menu` controller to handle blog-timezone adjusted date as well as UTC date --- ghost/admin/app/components/gh-user-active.js | 6 ++-- ghost/admin/app/components/gh-user-invited.js | 6 ++-- .../app/controllers/post-settings-menu.js | 21 +++++++------- ghost/admin/app/controllers/posts.js | 14 ++++----- ghost/admin/app/controllers/subscribers.js | 4 +-- .../app/mixins/editor-base-controller.js | 8 ++--- ghost/admin/app/models/post.js | 12 ++++---- ghost/admin/app/models/role.js | 4 +-- ghost/admin/app/models/subscriber.js | 6 ++-- ghost/admin/app/models/tag.js | 4 +-- ghost/admin/app/models/user.js | 6 ++-- ghost/admin/app/serializers/post.js | 5 +++- ghost/admin/app/serializers/role.js | 8 +++++ ghost/admin/app/serializers/subscriber.js | 9 ++++++ ghost/admin/app/serializers/tag.js | 5 ++++ ghost/admin/app/serializers/user.js | 5 +++- ghost/admin/app/templates/-user-list-item.hbs | 2 +- ghost/admin/app/templates/editor/edit.hbs | 2 +- .../app/templates/post-settings-menu.hbs | 4 +-- ghost/admin/app/templates/posts.hbs | 6 ++-- ghost/admin/app/templates/team/index.hbs | 2 +- .../admin/tests/unit/serializers/post-test.js | 23 +++++++++++++++ .../admin/tests/unit/serializers/role-test.js | 23 +++++++++++++++ .../tests/unit/serializers/setting-test.js | 29 +++++++++++++++++++ .../tests/unit/serializers/subscriber-test.js | 23 +++++++++++++++ .../admin/tests/unit/serializers/tag-test.js | 23 +++++++++++++++ .../admin/tests/unit/serializers/user-test.js | 29 +++++++++++++++++++ 27 files changed, 235 insertions(+), 54 deletions(-) create mode 100644 ghost/admin/app/serializers/role.js create mode 100644 ghost/admin/app/serializers/subscriber.js create mode 100644 ghost/admin/tests/unit/serializers/post-test.js create mode 100644 ghost/admin/tests/unit/serializers/role-test.js create mode 100644 ghost/admin/tests/unit/serializers/setting-test.js create mode 100644 ghost/admin/tests/unit/serializers/subscriber-test.js create mode 100644 ghost/admin/tests/unit/serializers/tag-test.js create mode 100644 ghost/admin/tests/unit/serializers/user-test.js diff --git a/ghost/admin/app/components/gh-user-active.js b/ghost/admin/app/components/gh-user-active.js index d9a9792c85..0e33ead661 100644 --- a/ghost/admin/app/components/gh-user-active.js +++ b/ghost/admin/app/components/gh-user-active.js @@ -24,9 +24,9 @@ export default Component.extend({ return htmlSafe(`background-image: url(${url})`); }), - lastLogin: computed('user.lastLogin', function () { - let lastLogin = this.get('user.lastLogin'); + lastLoginUTC: computed('user.lastLoginUTC', function () { + let lastLoginUTC = this.get('user.lastLoginUTC'); - return lastLogin ? moment(lastLogin).fromNow() : '(Never)'; + return lastLoginUTC ? moment(lastLoginUTC).fromNow() : '(Never)'; }) }); diff --git a/ghost/admin/app/components/gh-user-invited.js b/ghost/admin/app/components/gh-user-invited.js index 24325ca3ce..469e4a24b8 100644 --- a/ghost/admin/app/components/gh-user-invited.js +++ b/ghost/admin/app/components/gh-user-invited.js @@ -14,10 +14,10 @@ export default Component.extend({ notifications: service(), - createdAt: computed('user.createdAt', function () { - let createdAt = this.get('user.createdAt'); + createdAtUTC: computed('user.createdAtUTC', function () { + let createdAtUTC = this.get('user.createdAtUTC'); - return createdAt ? moment(createdAt).fromNow() : ''; + return createdAtUTC ? moment(createdAtUTC).fromNow() : ''; }), actions: { diff --git a/ghost/admin/app/controllers/post-settings-menu.js b/ghost/admin/app/controllers/post-settings-menu.js index bcd51dbadb..4e6ef817a3 100644 --- a/ghost/admin/app/controllers/post-settings-menu.js +++ b/ghost/admin/app/controllers/post-settings-menu.js @@ -290,11 +290,11 @@ export default Controller.extend(SettingsMenuMixin, { * Action sent by post settings menu view. * (#1351) */ - setPublishedAt(userInput) { + setPublishedAtUTC(userInput) { if (!userInput) { - // Clear out the publishedAt field for a draft + // Clear out the publishedAtUTC field for a draft if (this.get('model.isDraft')) { - this.set('model.publishedAt', null); + this.set('model.publishedAtUTC', null); } return; } @@ -303,8 +303,9 @@ export default Controller.extend(SettingsMenuMixin, { // 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 publishedAt = moment.utc(this.get('model.publishedAt')); + let publishedAtUTC = moment.utc(this.get('model.publishedAtUTC')); let errMessage = ''; + let newPublishedAtUTC; // Clear previous errors this.get('model.errors').remove('post-setting-date'); @@ -316,13 +317,13 @@ export default Controller.extend(SettingsMenuMixin, { } // Date is a valid date, so now make it UTC - newPublishedAt = moment.utc(newPublishedAt); + newPublishedAtUTC = moment.utc(newPublishedAt); - if (newPublishedAt.diff(moment.utc(new Date()), 'hours', true) > 0) { + 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 (newPublishedAt.diff(moment.utc(new Date()), 'minutes', true) < 2) { + 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 @@ -351,12 +352,12 @@ export default Controller.extend(SettingsMenuMixin, { } // Do nothing if the user didn't actually change the date - if (publishedAt && publishedAt.isSame(newPublishedAt)) { + if (publishedAtUTC && publishedAtUTC.isSame(newPublishedAtUTC)) { return; } // Validation complete - this.set('model.publishedAt', newPublishedAt); + 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 @@ -440,7 +441,7 @@ export default Controller.extend(SettingsMenuMixin, { }, resetPubDate() { - this.set('publishedAtValue', ''); + this.set('publishedAtUTCValue', ''); }, closeNavMenu() { diff --git a/ghost/admin/app/controllers/posts.js b/ghost/admin/app/controllers/posts.js index feb49d0c82..9952aebbb2 100644 --- a/ghost/admin/app/controllers/posts.js +++ b/ghost/admin/app/controllers/posts.js @@ -10,12 +10,12 @@ const {equal} = computed; // a custom sort function is needed in order to sort the posts list the same way the server would: // status: scheduled, draft, published -// publishedAt: DESC -// updatedAt: DESC +// publishedAtUTC: DESC +// updatedAtUTC: DESC // id: DESC function comparator(item1, item2) { - let updated1 = item1.get('updatedAt'); - let updated2 = item2.get('updatedAt'); + let updated1 = item1.get('updatedAtUTC'); + let updated2 = item2.get('updatedAtUTC'); let idResult, publishedAtResult, statusResult, @@ -85,8 +85,8 @@ function statusCompare(item1, item2) { } function publishedAtCompare(item1, item2) { - let published1 = item1.get('publishedAt'); - let published2 = item2.get('publishedAt'); + let published1 = item1.get('publishedAtUTC'); + let published2 = item2.get('publishedAtUTC'); if (!published1 && !published2) { return 0; @@ -112,7 +112,7 @@ export default Controller.extend({ postListFocused: equal('keyboardFocus', 'postList'), postContentFocused: equal('keyboardFocus', 'postContent'), - sortedPosts: computed('model.@each.status', 'model.@each.publishedAt', 'model.@each.isNew', 'model.@each.updatedAt', function () { + sortedPosts: computed('model.@each.status', 'model.@each.publishedAtUTC', 'model.@each.isNew', 'model.@each.updatedAtUTC', function () { let postsArray = this.get('model').toArray(); return postsArray.sort(comparator); diff --git a/ghost/admin/app/controllers/subscribers.js b/ghost/admin/app/controllers/subscribers.js index fe2750c2ef..fd4b9b5019 100644 --- a/ghost/admin/app/controllers/subscribers.js +++ b/ghost/admin/app/controllers/subscribers.js @@ -57,7 +57,7 @@ export default Controller.extend(PaginationMixin, { ascending: direction === 'asc' }, { label: 'Subscription Date', - valuePath: 'createdAt', + valuePath: 'createdAtUTC', format(value) { return value.format('MMMM DD, YYYY'); }, @@ -111,7 +111,7 @@ export default Controller.extend(PaginationMixin, { if (column.sorted) { this.setProperties({ - order: column.get('valuePath').trim().underscore(), + order: column.get('valuePath').trim().replace(/UTC$/, '').underscore(), direction: column.ascending ? 'asc' : 'desc' }); table.setRows([]); diff --git a/ghost/admin/app/mixins/editor-base-controller.js b/ghost/admin/app/mixins/editor-base-controller.js index d74c47914f..55e472c5c8 100644 --- a/ghost/admin/app/mixins/editor-base-controller.js +++ b/ghost/admin/app/mixins/editor-base-controller.js @@ -86,9 +86,9 @@ export default Mixin.create({ // countdown timer to show the time left until publish time for a scheduled post // starts 15 minutes before scheduled time - scheduleCountdown: computed('model.status', 'clock.second', 'model.publishedAt', 'model.timeScheduled', function () { + scheduleCountdown: computed('model.status', 'clock.second', 'model.publishedAtUTC', 'model.timeScheduled', function () { let status = this.get('model.status'); - let publishTime = this.get('model.publishedAt'); + let publishTime = this.get('model.publishedAtUTC'); this.get('clock.second'); @@ -104,9 +104,9 @@ export default Mixin.create({ // dropdown menu, the save button gets the status 'isDangerous' to turn red and will only have the option to unschedule the post // 2. when the scheduled time is reached we use a helper 'scheduledWillPublish' to pretend we're already dealing with a published post. // This will take effect on the save button menu, the workflows and existing conditionals. - statusFreeze: computed('model.status', 'clock.second', 'model.publishedAt', 'model.timeScheduled', function () { + statusFreeze: computed('model.status', 'clock.second', 'model.publishedAtUTC', 'model.timeScheduled', function () { let status = this.get('model.status'); - let publishTime = this.get('model.publishedAt'); + let publishTime = this.get('model.publishedAtUTC'); this.get('clock.second'); diff --git a/ghost/admin/app/models/post.js b/ghost/admin/app/models/post.js index 1920e4ea9c..804cdafec5 100644 --- a/ghost/admin/app/models/post.js +++ b/ghost/admin/app/models/post.js @@ -28,11 +28,11 @@ export default Model.extend(ValidationEngine, { metaDescription: attr('string'), author: belongsTo('user', {async: true}), authorId: attr('number'), - updatedAt: attr('moment-utc'), + updatedAtUTC: attr('moment-utc'), updatedBy: attr(), - publishedAt: attr('moment-utc'), + publishedAtUTC: attr('moment-utc'), publishedBy: belongsTo('user', {async: true}), - createdAt: attr('moment-utc'), + createdAtUTC: attr('moment-utc'), createdBy: attr(), tags: hasMany('tag', { embedded: 'always', @@ -74,11 +74,11 @@ export default Model.extend(ValidationEngine, { // TODO: move this into gh-posts-list-item component // Checks every second, if we reached the scheduled date - timeScheduled: computed('publishedAt', 'clock.second', function () { - let publishedAt = this.get('publishedAt') || moment.utc(new Date()); + timeScheduled: computed('publishedAtUTC', 'clock.second', function () { + let publishedAtUTC = this.get('publishedAtUTC') || moment.utc(new Date()); this.get('clock.second'); - return publishedAt.diff(moment.utc(new Date()), 'hours', true) > 0 ? true : false; + return publishedAtUTC.diff(moment.utc(new Date()), 'hours', true) > 0 ? true : false; }), // remove client-generated tags, which have `id: null`. diff --git a/ghost/admin/app/models/role.js b/ghost/admin/app/models/role.js index caa97ba1bb..fa263e0c1e 100644 --- a/ghost/admin/app/models/role.js +++ b/ghost/admin/app/models/role.js @@ -9,8 +9,8 @@ export default Model.extend({ uuid: attr('string'), name: attr('string'), description: attr('string'), - createdAt: attr('moment-utc'), - updatedAt: attr('moment-utc'), + createdAtUTC: attr('moment-utc'), + updatedAtUTC: attr('moment-utc'), createdBy: attr(), updatedBy: attr(), diff --git a/ghost/admin/app/models/subscriber.js b/ghost/admin/app/models/subscriber.js index 090183b19c..4b64c582e2 100644 --- a/ghost/admin/app/models/subscriber.js +++ b/ghost/admin/app/models/subscriber.js @@ -13,9 +13,9 @@ export default Model.extend(ValidationEngine, { subscribedUrl: attr('string'), subscribedReferrer: attr('string'), unsubscribedUrl: attr('string'), - unsubscribedAt: attr('moment-utc'), - createdAt: attr('moment-utc'), - updatedAt: attr('moment-utc'), + unsubscribedAtUTC: attr('moment-utc'), + createdAtUTC: attr('moment-utc'), + updatedAtUTC: attr('moment-utc'), createdBy: attr('number'), updatedBy: attr('number'), diff --git a/ghost/admin/app/models/tag.js b/ghost/admin/app/models/tag.js index d568a8b6d8..820de1ea35 100644 --- a/ghost/admin/app/models/tag.js +++ b/ghost/admin/app/models/tag.js @@ -24,8 +24,8 @@ export default Model.extend(ValidationEngine, { metaDescription: attr('string'), image: attr('string'), visibility: attr('string', {defaultValue: 'public'}), - createdAt: attr('moment-utc'), - updatedAt: attr('moment-utc'), + createdAtUTC: attr('moment-utc'), + updatedAtUTC: attr('moment-utc'), createdBy: attr(), updatedBy: attr(), count: attr('raw'), diff --git a/ghost/admin/app/models/user.js b/ghost/admin/app/models/user.js index 7537d35f9b..dc5070e36d 100644 --- a/ghost/admin/app/models/user.js +++ b/ghost/admin/app/models/user.js @@ -28,10 +28,10 @@ export default Model.extend(ValidationEngine, { language: attr('string', {defaultValue: 'en_US'}), metaTitle: attr('string'), metaDescription: attr('string'), - lastLogin: attr('moment-utc'), - createdAt: attr('moment-utc'), + lastLoginUTC: attr('moment-utc'), + createdAtUTC: attr('moment-utc'), createdBy: attr('number'), - updatedAt: attr('moment-utc'), + updatedAtUTC: attr('moment-utc'), updatedBy: attr('number'), roles: hasMany('role', { embedded: 'always', diff --git a/ghost/admin/app/serializers/post.js b/ghost/admin/app/serializers/post.js index c12309932b..a99720b173 100644 --- a/ghost/admin/app/serializers/post.js +++ b/ghost/admin/app/serializers/post.js @@ -10,7 +10,10 @@ const { export default ApplicationSerializer.extend(EmbeddedRecordsMixin, { // settings for the EmbeddedRecordsMixin. attrs: { - tags: {embedded: 'always'} + tags: {embedded: 'always'}, + publishedAtUTC: {key: 'published_at'}, + createdAtUTC: {key: 'created_at'}, + updatedAtUTC: {key: 'updated_at'} }, normalize(model, hash, prop) { diff --git a/ghost/admin/app/serializers/role.js b/ghost/admin/app/serializers/role.js new file mode 100644 index 0000000000..7eee8e40a4 --- /dev/null +++ b/ghost/admin/app/serializers/role.js @@ -0,0 +1,8 @@ +import ApplicationSerializer from 'ghost-admin/serializers/application'; + +export default ApplicationSerializer.extend({ + attrs: { + createdAtUTC: {key: 'created_at'}, + updatedAtUTC: {key: 'updated_at'} + } +}); diff --git a/ghost/admin/app/serializers/subscriber.js b/ghost/admin/app/serializers/subscriber.js new file mode 100644 index 0000000000..531c02f84e --- /dev/null +++ b/ghost/admin/app/serializers/subscriber.js @@ -0,0 +1,9 @@ +import ApplicationSerializer from 'ghost-admin/serializers/application'; + +export default ApplicationSerializer.extend({ + attrs: { + unsubscribedAtUTC: {key: 'unsubscribed_at'}, + createdAtUTC: {key: 'created_at'}, + updatedAtUTC: {key: 'updated_at'} + } +}); diff --git a/ghost/admin/app/serializers/tag.js b/ghost/admin/app/serializers/tag.js index cce7be6d1d..0452b31cfe 100644 --- a/ghost/admin/app/serializers/tag.js +++ b/ghost/admin/app/serializers/tag.js @@ -7,6 +7,11 @@ const { } = Ember; export default ApplicationSerializer.extend({ + attrs: { + createdAtUTC: {key: 'created_at'}, + updatedAtUTC: {key: 'updated_at'} + }, + serializeIntoHash(hash, type, record, options) { options = options || {}; options.includeId = true; diff --git a/ghost/admin/app/serializers/user.js b/ghost/admin/app/serializers/user.js index db9e556410..80433d1e79 100644 --- a/ghost/admin/app/serializers/user.js +++ b/ghost/admin/app/serializers/user.js @@ -8,7 +8,10 @@ const { export default ApplicationSerializer.extend(EmbeddedRecordsMixin, { attrs: { - roles: {embedded: 'always'} + roles: {embedded: 'always'}, + lastLoginUTC: {key: 'last_login'}, + createdAtUTC: {key: 'created_at'}, + updatedAtUTC: {key: 'updated_at'} }, extractSingle(store, primaryType, payload) { diff --git a/ghost/admin/app/templates/-user-list-item.hbs b/ghost/admin/app/templates/-user-list-item.hbs index 7b5eda3b14..53cbe03f84 100644 --- a/ghost/admin/app/templates/-user-list-item.hbs +++ b/ghost/admin/app/templates/-user-list-item.hbs @@ -7,7 +7,7 @@ {{user.name}}
- Last seen: {{component.lastLogin}} + Last seen: {{component.lastLoginUTC}}