├── app
├── .gitkeep
├── models
│ ├── contentful.js
│ └── contentful-asset.js
├── adapters
│ └── contentful.js
└── serializers
│ └── contentful.js
├── addon
├── .gitkeep
├── models
│ ├── contentful.js
│ └── contentful-asset.js
├── serializers
│ └── contentful.js
└── adapters
│ └── contentful.js
├── vendor
└── .gitkeep
├── tests
├── unit
│ ├── .gitkeep
│ ├── models
│ │ ├── contentful-test.js
│ │ └── contentful-asset-test.js
│ ├── adapters
│ │ └── contentful-test.js
│ └── serializers
│ │ └── contentful-test.js
├── integration
│ └── .gitkeep
├── dummy
│ ├── app
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ ├── .gitkeep
│ │ │ ├── author.js
│ │ │ └── post.js
│ │ ├── routes
│ │ │ ├── .gitkeep
│ │ │ ├── posts.js
│ │ │ └── post.js
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── controllers
│ │ │ └── .gitkeep
│ │ ├── templates
│ │ │ ├── components
│ │ │ │ └── .gitkeep
│ │ │ ├── posts.hbs
│ │ │ ├── post.hbs
│ │ │ └── application.hbs
│ │ ├── resolver.js
│ │ ├── adapters
│ │ │ └── application.js
│ │ ├── app.js
│ │ ├── index.html
│ │ └── router.js
│ ├── public
│ │ ├── robots.txt
│ │ └── crossdomain.xml
│ └── config
│ │ ├── targets.js
│ │ └── environment.js
├── helpers
│ ├── destroy-app.js
│ ├── resolver.js
│ ├── start-app.js
│ └── module-for-acceptance.js
├── test-helper.js
├── .jshintrc
└── index.html
├── .watchmanconfig
├── .bowerrc
├── bower.json
├── index.js
├── blueprints
├── .jshintrc
└── ember-data-contentful
│ ├── files
│ └── app
│ │ └── adapters
│ │ └── application.js
│ └── index.js
├── config
├── environment.js
├── release.js
└── ember-try.js
├── ember-cli-build.js
├── .ember-cli
├── .npmignore
├── .editorconfig
├── .gitignore
├── testem.js
├── .jshintrc
├── LICENSE.md
├── .eslintrc.js
├── .travis.yml
├── package.json
└── README.md
/app/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addon/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-data-contentful",
3 | "dependencies": {
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/app/models/contentful.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-data-contentful/models/contentful';
2 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | name: 'ember-data-contentful'
5 | };
6 |
--------------------------------------------------------------------------------
/app/adapters/contentful.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-data-contentful/adapters/contentful';
2 |
--------------------------------------------------------------------------------
/blueprints/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "console"
4 | ],
5 | "strict": false
6 | }
7 |
--------------------------------------------------------------------------------
/app/models/contentful-asset.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-data-contentful/models/contentful-asset';
2 |
--------------------------------------------------------------------------------
/app/serializers/contentful.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-data-contentful/serializers/contentful';
2 |
--------------------------------------------------------------------------------
/tests/dummy/app/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 |
3 | export default Resolver;
4 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(/* environment, appConfig */) {
4 | return { };
5 | };
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/adapters/application.js:
--------------------------------------------------------------------------------
1 | import ContentfulAdapter from 'ember-data-contentful/adapters/contentful';
2 |
3 | export default ContentfulAdapter.extend({});
4 |
--------------------------------------------------------------------------------
/tests/helpers/destroy-app.js:
--------------------------------------------------------------------------------
1 | import { run } from '@ember/runloop';
2 |
3 | export default function destroyApp(application) {
4 | run(application, 'destroy');
5 | }
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/posts.hbs:
--------------------------------------------------------------------------------
1 |
All Posts
2 |
3 | {{#each model as |post|}}
4 | - {{link-to post.title 'post' post}}
5 | {{/each}}
6 |
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/posts.js:
--------------------------------------------------------------------------------
1 | import Route from '@ember/routing/route';
2 |
3 | export default Route.extend({
4 | model() {
5 | return this.store.findAll('post');
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/blueprints/ember-data-contentful/files/app/adapters/application.js:
--------------------------------------------------------------------------------
1 | import ContentfulAdapter from 'ember-data-contentful/adapters/contentful';
2 |
3 | export default ContentfulAdapter.extend({});
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/author.js:
--------------------------------------------------------------------------------
1 | import Contentful from 'ember-data-contentful/models/contentful';
2 | import attr from 'ember-data/attr';
3 |
4 | export default Contentful.extend({
5 | name: attr('string')
6 | });
7 |
--------------------------------------------------------------------------------
/addon/models/contentful.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 | import attr from 'ember-data/attr';
3 |
4 | export default DS.Model.extend({
5 | contentType: attr('string'),
6 | createdAt: attr('date'),
7 | updatedAt: attr('date')
8 | });
9 |
--------------------------------------------------------------------------------
/addon/models/contentful-asset.js:
--------------------------------------------------------------------------------
1 | import Contentful from './contentful';
2 | import attr from 'ember-data/attr';
3 |
4 | export default Contentful.extend({
5 | file: attr(),
6 | title: attr('string'),
7 | description: attr('string')
8 | });
9 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
4 |
5 | module.exports = function(defaults) {
6 | let app = new EmberAddon(defaults);
7 |
8 | return app.toTree();
9 | };
10 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import Application from '../app';
2 | import config from '../config/environment';
3 | import { setApplication } from '@ember/test-helpers';
4 | import { start } from 'ember-qunit';
5 |
6 | setApplication(Application.create(config.APP));
7 |
8 | start();
9 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/tests/helpers/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from '../../resolver';
2 | import config from '../../config/environment';
3 |
4 | const resolver = Resolver.create();
5 |
6 | resolver.namespace = {
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix
9 | };
10 |
11 | export default resolver;
12 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/post.hbs:
--------------------------------------------------------------------------------
1 | {{model.title}}
2 | by {{#each model.author as |author|}}{{author.name}}{{/each}}
3 |
4 | {{#if model.featuredImage}}
5 |
6 | {{/if}}
7 |
8 |
9 | {{md-text text=model.body}}
10 |
11 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /bower_components
2 | /config/ember-try.js
3 | /dist
4 | /tests
5 | /tmp
6 | **/.gitkeep
7 | .bowerrc
8 | .editorconfig
9 | .ember-cli
10 | .eslintrc.js
11 | .gitignore
12 | .watchmanconfig
13 | .travis.yml
14 | bower.json
15 | ember-cli-build.js
16 | testem.js
17 |
18 | # ember-try
19 | .node_modules.ember-try/
20 | bower.json.ember-try
21 | package.json.ember-try
22 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/post.js:
--------------------------------------------------------------------------------
1 | import Route from '@ember/routing/route';
2 | import { get } from '@ember/object';
3 |
4 | export default Route.extend({
5 | model(params) {
6 | return this.store.queryRecord('post', {
7 | 'fields.slug': params.post_slug
8 | });
9 | },
10 |
11 | serialize(model) {
12 | return { post_slug: get(model, 'slug') };
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/tests/unit/models/contentful-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForModel, test } from 'ember-qunit';
2 |
3 | moduleForModel('contentful', 'Unit | Model | contentful', {
4 | // Specify the other units that are required for this test.
5 | needs: []
6 | });
7 |
8 | test('it exists', function(assert) {
9 | let model = this.subject();
10 | // let store = this.store();
11 | assert.ok(!!model);
12 | });
13 |
--------------------------------------------------------------------------------
/tests/dummy/config/targets.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const browsers = [
4 | 'last 1 Chrome versions',
5 | 'last 1 Firefox versions',
6 | 'last 1 Safari versions'
7 | ];
8 |
9 | const isCI = !!process.env.CI;
10 | const isProduction = process.env.EMBER_ENV === 'production';
11 |
12 | if (isCI || isProduction) {
13 | browsers.push('ie 11');
14 | }
15 |
16 | module.exports = {
17 | browsers
18 | };
19 |
--------------------------------------------------------------------------------
/tests/unit/models/contentful-asset-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForModel, test } from 'ember-qunit';
2 |
3 | moduleForModel('contentful-asset', 'Unit | Model | contentful asset', {
4 | // Specify the other units that are required for this test.
5 | needs: []
6 | });
7 |
8 | test('it exists', function(assert) {
9 | let model = this.subject();
10 | // let store = this.store();
11 | assert.ok(!!model);
12 | });
13 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Application from '@ember/application';
2 | import Resolver from './resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from './config/environment';
5 |
6 | const App = Application.extend({
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix,
9 | Resolver
10 | });
11 |
12 | loadInitializers(App, config.modulePrefix);
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/post.js:
--------------------------------------------------------------------------------
1 | import Contentful from 'ember-data-contentful/models/contentful';
2 | import attr from 'ember-data/attr';
3 | import { belongsTo, hasMany } from 'ember-data/relationships';
4 |
5 | export default Contentful.extend({
6 | author: hasMany('author'),
7 | body: attr('string'),
8 | date: attr('date'),
9 | featuredImage: belongsTo('contentful-asset'),
10 | slug: attr('string'),
11 | title: attr('string')
12 | });
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.hbs]
17 | insert_final_newline = false
18 |
19 | [*.{diff,md}]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # misc
12 | /.sass-cache
13 | /connect.lock
14 | /coverage/*
15 | /libpeerconnection.log
16 | npm-debug.log*
17 | yarn-error.log
18 | testem.log
19 | .DS_Store
20 |
21 | /.vscode
22 |
23 | # ember-try
24 | .node_modules.ember-try/
25 | bower.json.ember-try
26 | package.json.ember-try
27 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ember Data Contentful
4 |
9 |
10 | {{outlet}}
11 |
14 |
15 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | test_page: 'tests/index.html?hidepassed',
3 | disable_watching: true,
4 | launch_in_ci: [
5 | 'Chrome'
6 | ],
7 | launch_in_dev: [
8 | 'Chrome'
9 | ],
10 | browser_args: {
11 | Chrome: {
12 | mode: 'ci',
13 | args: [
14 | // --no-sandbox is needed when running Chrome inside a container
15 | process.env.TRAVIS ? '--no-sandbox' : null,
16 |
17 | '--disable-gpu',
18 | '--headless',
19 | '--remote-debugging-port=0',
20 | '--window-size=1440,900'
21 | ].filter(Boolean)
22 | }
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/tests/helpers/start-app.js:
--------------------------------------------------------------------------------
1 | import { merge } from '@ember/polyfills';
2 | import { run } from '@ember/runloop';
3 | import Application from '../../app';
4 | import config from '../../config/environment';
5 |
6 | export default function startApp(attrs) {
7 | let application;
8 |
9 | let attributes = merge({}, config.APP);
10 | attributes = merge(attributes, attrs); // use defaults, but you can override;
11 |
12 | run(() => {
13 | application = Application.create(attributes);
14 | application.setupForTesting();
15 | application.injectTestHelpers();
16 | });
17 |
18 | return application;
19 | }
20 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "-Promise"
6 | ],
7 | "browser": true,
8 | "boss": true,
9 | "curly": true,
10 | "debug": false,
11 | "devel": true,
12 | "eqeqeq": true,
13 | "evil": true,
14 | "forin": false,
15 | "immed": false,
16 | "laxbreak": false,
17 | "newcap": true,
18 | "noarg": true,
19 | "noempty": false,
20 | "nonew": false,
21 | "nomen": false,
22 | "onevar": false,
23 | "plusplus": false,
24 | "regexp": false,
25 | "undef": true,
26 | "sub": true,
27 | "strict": false,
28 | "white": false,
29 | "eqnull": true,
30 | "esversion": 6,
31 | "unused": true
32 | }
33 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/blueprints/ember-data-contentful/index.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 |
3 | var EOL = require('os').EOL;
4 | var chalk = require('chalk');
5 |
6 | module.exports = {
7 | description: 'ember-data-contentful',
8 |
9 | normalizeEntityName: function() {
10 | },
11 |
12 | afterInstall: function() {
13 | return this.addAddonToProject('ember-fetch', '3.4.3')
14 | .then(function () {
15 | var output = EOL;
16 | output += chalk.yellow('ember-data-contentful') + ' has been installed. Please configure your contentful space and accessTokens in ' + chalk.green('config/environment.js') + EOL;
17 | console.log(output);
18 | });
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/config/release.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true */
2 | // var RSVP = require('rsvp');
3 |
4 | // For details on each option run `ember help release`
5 | module.exports = {
6 | // local: true,
7 | // remote: 'some_remote',
8 | // annotation: "Release %@",
9 | // message: "Bumped version to %@",
10 | // manifest: [ 'package.json', 'bower.json', 'someconfig.json' ],
11 | // publish: true,
12 | // strategy: 'date',
13 | // format: 'YYYY-MM-DD',
14 | // timezone: 'America/Los_Angeles',
15 | //
16 | // beforeCommit: function(project, versions) {
17 | // return new RSVP.Promise(function(resolve, reject) {
18 | // // Do custom things here...
19 | // });
20 | // }
21 | };
22 |
--------------------------------------------------------------------------------
/tests/helpers/module-for-acceptance.js:
--------------------------------------------------------------------------------
1 | import { module } from 'qunit';
2 | import { Promise } from 'rsvp';
3 | import startApp from '../helpers/start-app';
4 | import destroyApp from '../helpers/destroy-app';
5 |
6 | export default function(name, options = {}) {
7 | module(name, {
8 | beforeEach() {
9 | this.application = startApp();
10 |
11 | if (options.beforeEach) {
12 | return options.beforeEach.apply(this, arguments);
13 | }
14 | },
15 |
16 | afterEach() {
17 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
18 | return Promise.resolve(afterEach).then(() => destroyApp(this.application));
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
21 |
22 |
23 | {{content-for "head-footer"}}
24 |
25 |
26 | {{content-for "body"}}
27 |
28 |
29 |
30 |
31 | {{content-for "body-footer"}}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import EmberRouter from '@ember/routing/router';
2 | import { inject as service } from '@ember/service';
3 | import { scheduleOnce } from '@ember/runloop';
4 | import { get } from '@ember/object';
5 | import config from './config/environment';
6 |
7 | const Router = EmberRouter.extend({
8 | location: config.locationType,
9 | rootURL: config.rootURL,
10 | metrics: service(),
11 |
12 | didTransition() {
13 | this._super(...arguments);
14 | if (typeof FastBoot === 'undefined') {
15 | this._trackPage();
16 | }
17 | },
18 |
19 | _trackPage() {
20 | scheduleOnce('afterRender', this, () => {
21 | const page = document.location.pathname;
22 | const title = this.getWithDefault('currentRouteName', 'unknown');
23 |
24 | get(this, 'metrics').trackPage({ page, title });
25 | });
26 | }
27 | });
28 |
29 | Router.map(function() {
30 | this.route('posts', { path: '/' });
31 | this.route('post', { path: 'posts/:post_slug'});
32 | });
33 |
34 | export default Router;
35 |
--------------------------------------------------------------------------------
/tests/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "location",
6 | "setTimeout",
7 | "$",
8 | "-Promise",
9 | "define",
10 | "console",
11 | "visit",
12 | "exists",
13 | "fillIn",
14 | "click",
15 | "keyEvent",
16 | "triggerEvent",
17 | "find",
18 | "findWithAssert",
19 | "wait",
20 | "DS",
21 | "andThen",
22 | "currentURL",
23 | "currentPath",
24 | "currentRouteName"
25 | ],
26 | "node": false,
27 | "browser": false,
28 | "boss": true,
29 | "curly": true,
30 | "debug": false,
31 | "devel": false,
32 | "eqeqeq": true,
33 | "evil": true,
34 | "forin": false,
35 | "immed": false,
36 | "laxbreak": false,
37 | "newcap": true,
38 | "noarg": true,
39 | "noempty": false,
40 | "nonew": false,
41 | "nomen": false,
42 | "onevar": false,
43 | "plusplus": false,
44 | "regexp": false,
45 | "undef": true,
46 | "sub": true,
47 | "strict": false,
48 | "white": false,
49 | "eqnull": true,
50 | "esversion": 6,
51 | "unused": true
52 | }
53 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parserOptions: {
4 | ecmaVersion: 2017,
5 | sourceType: 'module'
6 | },
7 | plugins: [
8 | 'ember'
9 | ],
10 | extends: [
11 | 'eslint:recommended',
12 | 'plugin:ember/recommended'
13 | ],
14 | env: {
15 | browser: true
16 | },
17 | rules: {
18 | },
19 | overrides: [
20 | // node files
21 | {
22 | files: [
23 | 'ember-cli-build.js',
24 | 'index.js',
25 | 'testem.js',
26 | 'config/**/*.js',
27 | 'tests/dummy/config/**/*.js'
28 | ],
29 | excludedFiles: [
30 | 'addon/**',
31 | 'addon-test-support/**',
32 | 'app/**',
33 | 'tests/dummy/app/**'
34 | ],
35 | parserOptions: {
36 | sourceType: 'script',
37 | ecmaVersion: 2015
38 | },
39 | env: {
40 | browser: false,
41 | node: true
42 | },
43 | plugins: ['node'],
44 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
45 | // add your custom rules and overrides for node files here
46 | })
47 | }
48 | ]
49 | };
50 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for "head"}}
11 | {{content-for "test-head"}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 | {{content-for "test-head-footer"}}
19 |
20 |
21 | {{content-for "body"}}
22 | {{content-for "test-body"}}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{content-for "body-footer"}}
31 | {{content-for "test-body-footer"}}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | # we recommend testing addons with the same minimum supported node version as Ember CLI
5 | # so that your addon works for all apps
6 | - "8"
7 |
8 | sudo: false
9 | dist: trusty
10 |
11 | addons:
12 | chrome: stable
13 |
14 | cache:
15 | yarn: true
16 |
17 | env:
18 | global:
19 | # See https://git.io/vdao3 for details.
20 | - JOBS=1
21 | matrix:
22 | # we recommend new addons test the current and previous LTS
23 | # as well as latest stable release (bonus points to beta/canary)
24 | - EMBER_TRY_SCENARIO=ember-lts-2.12
25 | - EMBER_TRY_SCENARIO=ember-lts-2.16
26 | - EMBER_TRY_SCENARIO=ember-lts-2.18
27 | - EMBER_TRY_SCENARIO=ember-release
28 | - EMBER_TRY_SCENARIO=ember-beta
29 | - EMBER_TRY_SCENARIO=ember-canary
30 | - EMBER_TRY_SCENARIO=ember-default
31 |
32 | matrix:
33 | fast_finish: true
34 | allow_failures:
35 | - env: EMBER_TRY_SCENARIO=ember-canary
36 |
37 | before_install:
38 | - curl -o- -L https://yarnpkg.com/install.sh | bash
39 | - export PATH=$HOME/.yarn/bin:$PATH
40 |
41 | install:
42 | - yarn install --no-lockfile --non-interactive
43 |
44 | script:
45 | - yarn lint:js
46 | # Usually, it's ok to finish the test scenario without reverting
47 | # to the addon's original dependency state, skipping "cleanup".
48 | - node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO --skip-cleanup
49 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const getChannelURL = require('ember-source-channel-url');
4 |
5 | module.exports = function() {
6 | return Promise.all([
7 | getChannelURL('release'),
8 | getChannelURL('beta'),
9 | getChannelURL('canary')
10 | ]).then((urls) => {
11 | return {
12 | useYarn: true,
13 | scenarios: [
14 | {
15 | name: 'ember-lts-2.12',
16 | npm: {
17 | devDependencies: {
18 | 'ember-source': '~2.12.0'
19 | }
20 | }
21 | },
22 | {
23 | name: 'ember-lts-2.16',
24 | npm: {
25 | devDependencies: {
26 | 'ember-source': '~2.16.0'
27 | }
28 | }
29 | },
30 | {
31 | name: 'ember-lts-2.18',
32 | npm: {
33 | devDependencies: {
34 | 'ember-source': '~2.18.0'
35 | }
36 | }
37 | },
38 | {
39 | name: 'ember-release',
40 | npm: {
41 | devDependencies: {
42 | 'ember-source': urls[0]
43 | }
44 | }
45 | },
46 | {
47 | name: 'ember-beta',
48 | npm: {
49 | devDependencies: {
50 | 'ember-source': urls[1]
51 | }
52 | }
53 | },
54 | {
55 | name: 'ember-canary',
56 | npm: {
57 | devDependencies: {
58 | 'ember-source': urls[2]
59 | }
60 | }
61 | },
62 | {
63 | name: 'ember-default',
64 | npm: {
65 | devDependencies: {}
66 | }
67 | }
68 | ]
69 | };
70 | });
71 | };
72 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(environment) {
4 | let ENV = {
5 | modulePrefix: 'dummy',
6 | environment,
7 | rootURL: '/',
8 | locationType: 'auto',
9 | contentful: {
10 | space: '10uc2hqlkgax',
11 | accessToken: 'cc086951ba89c5a39204506474a75446d1b7a6d418d3190cc77fa96bd91e0d82',
12 | previewAccessToken: '264e7e2fd47c6423ee45b54c0112a8a72d02de3d7a6e5cbb28ab0eee4da0673a',
13 | usePreviewApi: false
14 | },
15 | metricsAdapters: [
16 | {
17 | name: 'GoogleAnalytics',
18 | environments: ['all'],
19 | config: {
20 | id: 'UA-2516077-7'
21 | }
22 | }
23 | ],
24 | EmberENV: {
25 | FEATURES: {
26 | // Here you can enable experimental features on an ember canary build
27 | // e.g. 'with-controller': true
28 | },
29 | EXTEND_PROTOTYPES: {
30 | // Prevent Ember Data from overriding Date.parse.
31 | Date: false
32 | }
33 | },
34 |
35 | APP: {
36 | // Here you can pass flags/options to your application instance
37 | // when it is created
38 | }
39 | };
40 |
41 | if (environment === 'development') {
42 | // ENV.APP.LOG_RESOLVER = true;
43 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
44 | // ENV.APP.LOG_TRANSITIONS = true;
45 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
46 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
47 | }
48 |
49 | if (environment === 'test') {
50 | // Testem prefers this...
51 | ENV.locationType = 'none';
52 |
53 | // keep test console output quieter
54 | ENV.APP.LOG_ACTIVE_GENERATION = false;
55 | ENV.APP.LOG_VIEW_LOOKUPS = false;
56 |
57 | ENV.APP.rootElement = '#ember-testing';
58 | ENV.APP.autoboot = false;
59 | }
60 |
61 | if (environment === 'production') {
62 | ENV.locationType = 'hash';
63 | ENV.rootURL = '/ember-data-contentful/';
64 | }
65 |
66 | return ENV;
67 | };
68 |
--------------------------------------------------------------------------------
/tests/unit/adapters/contentful-test.js:
--------------------------------------------------------------------------------
1 | import { test, moduleForModel } from 'ember-qunit';
2 | import ContentfulModel from 'ember-data-contentful/models/contentful';
3 | import ContentfulAdapter from 'ember-data-contentful/adapters/contentful';
4 | import { run } from '@ember/runloop';
5 |
6 | import attr from 'ember-data/attr';
7 |
8 | let Post;
9 |
10 | moduleForModel('contentful', 'Unit | Adapter | contentful', {
11 | beforeEach() {
12 |
13 | Post = ContentfulModel.extend({
14 | title: attr('string'),
15 | });
16 |
17 | this.registry.register('model:post', Post);
18 | }
19 | });
20 |
21 | test('queryRecord calls _getContent with correct parameters when query is empty', function(assert) {
22 | let actualParams = null;
23 | let done = assert.async();
24 |
25 | let ApplicationAdapter = ContentfulAdapter.extend({
26 | _getContent(type, params) {
27 | actualParams = params;
28 | this._super(...arguments);
29 | }
30 | });
31 |
32 | this.registry.register('adapter:application', ApplicationAdapter);
33 |
34 | return run(() => {
35 | return this.store().queryRecord('post', { })
36 | .then(() => {
37 | assert.deepEqual(actualParams, {
38 | content_type: 'post',
39 | skip: 0,
40 | limit: 1
41 | });
42 | done();
43 | });
44 | });
45 | });
46 |
47 | test('queryRecord calls _getContent with correct parameters', function(assert) {
48 | let actualParams = null;
49 | let done = assert.async();
50 |
51 | let ApplicationAdapter = ContentfulAdapter.extend({
52 | _getContent(type, params) {
53 | actualParams = params;
54 | this._super(...arguments);
55 | }
56 | });
57 |
58 | this.registry.register('adapter:application', ApplicationAdapter);
59 |
60 | return run(() => {
61 | return this.store().queryRecord('post', { order: 'fields.title' })
62 | .then(() => {
63 | assert.deepEqual(actualParams, {
64 | content_type: 'post',
65 | order: 'fields.title',
66 | skip: 0,
67 | limit: 1
68 | });
69 | done();
70 | });
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-data-contentful",
3 | "version": "0.2.4",
4 | "description": "Retrieve data from contentful.com Content Delivery API",
5 | "keywords": [
6 | "ember-addon",
7 | "adapter",
8 | "serializer",
9 | "contentful",
10 | "ember-data"
11 | ],
12 | "directories": {
13 | "doc": "doc",
14 | "test": "tests"
15 | },
16 | "scripts": {
17 | "build": "ember build",
18 | "lint:js": "eslint ./*.js addon addon-test-support app config lib server test-support tests",
19 | "start": "ember serve",
20 | "test": "ember test",
21 | "test:all": "ember try:each"
22 | },
23 | "repository": "https://github.com/davidpett/ember-data-contentful",
24 | "bugs": "https://github.com/davidpett/ember-data-contentful/issues",
25 | "homepage": "http://davidpett.github.io/ember-data-contentful/",
26 | "engines": {
27 | "node": "^4.5 || 6.* || >= 7.*"
28 | },
29 | "author": "David Pett",
30 | "license": "MIT",
31 | "dependencies": {
32 | "chalk": "1.1.3",
33 | "ember-cli-babel": "^6.8.2",
34 | "ember-get-config": "0.2.4"
35 | },
36 | "devDependencies": {
37 | "broccoli-asset-rev": "^2.4.5",
38 | "ember-ajax": "^3.0.0",
39 | "ember-browserify": "^1.2.2",
40 | "ember-cli": "~3.1.2",
41 | "ember-cli-app-version": "^3.0.0",
42 | "ember-cli-dependency-checker": "^2.0.0",
43 | "ember-cli-eslint": "^4.2.1",
44 | "ember-cli-github-pages": "0.2.0",
45 | "ember-cli-htmlbars": "^2.0.1",
46 | "ember-cli-htmlbars-inline-precompile": "^1.0.0",
47 | "ember-cli-inject-live-reload": "^1.4.1",
48 | "ember-cli-qunit": "^4.1.1",
49 | "ember-cli-release": "1.0.0-beta.2",
50 | "ember-cli-shims": "^1.2.0",
51 | "ember-cli-sri": "^2.1.0",
52 | "ember-cli-uglify": "^2.0.0",
53 | "ember-data": "^3.1.0",
54 | "ember-disable-prototype-extensions": "^1.1.2",
55 | "ember-export-application-global": "^2.0.0",
56 | "ember-fetch": "3.4.4",
57 | "ember-load-initializers": "^1.0.0",
58 | "ember-markedjs": "^0.1.2",
59 | "ember-maybe-import-regenerator": "^0.1.6",
60 | "ember-metrics": "0.12.1",
61 | "ember-resolver": "^4.0.0",
62 | "ember-source": "~3.1.0",
63 | "ember-source-channel-url": "^1.0.1",
64 | "ember-try": "^0.2.23",
65 | "ember-welcome-page": "^3.0.0",
66 | "eslint-plugin-ember": "^5.0.0",
67 | "eslint-plugin-node": "^6.0.1",
68 | "highlight.js": "^9.9.0",
69 | "loader.js": "^4.2.3",
70 | "marked": "^0.3.6"
71 | },
72 | "files": [
73 | "addon/",
74 | "app/",
75 | "blueprints/",
76 | "index.js"
77 | ],
78 | "ember-addon": {
79 | "configPath": "tests/dummy/config",
80 | "demoURL": "http://davidpett.github.io/ember-data-contentful/"
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/davidpett/ember-data-contentful)
2 | [](https://badge.fury.io/js/ember-data-contentful)
3 | [](http://emberobserver.com/addons/ember-data-contentful)
4 | [](http://makeapullrequest.com)
5 | [](http://ember-fastboot.com)
6 | # ember-data-contentful
7 |
8 | This is an Ember Data adapter/serializer that uses the **READ ONLY** Content Delivery API from [contentful](http://contentful.com)
9 |
10 | ## Setup in your app
11 | ```sh
12 | ember install ember-data-contentful
13 | ```
14 |
15 | After installing the addon, configure your Contentful Space ID and Access Token inside `ENV` in `config/environment.js`:
16 | ```js
17 | contentful: {
18 | space: 'YOUR-CONTENTFUL-SPACE',
19 | accessToken: 'YOUR-CONTENTFUL-ACCESS-TOKEN',
20 | previewAccessToken: 'YOUR-CONTENTFUL-PREVIEW-ACCESS-TOKEN',
21 | usePreviewApi: false,
22 | environment: 'OPTIONAL CONTENTFUL ENVIRONMENT NAME'
23 | }
24 | ```
25 |
26 | You can enable the Preview API for use in development in `config/environment.js`:
27 |
28 | ```js
29 | if (environment === 'development') {
30 | // ...
31 |
32 | ENV.contentful.usePreviewApi = true;
33 | }
34 | ```
35 |
36 | ### Contentful models
37 |
38 | Included are a few models to help with some of the default fields. Here is an example:
39 |
40 | ```js
41 | // models/post.js
42 | import Contentful from 'ember-data-contentful/models/contentful';
43 | import attr from 'ember-data/attr';
44 | import { belongsTo, hasMany } from 'ember-data/relationships';
45 |
46 | export default Contentful.extend({
47 | author: hasMany('author'),
48 | body: attr('string'),
49 | date: attr('date'),
50 | featuredImage: belongsTo('contentful-asset'),
51 | slug: attr('string'),
52 | title: attr('string')
53 | });
54 | ```
55 | will give you the default fields of `contentType`, `createdAt`, and `updatedAt`.
56 |
57 | For multi-word model names, you can name your Contentful model IDs with dashes (ie. `timeline-post`) make sure the `contentType` field is inferred correctly.
58 |
59 | For any relationship property that is a Contentful Asset (image or other media file), use the `contentful-asset` model. i.e. `image: belongsTo('contentful-asset')` in order to get the asset correctly.
60 |
61 | ### Adapters and serializers
62 |
63 | You will also need to define an adapter and serializer for your model, so that Ember Data knows to fetch data from Contentful instead of your default backend.
64 |
65 | ```js
66 | // app/adapters/post.js
67 | import ContentfulAdapter from 'ember-data-contentful/adapters/contentful';
68 |
69 | export default ContentfulAdapter.extend({});
70 | ```
71 |
72 | ```js
73 | // app/serializers/post.js
74 | import ContentfulSerializer from 'ember-data-contentful/serializers/contentful';
75 |
76 | export default ContentfulSerializer.extend({});
77 | ```
78 |
79 | If you are only using Contentful models, you can set these to `app/adapters/application.js` and `app/serializers/application.js` to apply for all models.
80 |
81 | ## Usage
82 |
83 | Once you have configured your tokens and created your models, you can use the normal Ember Data requests of `findRecord`, `findAll`, `queryRecord`, and `query`. For example:
84 | ```js
85 | model() {
86 | return this.store.findAll('project');
87 | }
88 | ```
89 | or
90 | ```js
91 | model(params) {
92 | return this.store.findRecord('project', params.project_id);
93 | }
94 | ```
95 |
96 | If you want to use pretty urls and the `slug` field in contentful, you can make your query like so:
97 | ```js
98 | model(params) {
99 | return this.store.queryRecord('page', {
100 | 'fields.slug': params.page_slug
101 | });
102 | },
103 | serialize(model) {
104 | return { page_slug: get(model, 'slug') };
105 | }
106 | ```
107 | and ensure that you declare your route in `router.js` like this:
108 | ```js
109 | this.route('page', { path: ':page_slug' });
110 | ```
111 |
112 | ## Previewing Content
113 |
114 | Contentful provides a [Preview API](https://www.contentful.com/developers/docs/references/content-preview-api/) that allows you to preview unpublished content. In order to enable this, ensure you have your `previewAccessToken` configured in `config/environment.js` and enable the `usePreviewApi` property.
115 |
116 | For more information on the contentful Content Delivery API and the available queries, look here: https://www.contentful.com/developers/docs/references/content-delivery-api/
117 |
--------------------------------------------------------------------------------
/addon/serializers/contentful.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 | import { get } from '@ember/object';
3 | import { isNone, typeOf } from '@ember/utils';
4 |
5 | export default DS.JSONSerializer.extend({
6 | extractAttributes(modelClass, fieldsHash, objHash) {
7 | let attributeKey;
8 | let attributes = {};
9 |
10 | if (objHash.sys.type === 'Error') {
11 | console.warn(`[Contentful] ${objHash.message}`); /* eslint-disable-line no-console */
12 | console.warn(`[Contentful] It is possible that ${objHash.details.type}:${objHash.details.id} is not published, but is linked in this Entry.`); /* eslint-disable-line no-console */
13 | return {};
14 | }
15 | modelClass.eachAttribute((key) => {
16 | attributeKey = this.keyForAttribute(key, 'deserialize');
17 | if (fieldsHash && fieldsHash.hasOwnProperty(attributeKey)) {
18 | let attributeValue = fieldsHash[attributeKey];
19 | if (typeOf(attributeValue) === 'object' && attributeValue.sys && objHash.sys.type !== 'Asset') {
20 | attributeValue = attributeValue.sys.id;
21 | }
22 | attributes[key] = attributeValue;
23 | }
24 | if (objHash) {
25 | attributes['contentType'] = objHash.sys.type === 'Asset' ? 'asset' : objHash.sys.contentType.sys.id;
26 | attributes['createdAt'] = objHash.sys.createdAt;
27 | attributes['updatedAt'] = objHash.sys.updatedAt;
28 | }
29 | });
30 | return attributes;
31 | },
32 |
33 | modelHasAttributeOrRelationshipNamedType(modelClass) {
34 | return get(modelClass, 'attributes').has('type') || get(modelClass, 'relationshipsByName').has('type');
35 | },
36 |
37 | extractRelationship(relationshipModelName, relationshipHash) {
38 | if (isNone(relationshipHash)) {
39 | return null;
40 | }
41 | if (typeOf(relationshipHash) === 'object') {
42 | let modelClass = this.store.modelFor(relationshipModelName);
43 | if (relationshipHash.sys.type && !this.modelHasAttributeOrRelationshipNamedType(modelClass)) {
44 | relationshipHash.type = modelClass.modelName;
45 | relationshipHash.id = relationshipHash.sys.id;
46 | delete relationshipHash.sys;
47 |
48 | return relationshipHash;
49 | } else {
50 | if (relationshipHash.fields) {
51 | let data = {
52 | id: relationshipHash.sys.id,
53 | type: modelClass.modelName,
54 | attributes: this.extractAttributes(modelClass, relationshipHash.fields, relationshipHash),
55 | relationships: this.extractRelationships(modelClass, relationshipHash.fields)
56 | };
57 | return data;
58 | }
59 | }
60 | }
61 | return { id: relationshipHash.sys.id, type: relationshipModelName };
62 | },
63 |
64 | modelNameFromPayloadType(sys) {
65 | if (sys.type === "Asset") {
66 | return 'contentful-asset';
67 | } else {
68 | return sys.contentType.sys.id;
69 | }
70 | },
71 |
72 | normalize(modelClass, resourceHash) {
73 | let data = null;
74 |
75 | if (resourceHash) {
76 | data = {
77 | id: resourceHash.sys.id,
78 | type: this.modelNameFromPayloadType(resourceHash.sys),
79 | attributes: this.extractAttributes(modelClass, resourceHash.fields, resourceHash),
80 | relationships: this.extractRelationships(modelClass, resourceHash.fields)
81 | };
82 | this.applyTransforms(modelClass, data.attributes);
83 | }
84 |
85 | return { data };
86 | },
87 |
88 | normalizeResponse(store, primaryModelClass, payload, id, requestType) {
89 | switch (requestType) {
90 | case 'findRecord':
91 | return this.normalizeFindRecordResponse(...arguments);
92 | case 'queryRecord':
93 | return this.normalizeQueryRecordResponse(...arguments);
94 | case 'findAll':
95 | return this.normalizeFindAllResponse(...arguments);
96 | case 'findBelongsTo':
97 | return this.normalizeFindBelongsToResponse(...arguments);
98 | case 'findHasMany':
99 | return this.normalizeFindHasManyResponse(...arguments);
100 | case 'findMany':
101 | return this.normalizeFindManyResponse(...arguments);
102 | case 'query':
103 | return this.normalizeQueryResponse(...arguments);
104 | default:
105 | return null;
106 | }
107 | },
108 |
109 | normalizeFindRecordResponse() {
110 | return this.normalizeSingleResponse(...arguments);
111 | },
112 |
113 | normalizeQueryRecordResponse(store, primaryModelClass, payload, id, requestType) {
114 | let singlePayload = null;
115 | if (parseInt(payload.total) > 0) {
116 | singlePayload = payload.items[0];
117 | singlePayload.includes = payload.includes;
118 | }
119 | return this.normalizeSingleResponse(store, primaryModelClass, singlePayload, id, requestType);
120 | },
121 |
122 | normalizeFindAllResponse() {
123 | return this.normalizeArrayResponse(...arguments);
124 | },
125 |
126 | normalizeFindBelongsToResponse() {
127 | return this.normalizeSingleResponse(...arguments);
128 | },
129 |
130 | normalizeFindHasManyResponse() {
131 | return this.normalizeArrayResponse(...arguments);
132 | },
133 |
134 | normalizeFindManyResponse() {
135 | return this.normalizeArrayResponse(...arguments);
136 | },
137 |
138 | normalizeQueryResponse() {
139 | return this.normalizeArrayResponse(...arguments);
140 | },
141 |
142 | normalizeSingleResponse(store, primaryModelClass, payload) {
143 | return {
144 | data: this.normalize(primaryModelClass, payload).data,
145 | included: this._extractIncludes(store, payload)
146 | };
147 | },
148 |
149 | normalizeArrayResponse(store, primaryModelClass, payload) {
150 | return {
151 | data: payload.items.map((item) => {
152 | return this.normalize(primaryModelClass, item).data;
153 | }),
154 | included: this._extractIncludes(store, payload),
155 | meta: this.extractMeta(store, primaryModelClass, payload)
156 | };
157 | },
158 |
159 | /**
160 | @method extractMeta
161 | @param {DS.Store} store
162 | @param {DS.Model} modelClass
163 | @param {Object} payload
164 | @return {Object} { total: Integer, limit: Integer, skip: Integer }
165 | **/
166 | extractMeta(store, modelClass, payload) {
167 | if (payload) {
168 | let meta = {};
169 | if (payload.hasOwnProperty('limit')) {
170 | meta.limit = payload.limit;
171 | }
172 | if (payload.hasOwnProperty('skip')) {
173 | meta.skip = payload.skip;
174 | }
175 | if (payload.hasOwnProperty('total')) {
176 | meta.total = payload.total;
177 | }
178 | return meta;
179 | }
180 | },
181 |
182 | _extractIncludes(store, payload) {
183 | if(payload && payload.hasOwnProperty('includes') && typeof payload.includes !== "undefined") {
184 | let entries = new Array();
185 | let assets = new Array();
186 |
187 | if (payload.includes.Entry) {
188 | entries = payload.includes.Entry.map((item) => {
189 | return this.normalize(store.modelFor(item.sys.contentType.sys.id), item).data;
190 | });
191 | }
192 |
193 | if (payload.includes.Asset) {
194 | assets = payload.includes.Asset.map((item) => {
195 | return this.normalize(store.modelFor('contentful-asset'), item).data;
196 | });
197 | }
198 |
199 | return entries.concat(assets);
200 | } else {
201 | return [];
202 | }
203 | }
204 |
205 | });
206 |
--------------------------------------------------------------------------------
/addon/adapters/contentful.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 | import config from 'ember-get-config';
3 | import fetch from 'fetch';
4 |
5 | export default DS.Adapter.extend({
6 | /**
7 | @property coalesceFindRequests
8 | @type {boolean}
9 | @public
10 | */
11 | coalesceFindRequests: true,
12 |
13 | /**
14 | @property defaultSerializer
15 | @type {String}
16 | @public
17 | */
18 | defaultSerializer: 'contentful',
19 |
20 | /**
21 | Currently not implemented as this is adapter only implements the
22 | READ ONLY Content Delivery API (https://www.contentful.com/developers/docs/references/content-delivery-api/).
23 | For more information on the Content Management API,
24 | see https://www.contentful.com/developers/docs/references/content-management-api/
25 |
26 | @method createRecord
27 | @public
28 | */
29 | createRecord: null,
30 |
31 | /**
32 | Currently not implemented as this is adapter only implements the
33 | READ ONLY Content Delivery API (https://www.contentful.com/developers/docs/references/content-delivery-api/).
34 | For more information on the Content Management API,
35 | see https://www.contentful.com/developers/docs/references/content-management-api/
36 |
37 | @method updateRecord
38 | @public
39 | */
40 | updateRecord: null,
41 |
42 | /**
43 | Currently not implemented as this is adapter only implements the
44 | READ ONLY Content Delivery API (https://www.contentful.com/developers/docs/references/content-delivery-api/).
45 | For more information on the Content Management API,
46 | see https://www.contentful.com/developers/docs/references/content-management-api/
47 |
48 | @method deleteRecord
49 | @public
50 | */
51 | deleteRecord: null,
52 |
53 | /**
54 | Allows the adapter to override the content type param used in api calls where
55 | content type param is needed. (e.g. `findAll`, `query`, `queryRecord`)
56 |
57 | @method contentTypeParam
58 | @param {String} modelName
59 | @return {String}
60 | @public
61 | */
62 |
63 | contentTypeParam(modelName) {
64 | return modelName;
65 | },
66 |
67 | /**
68 | Called by the store in order to fetch the JSON for a given
69 | type and ID.
70 |
71 | The `findRecord` method makes a fetch (HTTP GET) request to a URL, and returns a
72 | promise for the resulting payload.
73 |
74 | @method findRecord
75 | @param {DS.Store} store
76 | @param {DS.Model} type
77 | @param {String} id
78 | @return {Promise} promise
79 | @public
80 | */
81 | findRecord(store, type, id) {
82 | let contentType = (type.modelName === 'asset' || type.modelName === 'contentful-asset') ? 'assets' : 'entries';
83 |
84 | return this._getContent(`${contentType}/${id}`);
85 | },
86 |
87 | /**
88 | Called by the store in order to fetch several records together.
89 |
90 | The `findMany` method makes a fetch (HTTP GET) request to a URL, and returns a
91 | promise for the resulting payload.
92 |
93 | @method findMany
94 | @param {DS.Store} store
95 | @param {DS.Model} type
96 | @param {Array} ids
97 | @return {Promise} promise
98 | @public
99 | */
100 | findMany(store, type, ids) {
101 | let contentType = (type.modelName === 'asset' || type.modelName === 'contentful-asset') ? 'assets' : 'entries';
102 |
103 | return this._getContent(contentType, { 'sys.id[in]': ids.toString() });
104 | },
105 |
106 | /**
107 | Called by the store in order to fetch a JSON array for all
108 | of the records for a given type.
109 |
110 | The `findAll` method makes a fetch (HTTP GET) request to a URL, and returns a
111 | promise for the resulting payload.
112 |
113 | @method findAll
114 | @param {DS.Store} store
115 | @param {DS.Model} type
116 | @return {Promise} promise
117 | @public
118 | */
119 | findAll(store, type) {
120 | return this._getContent('entries', { 'content_type': this.contentTypeParam(type.modelName) });
121 | },
122 |
123 | /**
124 | Called by the store in order to fetch a JSON array for
125 | the records that match a particular query.
126 |
127 | The `query` method makes a fetch (HTTP GET) request to a URL
128 | and returns a promise for the resulting payload.
129 |
130 | The `query` argument is a simple JavaScript object that will be passed directly
131 | to the server as parameters.
132 |
133 | @method query
134 | @param {DS.Store} store
135 | @param {DS.Model} type
136 | @param {Object} query
137 | @return {Promise} promise
138 | @public
139 | */
140 | query(store, type, query) {
141 | query = query || {};
142 | query['content_type'] = this.contentTypeParam(type.modelName);
143 | return this._getContent('entries', query);
144 | },
145 |
146 | /**
147 | Called by the store in order to fetch a JSON object for
148 | the record that matches a particular query.
149 |
150 | The `queryRecord` method makes a fetch (HTTP GET) request to a URL
151 | and returns a promise for the resulting payload.
152 |
153 | The `query` argument is a simple JavaScript object that will be passed directly
154 | to the server as parameters.
155 |
156 | @method queryRecord
157 | @param {DS.Store} store
158 | @param {DS.Model} type
159 | @param {Object} query
160 | @return {Promise} promise
161 | @public
162 | */
163 | queryRecord(store, type, query) {
164 | query = query || {};
165 | query['content_type'] = this.contentTypeParam(type.modelName);
166 | query['limit'] = 1;
167 | query['skip'] = 0;
168 | return this._getContent('entries', query);
169 | },
170 |
171 | /**
172 | `_getContent` makes all requests to the contentful.com content delivery API
173 |
174 | @method _getContent
175 | @param {String} type
176 | @param {Object} params
177 | @return {Promise} promise
178 | @private
179 | */
180 | _getContent(type, params) {
181 | let data = params || {};
182 | let {
183 | accessToken,
184 | api,
185 | space,
186 | environment
187 | } = this._getConfig();
188 |
189 | return fetch(`https://${api}.contentful.com/spaces/${space}${environment}/${type}/${this._serializeQueryParams(data)}`, {
190 | headers: {
191 | 'Accept': 'application/json; charset=utf-8',
192 | 'Authorization': `Bearer ${accessToken}`
193 | }
194 | }).then((response) => {
195 | return response.json();
196 | });
197 | },
198 |
199 | /**
200 | `_setContent` makes all requests to the contentful.com content management API
201 |
202 | @method _setContent
203 | @param {String} type
204 | @param {Object} params
205 | @return {Promise} promise
206 | @private
207 | */
208 | _setContent() {
209 | console.warn(`The Contentful Content Management API has not yet been implemented`); /* eslint-disable-line no-console */
210 | },
211 |
212 | /**
213 | `_serializeQueryParams` is a private utility used to
214 | stringify the query param object to be used with the fetch API.
215 |
216 | @method _serializeQueryParams
217 | @param {Object} obj
218 | @return {String} query string
219 | @private
220 | */
221 | _serializeQueryParams(obj) {
222 | let str = [];
223 | for (let p in obj) {
224 | if (obj.hasOwnProperty(p)) {
225 | str.push(`${encodeURIComponent(p)}=${encodeURIComponent(obj[p])}`);
226 | }
227 | }
228 | return str.length ? `?${str.join('&')}` : '';
229 | },
230 |
231 | /**
232 | `_getConfig` returns the config from your `config/environment.js`
233 |
234 | @method _getConfig
235 | @return {Object} params
236 | @private
237 | */
238 | _getConfig() {
239 | let accessToken = config.contentful ? config.contentful.accessToken : config.contentfulAccessToken;
240 | let api = 'cdn';
241 | let space = config.contentful ? config.contentful.space : config.contentfulSpace;
242 | let environment = config.contentful.environment ? `/environments/${config.contentful.environment}` : '';
243 | let previewAccessToken = config.contentful ? config.contentful.previewAccessToken : config.contentfulPreviewAccessToken;
244 |
245 | if (config.contentful.usePreviewApi || config.contentfulUsePreviewApi) {
246 | if (!previewAccessToken) {
247 | console.warn('You have specified to use the Contentful Preview API; However, no `previewAccessToken` has been specified in config/environment.js'); /* eslint-disable-line no-console */
248 | } else {
249 | accessToken = previewAccessToken;
250 | api = 'preview';
251 | }
252 | }
253 | if (config.contentfulAccessToken || config.contentfulSpace) {
254 | /* eslint-disable-next-line no-console */
255 | console.warn(`DEPRECATION: Use of 'contentfulAccessToken' and 'contentfulSpace' will be removed in ember-data-contentful@1.0.0. please migrate to the contentful object syntax:
256 | contentful: {
257 | accessToken: '${accessToken}',
258 | space: '${space}'
259 | }`);
260 | }
261 | return {
262 | accessToken,
263 | api,
264 | space,
265 | environment
266 | };
267 | }
268 | });
269 |
--------------------------------------------------------------------------------
/tests/unit/serializers/contentful-test.js:
--------------------------------------------------------------------------------
1 | import { test, moduleForModel } from 'ember-qunit';
2 | import ContentfulModel from 'ember-data-contentful/models/contentful';
3 | import ContentfulAsset from 'ember-data-contentful/models/contentful-asset';
4 | import ContentfulAdapter from 'ember-data-contentful/adapters/contentful';
5 | import ContentfulSerializer from 'ember-data-contentful/serializers/contentful';
6 |
7 | import attr from 'ember-data/attr';
8 | import { belongsTo } from 'ember-data/relationships';
9 |
10 | let Post, post, image;
11 |
12 |
13 | moduleForModel('contentful', 'Unit | Serializer | contentful', {
14 | needs: ['model:contentful-asset'],
15 | beforeEach() {
16 | const ApplicationAdapter = ContentfulAdapter.extend({});
17 | this.registry.register('adapter:application', ApplicationAdapter);
18 |
19 | const ApplicationSerializer = ContentfulSerializer.extend({});
20 | this.registry.register('serializer:application', ApplicationSerializer);
21 |
22 | Post = ContentfulModel.extend({
23 | title: attr('string'),
24 | image: belongsTo('contentful-asset'),
25 | location: attr()
26 | });
27 |
28 | this.registry.register('model:post', Post);
29 | post = {
30 | "sys": {
31 | "space": {
32 | "sys": {
33 | "type": "Link",
34 | "linkType": "Space",
35 | "id": "foobar"
36 | }
37 | },
38 | "id": "1",
39 | "type": "Entry",
40 | "createdAt": "2017-02-23T21:40:37.180Z",
41 | "updatedAt": "2017-02-27T21:24:26.007Z",
42 | "revision": 3,
43 | "contentType": {
44 | "sys": {
45 | "type": "Link",
46 | "linkType": "ContentType",
47 | "id": "post"
48 | }
49 | },
50 | "locale": "en-US"
51 | },
52 | "fields": {
53 | "title": "Example Post",
54 | "image": {
55 | "sys": {
56 | "type": "Link",
57 | "linkType": "Asset",
58 | "id": "2"
59 | }
60 | },
61 | "location": {
62 | "lon": -0.1055993,
63 | "lat": 51.5109263
64 | }
65 | }
66 | };
67 |
68 | image = {
69 | "sys": {
70 | "space": {
71 | "sys": {
72 | "type": "Link",
73 | "linkType": "Space",
74 | "id": "foobar"
75 | }
76 | },
77 | "id": "2",
78 | "type": "Asset",
79 | "createdAt": "2017-02-23T21:35:47.892Z",
80 | "updatedAt": "2017-02-23T21:35:47.892Z",
81 | "revision": 1,
82 | "locale": "en-US"
83 | },
84 | "fields": {
85 | "title": "Sample Image",
86 | "file": {
87 | "url": "//example.com/image.jpg",
88 | "details": {
89 | "size": 651763,
90 | "image": {
91 | "width": 931,
92 | "height": 1071
93 | }
94 | },
95 | "fileName": "image.jpg",
96 | "contentType": "image/jpeg"
97 | }
98 | }
99 | };
100 | }
101 | });
102 |
103 | // Sanity check to make sure everything is setup correctly.
104 | test('returns correct serializer for Post', function(assert) {
105 |
106 | let serializer = this.store().serializerFor('post');
107 |
108 | assert.ok(serializer instanceof ContentfulSerializer, 'serializer returned from serializerFor is an instance of ContentfulSerializer');
109 | });
110 |
111 | test('modelNameFromPayloadType for Asset', function(assert) {
112 | let sys = image.sys;
113 | let serializer = this.store().serializerFor('post');
114 |
115 | assert.equal(serializer.modelNameFromPayloadType(sys), 'contentful-asset');
116 |
117 | });
118 |
119 | test('modelNameFromPayloadType for Entry', function(assert) {
120 | let sys = post.sys;
121 | let serializer = this.store().serializerFor('post');
122 |
123 | assert.equal(serializer.modelNameFromPayloadType(sys), 'post');
124 |
125 | });
126 |
127 | test('normalize with empty resourceHash', function(assert) {
128 | let resourceHash = null;
129 | let serializer = this.store().serializerFor('post');
130 |
131 | assert.deepEqual(serializer.normalize(Post, resourceHash), { data: null });
132 | });
133 |
134 | test('normalize with Entry payload', function(assert) {
135 | let serializer = this.store().serializerFor('post');
136 | let resourceHash = post;
137 |
138 | let normalizedPost = serializer.normalize(Post, resourceHash);
139 | assert.equal(normalizedPost.data.attributes.contentType, "post");
140 | assert.equal(normalizedPost.data.attributes.title, post.fields.title);
141 |
142 | let expectedCreatedAt = new Date(normalizedPost.data.attributes.createdAt);
143 | let actualCreatedAt = new Date(post.sys.createdAt);
144 | assert.equal(expectedCreatedAt.toString(), actualCreatedAt.toString());
145 |
146 | let expectedUpdatedAt = new Date(normalizedPost.data.attributes.updatedAt);
147 | let actualUpdatedAt = new Date(post.sys.updatedAt);
148 | assert.equal(expectedUpdatedAt.toString(), actualUpdatedAt.toString());
149 |
150 | assert.equal(normalizedPost.data.id, post.sys.id);
151 | assert.equal(normalizedPost.data.type, "post");
152 | assert.deepEqual(normalizedPost.data.relationships, {
153 | "image": {
154 | "data": {
155 | "id": '2',
156 | "type": 'contentful-asset'
157 | }
158 | }
159 | });
160 | });
161 |
162 | test('normalize with Asset payload', function(assert) {
163 | let serializer = this.store().serializerFor('contentful-asset');
164 | let resourceHash = image;
165 |
166 | let normalizedAsset = serializer.normalize(ContentfulAsset, resourceHash);
167 |
168 | assert.equal(normalizedAsset.data.attributes.contentType, "asset");
169 | assert.equal(normalizedAsset.data.id, image.sys.id);
170 | assert.equal(normalizedAsset.data.type, "contentful-asset");
171 | assert.equal(normalizedAsset.data.attributes.title, image.fields.title);
172 | assert.deepEqual(normalizedAsset.data.attributes.file, {
173 | contentType: "image/jpeg",
174 | details: {
175 | size: 651763,
176 | image: {
177 | width: 931,
178 | height: 1071
179 | }
180 | },
181 | fileName: "image.jpg",
182 | url: "//example.com/image.jpg"
183 | });
184 |
185 | let expectedAssetCreatedAt = new Date(normalizedAsset.data.attributes.createdAt);
186 | let actualAssetCreatedAt = new Date(image.sys.createdAt);
187 | assert.equal(expectedAssetCreatedAt.toString(), actualAssetCreatedAt.toString());
188 |
189 | let expectedAssetUpdatedAt = new Date(normalizedAsset.data.attributes.updatedAt);
190 | let actualAssetUpdatedAt = new Date(image.sys.updatedAt);
191 | assert.equal(expectedAssetUpdatedAt.toString(), actualAssetUpdatedAt.toString());
192 | });
193 |
194 | test('normalize with Location payload', function(assert) {
195 | let serializer = this.store().serializerFor('post');
196 | let resourceHash = post;
197 |
198 | let normalizedPost = serializer.normalize(Post, resourceHash);
199 | assert.equal(normalizedPost.data.attributes.location, post.fields.location);
200 | });
201 |
202 | test('normalizeQueryRecordResponse with empty items', function(assert) {
203 | let id = '';
204 | let payload = {
205 | "sys": {
206 | "type": "Array"
207 | },
208 | "total": 0,
209 | "skip": 0,
210 | "limit": 100,
211 | "items": []
212 | };
213 |
214 | let serializer = this.store().serializerFor('post');
215 |
216 | let documentHash = serializer.normalizeQueryRecordResponse(
217 | this.store(),
218 | Post,
219 | payload,
220 | id,
221 | 'queryRecord'
222 | );
223 |
224 | assert.deepEqual(documentHash, { data: null, included: []});
225 | });
226 |
227 | test('normalizeQueryRecordResponse with an item with includes', function(assert) {
228 |
229 | let id = '';
230 | let payload = {
231 | "sys": {
232 | "type": "Array"
233 | },
234 | "total": 1,
235 | "skip": 0,
236 | "limit": 1,
237 | "items": [
238 | post
239 | ],
240 | "includes": {
241 | "Asset": [ image ]
242 | }
243 | };
244 |
245 | let serializer = this.store().serializerFor('post');
246 |
247 | let documentHash = serializer.normalizeQueryRecordResponse(
248 | this.store(),
249 | Post,
250 | payload,
251 | id,
252 | 'queryRecord'
253 | );
254 |
255 | assert.equal(documentHash.data.attributes.contentType, "post");
256 | assert.equal(documentHash.data.attributes.title, post.fields.title);
257 |
258 | let expectedCreatedAt = new Date(documentHash.data.attributes.createdAt);
259 | let actualCreatedAt = new Date(post.sys.createdAt);
260 | assert.equal(expectedCreatedAt.toString(), actualCreatedAt.toString());
261 |
262 | let expectedUpdatedAt = new Date(documentHash.data.attributes.updatedAt);
263 | let actualUpdatedAt = new Date(post.sys.updatedAt);
264 | assert.equal(expectedUpdatedAt.toString(), actualUpdatedAt.toString());
265 |
266 | assert.equal(documentHash.data.id, post.sys.id);
267 | assert.equal(documentHash.data.type, "post");
268 | assert.deepEqual(documentHash.data.relationships, {
269 | "image": {
270 | "data": {
271 | "id": '2',
272 | "type": 'contentful-asset'
273 | }
274 | }
275 | });
276 |
277 | assert.equal(documentHash.included.length, 1);
278 | let asset = documentHash.included[0];
279 | assert.equal(asset.attributes.contentType, "asset");
280 | assert.equal(asset.id, image.sys.id);
281 | assert.equal(asset.type, "contentful-asset");
282 | assert.equal(asset.attributes.title, image.fields.title);
283 | assert.deepEqual(asset.attributes.file, {
284 | contentType: "image/jpeg",
285 | details: {
286 | size: 651763,
287 | image: {
288 | width: 931,
289 | height: 1071
290 | }
291 | },
292 | fileName: "image.jpg",
293 | url: "//example.com/image.jpg"
294 | });
295 |
296 | let expectedAssetCreatedAt = new Date(asset.attributes.createdAt);
297 | let actualAssetCreatedAt = new Date(image.sys.createdAt);
298 | assert.equal(expectedAssetCreatedAt.toString(), actualAssetCreatedAt.toString());
299 |
300 | let expectedAssetUpdatedAt = new Date(asset.attributes.updatedAt);
301 | let actualAssetUpdatedAt = new Date(image.sys.updatedAt);
302 | assert.equal(expectedAssetUpdatedAt.toString(), actualAssetUpdatedAt.toString());
303 |
304 | assert.deepEqual(asset.relationships, {});
305 | });
306 |
307 | test('normalizeQueryRecordResponse with an item w/o includes', function(assert) {
308 |
309 | let id = '';
310 | let payload = {
311 | "sys": {
312 | "type": "Array"
313 | },
314 | "total": 1,
315 | "skip": 0,
316 | "limit": 1,
317 | "items": [
318 | {
319 | "sys": {
320 | "space": {
321 | "sys": {
322 | "type": "Link",
323 | "linkType": "Space",
324 | "id": "foobar"
325 | }
326 | },
327 | "id": "1",
328 | "type": "Entry",
329 | "createdAt": "2017-02-23T21:40:37.180Z",
330 | "updatedAt": "2017-02-27T21:24:26.007Z",
331 | "revision": 3,
332 | "contentType": {
333 | "sys": {
334 | "type": "Link",
335 | "linkType": "ContentType",
336 | "id": "post"
337 | }
338 | },
339 | "locale": "en-US"
340 | },
341 | "fields": {
342 | "title": "Example Post"
343 | }
344 | }
345 | ]
346 | };
347 |
348 | Post = ContentfulModel.extend({
349 | title: attr('string')
350 | });
351 |
352 | let serializer = this.store().serializerFor('post');
353 |
354 | let documentHash = serializer.normalizeQueryRecordResponse(
355 | this.store(),
356 | Post,
357 | payload,
358 | id,
359 | 'queryRecord'
360 | );
361 |
362 | assert.equal(documentHash.data.attributes.contentType, "post");
363 | assert.equal(documentHash.data.attributes.title, post.fields.title);
364 |
365 | let expectedCreatedAt = new Date(documentHash.data.attributes.createdAt);
366 | let actualCreatedAt = new Date(post.sys.createdAt);
367 | assert.equal(expectedCreatedAt.toString(), actualCreatedAt.toString());
368 |
369 | let expectedUpdatedAt = new Date(documentHash.data.attributes.updatedAt);
370 | let actualUpdatedAt = new Date(post.sys.updatedAt);
371 | assert.equal(expectedUpdatedAt.toString(), actualUpdatedAt.toString());
372 |
373 | assert.equal(documentHash.data.id, post.sys.id);
374 | assert.equal(documentHash.data.type, "post");
375 | assert.deepEqual(documentHash.data.relationships, {});
376 | assert.equal(documentHash.included.length, 0);
377 | });
378 |
379 | test('normalizeQueryResponse with an item with includes', function(assert) {
380 |
381 | let id = '';
382 | let payload = {
383 | "sys": {
384 | "type": "Array"
385 | },
386 | "total": 1,
387 | "skip": 0,
388 | "limit": 1,
389 | "items": [
390 | post
391 | ],
392 | "includes": {
393 | "Asset": [ image ]
394 | }
395 | };
396 |
397 | let serializer = this.store().serializerFor('post');
398 |
399 | let documentHash = serializer.normalizeQueryResponse(
400 | this.store(),
401 | Post,
402 | payload,
403 | id,
404 | 'queryRecord'
405 | );
406 |
407 | // Items (Posts)
408 | assert.equal(documentHash.data.length, 1);
409 |
410 | let postData = documentHash.data[0];
411 | assert.equal(postData.attributes.contentType, "post");
412 | assert.equal(postData.attributes.title, post.fields.title);
413 |
414 | let expectedCreatedAt = new Date(postData.attributes.createdAt);
415 | let actualCreatedAt = new Date(post.sys.createdAt);
416 | assert.equal(expectedCreatedAt.toString(), actualCreatedAt.toString());
417 |
418 | let expectedUpdatedAt = new Date(postData.attributes.updatedAt);
419 | let actualUpdatedAt = new Date(post.sys.updatedAt);
420 | assert.equal(expectedUpdatedAt.toString(), actualUpdatedAt.toString());
421 |
422 | assert.equal(postData.id, post.sys.id);
423 | assert.equal(postData.type, "post");
424 | assert.deepEqual(postData.relationships, {
425 | "image": {
426 | "data": {
427 | "id": '2',
428 | "type": 'contentful-asset'
429 | }
430 | }
431 | });
432 |
433 | // Includes
434 | assert.equal(documentHash.included.length, 1);
435 | let asset = documentHash.included[0];
436 | assert.equal(asset.attributes.contentType, "asset");
437 | assert.equal(asset.id, image.sys.id);
438 | assert.equal(asset.type, "contentful-asset");
439 | assert.equal(asset.attributes.title, image.fields.title);
440 | assert.deepEqual(asset.attributes.file, {
441 | contentType: "image/jpeg",
442 | details: {
443 | size: 651763,
444 | image: {
445 | width: 931,
446 | height: 1071
447 | }
448 | },
449 | fileName: "image.jpg",
450 | url: "//example.com/image.jpg"
451 | });
452 |
453 | let expectedAssetCreatedAt = new Date(asset.attributes.createdAt);
454 | let actualAssetCreatedAt = new Date(image.sys.createdAt);
455 | assert.equal(expectedAssetCreatedAt.toString(), actualAssetCreatedAt.toString());
456 |
457 | let expectedAssetUpdatedAt = new Date(asset.attributes.updatedAt);
458 | let actualAssetUpdatedAt = new Date(image.sys.updatedAt);
459 | assert.equal(expectedAssetUpdatedAt.toString(), actualAssetUpdatedAt.toString());
460 |
461 | assert.deepEqual(asset.relationships, {});
462 |
463 | // Meta
464 | let meta = serializer.extractMeta(this.store(), Post, payload);
465 | assert.deepEqual(documentHash.meta, meta);
466 | });
467 |
468 | test('normalizeQueryResponse with an item w/o includes', function(assert) {
469 |
470 | let id = '';
471 | let payload = {
472 | "sys": {
473 | "type": "Array"
474 | },
475 | "total": 1,
476 | "skip": 0,
477 | "limit": 1,
478 | "items": [
479 | {
480 | "sys": {
481 | "space": {
482 | "sys": {
483 | "type": "Link",
484 | "linkType": "Space",
485 | "id": "foobar"
486 | }
487 | },
488 | "id": "1",
489 | "type": "Entry",
490 | "createdAt": "2017-02-23T21:40:37.180Z",
491 | "updatedAt": "2017-02-27T21:24:26.007Z",
492 | "revision": 3,
493 | "contentType": {
494 | "sys": {
495 | "type": "Link",
496 | "linkType": "ContentType",
497 | "id": "post"
498 | }
499 | },
500 | "locale": "en-US"
501 | },
502 | "fields": {
503 | "title": "Example Post"
504 | }
505 | }
506 | ]
507 | };
508 |
509 | Post = ContentfulModel.extend({
510 | title: attr('string')
511 | });
512 |
513 | let serializer = this.store().serializerFor('post');
514 |
515 | let documentHash = serializer.normalizeQueryResponse(
516 | this.store(),
517 | Post,
518 | payload,
519 | id,
520 | 'queryRecord'
521 | );
522 |
523 | // Items (Posts)
524 | assert.equal(documentHash.data.length, 1);
525 |
526 | let postData = documentHash.data[0];
527 | assert.equal(postData.attributes.contentType, "post");
528 | assert.equal(postData.attributes.title, post.fields.title);
529 |
530 | let expectedCreatedAt = new Date(postData.attributes.createdAt);
531 | let actualCreatedAt = new Date(post.sys.createdAt);
532 | assert.equal(expectedCreatedAt.toString(), actualCreatedAt.toString());
533 |
534 | let expectedUpdatedAt = new Date(postData.attributes.updatedAt);
535 | let actualUpdatedAt = new Date(post.sys.updatedAt);
536 | assert.equal(expectedUpdatedAt.toString(), actualUpdatedAt.toString());
537 |
538 | assert.equal(postData.id, post.sys.id);
539 | assert.equal(postData.type, "post");
540 | assert.deepEqual(postData.relationships, {});
541 |
542 | // Includes
543 | assert.equal(documentHash.included.length, 0);
544 |
545 | // Meta
546 | let meta = serializer.extractMeta(this.store(), Post, payload);
547 | assert.deepEqual(documentHash.meta, meta);
548 | });
549 |
550 | test('extractMeta returns null without a payload', function(assert) {
551 | let serializer = this.store().serializerFor('post');
552 | let meta = serializer.extractMeta(this.store(), Post, null);
553 |
554 | assert.equal(meta, null);
555 | });
556 |
557 | test('extractMeta returns meta from payload', function(assert) {
558 | let payload = {
559 | "sys": {
560 | "type": "Array"
561 | },
562 | "total": 3,
563 | "skip": 1,
564 | "limit": 2,
565 | "items": [
566 | post
567 | ],
568 | "includes": {
569 | "Asset": [ image ]
570 | }
571 | };
572 |
573 | let serializer = this.store().serializerFor('post');
574 |
575 | let meta = serializer.extractMeta(this.store(), Post, payload);
576 |
577 | assert.deepEqual(meta, {
578 | total: 3,
579 | skip: 1,
580 | limit: 2
581 | });
582 | });
583 |
--------------------------------------------------------------------------------