├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .eslintrc.js ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── .watchmanconfig ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep ├── adapters │ └── contentful.js ├── models │ ├── contentful-asset.js │ └── contentful.js └── serializers │ └── contentful.js ├── app ├── .gitkeep ├── adapters │ └── contentful.js ├── models │ ├── contentful-asset.js │ └── contentful.js └── serializers │ └── contentful.js ├── blueprints ├── .jshintrc └── ember-data-contentful │ ├── files │ └── app │ │ └── adapters │ │ └── application.js │ └── index.js ├── bower.json ├── config ├── ember-try.js ├── environment.js └── release.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.js ├── tests ├── .jshintrc ├── dummy │ ├── app │ │ ├── adapters │ │ │ └── application.js │ │ ├── app.js │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ ├── .gitkeep │ │ │ ├── author.js │ │ │ └── post.js │ │ ├── resolver.js │ │ ├── router.js │ │ ├── routes │ │ │ ├── .gitkeep │ │ │ ├── post.js │ │ │ └── posts.js │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ ├── components │ │ │ └── .gitkeep │ │ │ ├── post.hbs │ │ │ └── posts.hbs │ ├── config │ │ ├── environment.js │ │ └── targets.js │ └── public │ │ ├── crossdomain.xml │ │ └── robots.txt ├── helpers │ ├── destroy-app.js │ ├── module-for-acceptance.js │ ├── resolver.js │ └── start-app.js ├── index.html ├── integration │ └── .gitkeep ├── test-helper.js └── unit │ ├── .gitkeep │ ├── adapters │ └── contentful-test.js │ ├── models │ ├── contentful-asset-test.js │ └── contentful-test.js │ └── serializers │ └── contentful-test.js ├── vendor └── .gitkeep └── yarn.lock /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidpett/ember-data-contentful/b5c4de89de0620b1a5f69aaab079eccaeec38eaf/addon/.gitkeep -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidpett/ember-data-contentful/b5c4de89de0620b1a5f69aaab079eccaeec38eaf/app/.gitkeep -------------------------------------------------------------------------------- /app/adapters/contentful.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-data-contentful/adapters/contentful'; 2 | -------------------------------------------------------------------------------- /app/models/contentful-asset.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-data-contentful/models/contentful-asset'; 2 | -------------------------------------------------------------------------------- /app/models/contentful.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-data-contentful/models/contentful'; 2 | -------------------------------------------------------------------------------- /app/serializers/contentful.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-data-contentful/serializers/contentful'; 2 | -------------------------------------------------------------------------------- /blueprints/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "console" 4 | ], 5 | "strict": false 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-data-contentful", 3 | "dependencies": { 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(/* environment, appConfig */) { 4 | return { }; 5 | }; 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: 'ember-data-contentful' 5 | }; 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/.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 | -------------------------------------------------------------------------------- /tests/dummy/app/adapters/application.js: -------------------------------------------------------------------------------- 1 | import ContentfulAdapter from 'ember-data-contentful/adapters/contentful'; 2 | 3 | export default ContentfulAdapter.extend({}); 4 | -------------------------------------------------------------------------------- /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/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidpett/ember-data-contentful/b5c4de89de0620b1a5f69aaab079eccaeec38eaf/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidpett/ember-data-contentful/b5c4de89de0620b1a5f69aaab079eccaeec38eaf/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidpett/ember-data-contentful/b5c4de89de0620b1a5f69aaab079eccaeec38eaf/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |