├── .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 | 
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 |
--------------------------------------------------------------------------------