├── .gitignore ├── screenshot.png ├── views ├── error.jade ├── index.jade ├── category.jade ├── post.jade ├── mixins.jade └── layout.jade ├── services ├── cf.js └── blog.js ├── routes ├── index.js ├── posts.js └── category.js ├── package.json ├── LICENSE ├── README.md ├── bin └── www ├── app.js └── public └── stylesheets ├── style.styl └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/cf-expressjs-blog/master/screenshot.png -------------------------------------------------------------------------------- /views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block nav 4 | a.blog-nav-item.active(href="/") Home 5 | 6 | block content 7 | +blogpostList(posts, 2) 8 | -------------------------------------------------------------------------------- /views/category.jade: -------------------------------------------------------------------------------- 1 | extends layout.jade 2 | 3 | block nav 4 | a.blog-nav-item(href="/") Home 5 | a.blog-nav-item.active(href="#")= category.fields.title 6 | 7 | block content 8 | h2.blog-post-title Posts classified in "#{category.fields.title}" 9 | .blog-post-meta #{category.fields.shortDescription} 10 | +blogpostList(posts, 3) 11 | -------------------------------------------------------------------------------- /views/post.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block nav 4 | a.blog-nav-item(href="/") Home 5 | a.blog-nav-item.active(href="#")= title 6 | 7 | block content 8 | +blogpost(title, date, author, categories) 9 | if featuredImage 10 | div.featured 11 | img(src=featuredImage.file.url, title=featuredImage.title) 12 | section!= body 13 | -------------------------------------------------------------------------------- /services/cf.js: -------------------------------------------------------------------------------- 1 | var contentful = require('contentful'); 2 | 3 | // Configure Contentful 4 | exports.const = { 5 | "POST_CT": '2wKn6yEnZewu2SCCkus4as', 6 | "CAT_CT": '5KMiN6YPvi42icqAUQMCQe', 7 | "AUTHOR_CT": '1kUEViTN4EmGiEaaeC6ouY' 8 | }; 9 | 10 | exports.api = contentful.createClient({ 11 | space: 'w7sdyslol3fu', 12 | accessToken: '7fae8d7e008ac19995f5e95be0e61d4308e34c83541791eeec0ccce7d2f8d8be' 13 | }); 14 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var blog = require('../services/blog'); 3 | var router = express.Router(); 4 | 5 | 6 | // Fetch first 5 blog posts 7 | router.use(function(req, res, next) { 8 | blog.getPostsList(5).then(function(posts) { 9 | req.posts = posts; 10 | next(); 11 | }) 12 | }); 13 | 14 | /* GET home page. */ 15 | router.get('/', function(req, res, next) { 16 | res.render('index', { 17 | 'title': 'Contentful Blog', 18 | 'posts': req.posts 19 | }); 20 | }); 21 | 22 | module.exports = router; 23 | -------------------------------------------------------------------------------- /routes/posts.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var blog = require('../services/blog'); 3 | 4 | var router = express.Router(); 5 | 6 | // Fetch post for rendering 7 | router.param('slug', function(req, res, next, slug) { 8 | blog.getPostBySlug(slug).then(function(post) { 9 | req.blogPost = post; 10 | next(); 11 | }).fail(function(errorMsg) { 12 | res.status(404).send(errorMsg); 13 | }); 14 | }); 15 | 16 | // Show the blog post page. 17 | router.get('/:slug', function(req, res, next) { 18 | res.render('post', req.blogPost); 19 | }); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /routes/category.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var blog = require('../services/blog'); 3 | var router = express.Router(); 4 | 5 | 6 | router.param('id', function(req, res, next, id) { 7 | console.log('Fetching category and posts...'); 8 | blog.getCategoryWithPosts(id).then(function(res) { 9 | req.posts = res.posts; 10 | req.category = res.category; 11 | next(); 12 | }); 13 | }); 14 | 15 | /* Category home page. */ 16 | router.get('/:id', function(req, res, next) { 17 | res.render('category', { 18 | posts: req.posts, 19 | category: req.category 20 | }); 21 | }); 22 | 23 | module.exports = router; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-blog", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "nodemon ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.13.2", 10 | "contentful": "^3.3.14", 11 | "cookie-parser": "~1.3.5", 12 | "dateformat": "^1.0.12", 13 | "debug": "~2.2.0", 14 | "express": "~4.13.1", 15 | "jade": "~1.11.0", 16 | "marked": "^0.3.5", 17 | "morgan": "~1.6.1", 18 | "q": "^1.4.1", 19 | "serve-favicon": "~2.3.0", 20 | "stylus": "0.42.3", 21 | "util": "^0.10.3" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^1.10.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /views/mixins.jade: -------------------------------------------------------------------------------- 1 | mixin blogpostList(posts, level) 2 | each post in posts 3 | +blogpost(post.title, post.date, post.author, post.categories, level) 4 | p!= post.plot 5 | ul.pager 6 | li 7 | a(href='/post/' + post.slug) Read more... 8 | 9 | mixin blogpost(title, date, author, postCategories, level) 10 | .blog-post 11 | if level == 2 12 | h2.blog-post-title #{title} 13 | else 14 | h3 #{title} 15 | .blog-post-meta #{date} by #{author.name} in 16 | each category, index in postCategories 17 | span= index > 0 ? ', ' : ' ' 18 | a(href='/category/' + category.sys.id)= category.fields.title 19 | if block 20 | block 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Hervé Labas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Deprecation notice 3 | 4 | This repository is deprecated and no further maintenance is planned. For an express.js example, please see https://github.com/contentful/the-example-app.nodejs 5 | 6 | --- 7 | 8 | This example showcases usage of the Contentful Delivery API through the Node.js SDK for building the blog using the Blog demo space. 9 | 10 | ![screenshot](./screenshot.png) 11 | 12 | # About 13 | 14 | [Contentful](https://www.contentful.com) is a content management platform for web applications, mobile apps and connected devices. It allows you to create, edit & manage content in the cloud and publish it anywhere via a powerful API. Contentful offers tools for managing editorial teams and enabling cooperation between organizations. 15 | 16 | # Installation & usage 17 | 18 | Clone the project 19 | 20 | ```sh 21 | $ git clone https://github.com/contentful/cf-expressjs-blog 22 | ``` 23 | 24 | Install dependencies 25 | 26 | ```sh 27 | $ npm install 28 | ``` 29 | 30 | To check the app in the browser navigate to `http://localhost:3000` 31 | 32 | Start the project 33 | 34 | ```sh 35 | npm start 36 | ``` 37 | # Documentation 38 | 39 | ## Service modules 40 | * The [cf](services/cf.js) module is encapsulating Contentful's specifics like the API key, as well as constants for content type identifiers 41 | * The [blog](services/blog.js) module is a service taking care of calling Contentful and normalizing the results from the API 42 | 43 | ## View 44 | * The main CSS structure comes from the [Bootstrap blog template](http://getbootstrap.com/examples/blog/) it is defined in the [layout](views/layout.jade) template. 45 | 46 | * Blog posts rendering is handled in the [mixins](views/mixins.jade), so it can be reused in the [category](views/category.jade) and [index](views/index.jade) views. 47 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app') 8 | var debug = require('debug')('contentful_express_tutorial:server') 9 | var http = require('http') 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000') 16 | app.set('port', port) 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app) 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port) 29 | server.on('error', onError) 30 | server.on('listening', onListening) 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort (val) { 37 | var port = parseInt(val, 10) 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port 47 | } 48 | 49 | return false 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError (error) { 57 | if (error.syscall !== 'listen') { 58 | throw error 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges') 69 | process.exit(1) 70 | break 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use') 73 | process.exit(1) 74 | break 75 | default: 76 | throw error 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening () { 85 | var addr = server.address() 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port 89 | debug('Listening on ' + bind) 90 | } 91 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var blog = require('./services/blog'); 8 | 9 | var routes = require('./routes/index'); 10 | var posts = require('./routes/posts'); 11 | var category = require('./routes/category'); 12 | 13 | 14 | var app = express(); 15 | 16 | // view engine setup 17 | app.set('views', path.join(__dirname, 'views')); 18 | app.set('view engine', 'jade'); 19 | 20 | // uncomment after placing your favicon in /public 21 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 22 | app.use(logger('dev')); 23 | app.use(bodyParser.json()); 24 | app.use(bodyParser.urlencoded({ extended: false })); 25 | app.use(cookieParser()); 26 | app.use(require('stylus').middleware(path.join(__dirname, 'public'))); 27 | app.use(express.static(path.join(__dirname, 'public'))); 28 | 29 | 30 | // Fetch categories 31 | app.use(function(req, res, next) { 32 | blog.getCategories().then(function(categories) { 33 | app.locals.categories = categories; 34 | next(); 35 | }); 36 | }); 37 | 38 | app.use('/', routes); 39 | app.use('/category', category); 40 | app.use('/post', posts); 41 | 42 | // catch 404 and forward to error handler 43 | app.use(function(req, res, next) { 44 | var err = new Error('Not Found'); 45 | err.status = 404; 46 | next(err); 47 | }); 48 | 49 | // error handlers 50 | 51 | // development error handler 52 | // will print stacktrace 53 | if (app.get('env') === 'development') { 54 | app.use(function(err, req, res, next) { 55 | res.status(err.status || 500); 56 | res.render('error', { 57 | message: err.message, 58 | error: err 59 | }); 60 | }); 61 | } 62 | 63 | // production error handler 64 | // no stacktraces leaked to user 65 | app.use(function(err, req, res, next) { 66 | res.status(err.status || 500); 67 | res.render('error', { 68 | message: err.message, 69 | error: {} 70 | }); 71 | }); 72 | 73 | 74 | module.exports = app; 75 | -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | include ./mixins.jade 2 | 3 | doctype html 4 | html 5 | head 6 | meta(charset="utf-8") 7 | meta(http-equiv="X-UA-Compatible", content="IE=edge") 8 | meta(name="viewport", content="width=device-width, initial-scale=1") 9 | 10 | title= title 11 | 12 | link(rel='stylesheet', href='/stylesheets/style.css') 13 | link(rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css' 14 | integrity='sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7', 15 | crossorigin='anonymous') 16 | link(rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css', 17 | integrity='sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r', 18 | crossorigin='anonymous') 19 | 20 | body 21 | .blog-masthead 22 | .container 23 | nav.blog-nav 24 | block nav 25 | .container 26 | 27 | .blog-header 28 | h1.blog-title Contentful's Express Blog 29 | p.lead.blog-description An ExpressJS powered demonstration showcasing Contentful's Blog Space template. 30 | 31 | .row 32 | .col-sm-8.blog-main 33 | block content 34 | .col-sm-3.col-sm-offset-1.blog-sidebar 35 | .sidebar-module.sidebar-module-inset 36 | h4 About 37 | p You can learn more about Contentful, a headless CMS designed for API-first thinkers. 38 | .sidebar-module 39 | h4 Categories 40 | ul.list-unstyled 41 | each category in categories 42 | li 43 | a(href='/category/' + category.sys.id)= category.fields.title 44 | block sidebar 45 | 46 | footer.blog-footer 47 | a(href="https://github.com/contentful/cf-expressjs-blog") Fork me on Github 48 | 49 | script(src='https://code.jquery.com/jquery-2.2.4.js', 50 | integrity='sha256-iT6Q9iMJYuQiMWNd9lDyBUStIq/8PuOW33aOqmvFpqI=', 51 | crossorigin='anonymous') 52 | 53 | script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js', 54 | integrity='sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS', 55 | crossorigin='anonymous') 56 | -------------------------------------------------------------------------------- /public/stylesheets/style.styl: -------------------------------------------------------------------------------- 1 | box-shadow() 2 | -webkit-box-shadow: arguments 3 | box-shadow: arguments 4 | 5 | body 6 | font-family: Georgia, "Times New Roman", Times, serif 7 | color: #555 8 | 9 | 10 | h1, .h1, 11 | h2, .h2, 12 | h3, .h3, 13 | h4, .h4, 14 | h5, .h5, 15 | h6, .h6 16 | margin-top: 0 17 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif 18 | font-weight: normal 19 | color: #333 20 | 21 | @media (min-width: 1200px) 22 | .container 23 | width: 970px 24 | 25 | .blog-masthead 26 | background-color: #428bca 27 | box-shadow(inset 0 -2px 5px rgba(0,0,0,.1)) 28 | 29 | .blog-nav-item 30 | position: relative 31 | display: inline-block 32 | padding: 10px 33 | font-weight: 500 34 | color: #cdddeb 35 | 36 | :hover, 37 | :focus 38 | color: #fff 39 | text-decoration: none 40 | 41 | .blog-nav .active 42 | color: #fff 43 | 44 | .blog-nav .active:after 45 | position: absolute 46 | bottom: 0 47 | left: 50% 48 | width: 0 49 | height: 0 50 | margin-left: -5px 51 | vertical-align: middle 52 | content: " " 53 | border-right: 5px solid transparent 54 | border-bottom: 5px solid 55 | border-left: 5px solid transparent 56 | 57 | 58 | 59 | /* 60 | * Blog name and description 61 | */ 62 | 63 | .blog-header 64 | padding-top: 20px 65 | padding-bottom: 20px 66 | 67 | .blog-title 68 | margin-top: 30px 69 | margin-bottom: 0 70 | font-size: 60px 71 | font-weight: normal 72 | 73 | .blog-description 74 | font-size: 20px 75 | color: #999 76 | 77 | 78 | 79 | /* 80 | * Main column and sidebar layout 81 | */ 82 | 83 | .blog-main 84 | font-size: 18px 85 | line-height: 1.5 86 | 87 | 88 | /* Sidebar modules for boxing content */ 89 | .sidebar-module 90 | padding: 15px 91 | margin: 0 -15px 15px 92 | 93 | .sidebar-module-inset 94 | padding: 15px 95 | background-color: #f5f5f5 96 | border-radius: 4px 97 | 98 | p:last-child, 99 | ul:last-child, 100 | ol:last-child 101 | margin-bottom: 0 102 | 103 | 104 | 105 | /* Pagination */ 106 | .pager 107 | margin-bottom: 60px 108 | text-align: left 109 | 110 | li > a 111 | width: 140px 112 | padding: 10px 20px 113 | text-align: center 114 | border-radius: 30px 115 | 116 | 117 | 118 | /* 119 | * Blog posts 120 | */ 121 | 122 | .blog-post 123 | margin-bottom: 60px 124 | 125 | .blog-post-title 126 | margin-bottom: 5px 127 | font-size: 40px 128 | 129 | .blog-post-meta 130 | margin-bottom: 20px 131 | color: #999 132 | 133 | 134 | 135 | /* 136 | * Footer 137 | */ 138 | 139 | .blog-footer 140 | padding: 40px 0 141 | color: #999 142 | text-align: center 143 | background-color: #f9f9f9 144 | border-top: 1px solid #e5e5e5 145 | 146 | p:last-child 147 | margin-bottom: 0 148 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Georgia, "Times New Roman", Times, serif; 3 | color: #555; 4 | } 5 | h1, 6 | .h1, 7 | h2, 8 | .h2, 9 | h3, 10 | .h3, 11 | h4, 12 | .h4, 13 | h5, 14 | .h5, 15 | h6, 16 | .h6 { 17 | margin-top: 0; 18 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 19 | font-weight: normal; 20 | color: #333; 21 | } 22 | @media (min-width: 1200px) { 23 | .container { 24 | width: 970px; 25 | } 26 | } 27 | .blog-masthead { 28 | background-color: #428bca; 29 | -webkit-box-shadow: inset 0 -2px 5px rgba(0,0,0,0.1); 30 | box-shadow: inset 0 -2px 5px rgba(0,0,0,0.1); 31 | } 32 | .blog-nav-item { 33 | position: relative; 34 | display: inline-block; 35 | padding: 10px; 36 | font-weight: 500; 37 | color: #cdddeb; 38 | } 39 | .blog-nav-item :hover, 40 | .blog-nav-item :focus { 41 | color: #fff; 42 | text-decoration: none; 43 | } 44 | .blog-nav .active { 45 | color: #fff; 46 | } 47 | .blog-nav .active:after { 48 | position: absolute; 49 | bottom: 0; 50 | left: 50%; 51 | width: 0; 52 | height: 0; 53 | margin-left: -5px; 54 | vertical-align: middle; 55 | content: " "; 56 | border-right: 5px solid transparent; 57 | border-bottom: 5px solid; 58 | border-left: 5px solid transparent; 59 | } 60 | /* 61 | * Blog name and description 62 | */ 63 | .blog-header { 64 | padding-top: 20px; 65 | padding-bottom: 20px; 66 | } 67 | .blog-title { 68 | margin-top: 30px; 69 | margin-bottom: 0; 70 | font-size: 60px; 71 | font-weight: normal; 72 | } 73 | .blog-description { 74 | font-size: 20px; 75 | color: #999; 76 | } 77 | /* 78 | * Main column and sidebar layout 79 | */ 80 | .blog-main { 81 | font-size: 18px; 82 | line-height: 1.5; 83 | } 84 | /* Sidebar modules for boxing content */ 85 | .sidebar-module { 86 | padding: 15px; 87 | margin: 0 -15px 15px; 88 | } 89 | .sidebar-module-inset { 90 | padding: 15px; 91 | background-color: #f5f5f5; 92 | border-radius: 4px; 93 | } 94 | .sidebar-module-inset p:last-child, 95 | .sidebar-module-inset ul:last-child, 96 | .sidebar-module-inset ol:last-child { 97 | margin-bottom: 0; 98 | } 99 | /* Pagination */ 100 | .pager { 101 | margin-bottom: 60px; 102 | text-align: left; 103 | } 104 | .pager li > a { 105 | width: 140px; 106 | padding: 10px 20px; 107 | text-align: center; 108 | border-radius: 30px; 109 | } 110 | /* 111 | * Blog posts 112 | */ 113 | .blog-post { 114 | margin-bottom: 60px; 115 | } 116 | .blog-post-title { 117 | margin-bottom: 5px; 118 | font-size: 40px; 119 | } 120 | .blog-post-meta { 121 | margin-bottom: 20px; 122 | color: #999; 123 | } 124 | /* 125 | * Footer 126 | */ 127 | .blog-footer { 128 | padding: 40px 0; 129 | color: #999; 130 | text-align: center; 131 | background-color: #f9f9f9; 132 | border-top: 1px solid #e5e5e5; 133 | } 134 | .blog-footer p:last-child { 135 | margin-bottom: 0; 136 | } 137 | -------------------------------------------------------------------------------- /services/blog.js: -------------------------------------------------------------------------------- 1 | var cf = require('./cf'); 2 | var dateFormat = require('dateformat'); 3 | var md = require('marked'); 4 | var Q = require('q'); 5 | 6 | module.exports = { 7 | 8 | /** 9 | * Retrieves a list of blog posts. 10 | * @param {number} limit The number of posts to retrieve. 11 | * @return {Promise} The promise called once the posts are fetched an ready. 12 | * This promise will take the main post list as a parameter for the 13 | * resolve call. 14 | */ 15 | getPostsList: function(limit) { 16 | var deferred = Q.defer(); 17 | cf.api.getEntries({ 18 | 'content_type': cf.const.POST_CT, 19 | 'limit': limit 20 | }).then(function (response) { 21 | var posts = []; 22 | if (response.total > 0) { 23 | posts = this.formatPosts_(response.items); 24 | } 25 | deferred.resolve(posts); 26 | }.bind(this), function(errorMsg) { 27 | console.error(errorMsg); 28 | deferred.reject(errorMsg); 29 | }.bind(this)); 30 | 31 | return deferred.promise; 32 | }, 33 | 34 | /** 35 | * Retrieves a blog post using the provided slug. 36 | * @param {string} slug The slug of the blog post to retrieve. 37 | * @return {Promise} The promise called once the post is fetched an ready. 38 | * This promise will take the main post object as a parameter for the 39 | * resolve call. 40 | */ 41 | getPostBySlug: function(slug) { 42 | var deferred = Q.defer(); 43 | cf.api.getEntries({ 44 | 'content_type': cf.const.POST_CT, 45 | 'fields.slug': slug 46 | }).then(function (entries) { 47 | if (entries.total == 0) { 48 | deferred.reject('Sorry, this blog post hasn\'t been found'); 49 | } else { 50 | deferred.resolve(this.formatPost_(entries.items[0])); 51 | } 52 | }.bind(this), function(errorMsg) { 53 | console.error(errorMsg); 54 | deferred.reject(errorMsg); 55 | }.bind(this)); 56 | return deferred.promise; 57 | }, 58 | 59 | /** 60 | * Fetches the list of categories. 61 | * @return {Promise} The promise which resolves with the list of fetched 62 | * categories. 63 | */ 64 | getCategories: function() { 65 | var deferred = Q.defer(); 66 | cf.api.getEntries({ 67 | 'content_type': cf.const.CAT_CT, 68 | 'include': 0 69 | }).then(function (response) { 70 | if (response.total == 0) { 71 | deferred.resolve([]); 72 | } else { 73 | deferred.resolve(response.items); 74 | } 75 | }.bind(this)); 76 | return deferred.promise; 77 | }, 78 | 79 | /** 80 | * Fetches the categories using its id. 81 | * @param {string} id The category id. 82 | * @return {Promise} The promise which resolves with the category. 83 | */ 84 | getCategoryWithPosts: function(id) { 85 | var deferred = Q.defer(); 86 | cf.api.getEntry(id).then(function (catResponse) { 87 | cf.api.getEntries({ 88 | 'content_type': cf.const.POST_CT, 89 | 'fields.category.sys.id': id 90 | }).then(function(postsResponse) { 91 | deferred.resolve({ 92 | category: catResponse, 93 | posts: this.formatPosts_(postsResponse.items) 94 | }); 95 | }.bind(this), function(errorMsg) { 96 | console.error(errorMsg); 97 | deferred.reject(errorMsg); 98 | }); 99 | return deferred.promise; 100 | }.bind(this), function(errorMsg) { 101 | console.error(errorMsg); 102 | deferred.reject(errorMsg); 103 | }.bind(this)); 104 | return deferred.promise; 105 | }, 106 | 107 | /** 108 | * Formats a list of post objects returned by the Contentful API to ease their 109 | * use in the subsequent views. 110 | * @param {array} posts The list of post objects to format. 111 | * @return {array} The formatted blog post object list. 112 | */ 113 | formatPosts_: function(posts) { 114 | var result = []; 115 | console.log(require('util').inspect(posts, { depth: null })); 116 | for (var i = 0; i < posts.length; i++) { 117 | var post = posts[i]; 118 | result.push({ 119 | title: post.fields.title, 120 | slug: post.fields.slug, 121 | featuredImage: post.fields.featuredImage ? post.fields.featuredImage.fields : null, 122 | plot: post.fields.body.substr(0, 150) + '...', 123 | date: dateFormat(post.fields.date, 'fullDate'), 124 | categories: post.fields.category, 125 | author: { 126 | name: post.fields.author[0].fields.name, 127 | website: post.fields.author[0].fields.website 128 | } 129 | }); 130 | } 131 | return result; 132 | }, 133 | 134 | /** 135 | * Formats a post objects returned by the Contentful API to ease its use in 136 | * the subsequent views. 137 | * @return {object} The formatted blog post object. 138 | */ 139 | formatPost_: function(post) { 140 | return { 141 | title: post.fields.title, 142 | featuredImage: post.fields.featuredImage ? post.fields.featuredImage.fields : null, 143 | body: md(post.fields.body), 144 | date: dateFormat(post.fields.date, 'fullDate'), 145 | categories: post.fields.category, 146 | author: { 147 | name: post.fields.author[0].fields.name, 148 | website: post.fields.author[0].fields.website 149 | } 150 | }; 151 | } 152 | }; 153 | --------------------------------------------------------------------------------