├── views ├── footer.ejs ├── links.ejs ├── tags.ejs ├── 404.ejs ├── paging.ejs ├── login.ejs ├── upload.ejs ├── post.ejs ├── archive.ejs ├── search.ejs ├── tag.ejs ├── reg.ejs ├── edit.ejs ├── header.ejs ├── comment.ejs ├── index.ejs ├── user.ejs └── article.ejs ├── .gitignore ├── public ├── images │ └── favicon.ico └── stylesheets │ └── style.css ├── settings.js ├── models ├── db.js ├── comment.js ├── user.js └── post.js ├── package.json ├── app.js ├── README.md ├── routes └── index.js └── yarn.lock /views/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.github 4 | .idea 5 | -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hawx1993/node-blog/master/public/images/favicon.ico -------------------------------------------------------------------------------- /settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cookieSecret: 'myblog', 3 | db: 'blog', 4 | host: 'localhost', 5 | port: 27017 6 | }; -------------------------------------------------------------------------------- /views/links.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 | 9 | <%- include footer %> 10 | -------------------------------------------------------------------------------- /views/tags.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 |

3 | <% posts.forEach(function (tag, index) { %> 4 | <%= tag %> 5 | <% }) %> 6 |

7 | <%- include footer %> -------------------------------------------------------------------------------- /views/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blog 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /models/db.js: -------------------------------------------------------------------------------- 1 | var settings = require('../settings'), 2 | Db = require('mongodb').Db, 3 | Connection = require('mongodb').Connection, 4 | Server = require('mongodb').Server; 5 | 6 | module.exports = new Db(settings.db, 7 | new Server(settings.host, settings.port), 8 | {safe: true} 9 | ); -------------------------------------------------------------------------------- /views/paging.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | <% if (!isFirstPage) { %> 4 | 上一页 5 | <% } %> 6 | 7 | <% if (!isLastPage) { %> 8 | 下一页 9 | <% } %> 10 |
11 | -------------------------------------------------------------------------------- /views/login.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 |
3 | 用户名:
4 | 密 码:
5 | 6 |
7 | <%- include footer %> -------------------------------------------------------------------------------- /views/upload.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 |
3 |
4 |
5 |
6 |
7 |
8 | 9 |
10 | <%- include footer %> -------------------------------------------------------------------------------- /views/post.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 |
3 |
标题:
4 |
5 |
标签:
6 |
7 |
正文:
8 |
9 | 10 |
11 | <%- include footer %> -------------------------------------------------------------------------------- /views/archive.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 | 12 | <%- include footer %> -------------------------------------------------------------------------------- /views/search.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 | 12 | <%- include footer %> -------------------------------------------------------------------------------- /views/tag.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 | 12 | <%- include footer %> -------------------------------------------------------------------------------- /views/reg.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 |
3 | 用 户 名:
4 | 密 码:
5 | 确认密码:
6 | 邮 箱:
7 | 8 |
9 | <%- include footer %> -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-blog", 3 | "version": "1.0.1", 4 | "private": true, 5 | "description": "blog for express4.x", 6 | "scripts": { 7 | "start": "node app" 8 | }, 9 | "dependencies": { 10 | "body-parser": "^1.17.2", 11 | "connect-flash": "0.1.1", 12 | "connect-mongo": "0.4.1", 13 | "cookie-parser": "1.3.3", 14 | "ejs": "1.0.0", 15 | "express": "^4.10.2", 16 | "express-session": "1.9.1", 17 | "markdown": "^0.5.0", 18 | "mongodb": "^2.2.31", 19 | "morgan": "1.3.1", 20 | "multer": "0.1.6", 21 | "serve-favicon": "^2.4.3" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /views/edit.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 |
3 | 标题:
4 |
5 | 标签:
6 | 7 | 8 |
9 | 正文:
10 |
11 | 12 |
13 | <%- include footer %> -------------------------------------------------------------------------------- /views/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AlvIn's Blog 6 | 7 | 8 | 9 | 10 |
11 |

<%= title %>

12 |
13 | 14 | 28 |
29 | <% if (success) { %> 30 |
<%= success %>
31 | <% } %> 32 | <% if (error) { %> 33 |
<%= error %>
34 | <% } %> 35 | -------------------------------------------------------------------------------- /views/comment.ejs: -------------------------------------------------------------------------------- 1 |
2 | <% post.comments.forEach(function (comment, index) { %> 3 |
4 |

<%= comment.name %> 5 | 回复于 <%= comment.time %>

6 |

<%- comment.content %>

7 |
8 | <% }) %> 9 | 10 |
11 | <% if (user) { %> 12 | 姓名:
13 | 邮箱:
14 | 网址:

15 | <% } else { %> 16 | 姓名:
17 | 邮箱:
18 | 网址:

19 | <% } %> 20 |
21 | 22 |
-------------------------------------------------------------------------------- /models/comment.js: -------------------------------------------------------------------------------- 1 | const mongodb = require('./db'); 2 | 3 | function Comment(name, day, title, comment) { 4 | this.name = name; 5 | this.day = day; 6 | this.title = title; 7 | this.comment = comment; 8 | } 9 | 10 | module.exports = Comment; 11 | 12 | //存储一条留言信息 13 | Comment.prototype.save = function(callback) { 14 | var name = this.name, 15 | day = this.day, 16 | title = this.title, 17 | comment = this.comment; 18 | //打开数据库 19 | mongodb.open(function (err, db) { 20 | if (err) { 21 | return callback(err); 22 | } 23 | //读取 posts 集合 24 | db.collection('posts', function (err, collection) { 25 | if (err) { 26 | mongodb.close(); 27 | return callback(err); 28 | } 29 | //通过用户名、时间及标题查找文档,并把一条留言对象添加到该文档的 comments 数组里 30 | collection.update({ 31 | "name": name, 32 | "time.day": day, 33 | "title": title 34 | }, { 35 | $push: {"comments": comment} 36 | } , function (err) { 37 | mongodb.close(); 38 | if (err) { 39 | return callback(err); 40 | } 41 | callback(null); 42 | }); 43 | }); 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 | <% posts.forEach(function (post, index) { %> 3 |

<%= post.title %>

4 |

5 |

6 | 作者:<%= post.name %> | 7 | 日期:<%= post.time.minute %> | 8 | 标签: 9 | <% post.tags.forEach(function (tag, index) { %> 10 | <% if (tag) { %> 11 | <%= tag %> 12 | <% } %> 13 | <% }) %> 14 | <% if (post.reprint_info.reprint_from) { %> 15 |
原文链接 16 | <% } %> 17 |

18 |

<%- post.post %>

19 |

20 | 阅读:<%= post.pv %> | 21 | 评论:<%= post.comments.length %> | 22 | 转载: 23 | <% if (post.reprint_info.reprint_to) { %> 24 | <%= post.reprint_info.reprint_to.length %> 25 | <% } else { %> 26 | <%= 0 %> 27 | <% } %> 28 |

29 | <% }) %> 30 | <%- include paging %> 31 | <%- include footer %> -------------------------------------------------------------------------------- /views/user.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 | <% posts.forEach(function (post, index) { %> 3 |

4 |

<%= post.title %>

5 | 6 |

7 |

8 | 作者:<%= post.name %> | 9 | 日期:<%= post.time.minute %> | 10 | 标签: 11 | <% post.tags.forEach(function (tag, index) { %> 12 | <% if (tag) { %> 13 | <%= tag %> 14 | <% } %> 15 | <% }) %> 16 | <% if (post.reprint_info.reprint_from) { %> 17 |
原文链接 18 | <% } %> 19 |

20 |

<%- post.post %>

21 |

22 | 阅读:<%= post.pv %> | 23 | 评论:<%= post.comments.length %> | 24 | 转载: 25 | <% if (post.reprint_info.reprint_to) { %> 26 | <%= post.reprint_info.reprint_to.length %> 27 | <% } else { %> 28 | <%= 0 %> 29 | <% } %> 30 |

31 | <% }) %> 32 | <%- include paging %> 33 | <%- include footer %> -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const express = require('express'); 4 | const favicon = require('serve-favicon'); 5 | const logger = require('morgan');//打印nodejs 服务器接受到的请求的信息。 6 | const cookieParser = require('cookie-parser');//获取web浏览器发送的cookie中的内容 7 | const bodyParser = require('body-parser');//解析http请求体 8 | const session = require('express-session'); 9 | const MongoStore = require('connect-mongo')(session); 10 | const flash = require('connect-flash');//页面通知中间件 11 | const multer = require('multer');// Express文件上传 12 | 13 | const routes = require('./routes/index'); 14 | const settings = require('./settings'); 15 | 16 | const fs = require('fs'); 17 | const accessLog = fs.createWriteStream('access.log', {flags: 'a'}); 18 | const errorLog = fs.createWriteStream('error.log', {flags: 'a'}); 19 | 20 | const app = express(); 21 | 22 | app.set('port', process.env.PORT || 3000); 23 | app.set('views', path.join(__dirname, 'views')); 24 | app.set('view engine', 'ejs'); 25 | app.use(favicon(__dirname + '/public/images/favicon.ico')); 26 | app.use(logger('dev')); 27 | app.use(logger({stream: accessLog})); 28 | app.use(bodyParser.urlencoded({ extended: false })); 29 | 30 | app.use(cookieParser()); 31 | app.use(session({ 32 | secret: settings.cookieSecret, 33 | key: settings.db,//cookie name 34 | cookie: {maxAge: 1000 * 60 * 60 * 24 * 30}//30 days 35 | })); 36 | app.use(flash()); 37 | app.use(express.static(path.join(__dirname, 'public'))); 38 | 39 | routes(app); 40 | 41 | app.use(function (err, req, res, next) { 42 | var meta = '[' + new Date() + '] ' + req.url + '\n'; 43 | errorLog.write(meta + err.stack + '\n'); 44 | next(); 45 | }); 46 | 47 | app.listen(app.get('port'), function(){ 48 | console.log('Express server listening on port ' + app.get('port')); 49 | }); -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | const mongodb = require('./db'); 2 | const crypto = require('crypto'); 3 | 4 | function User(user) { 5 | this.name = user.name; 6 | this.password = user.password; 7 | this.email = user.email; 8 | } 9 | 10 | module.exports = User; 11 | 12 | //存储用户信息 13 | User.prototype.save = function(callback) { 14 | var md5 = crypto.createHash('md5'), 15 | head = "./../public/images/favicon.ico"; 16 | //要存入数据库的用户信息文档 17 | var user = { 18 | name: this.name, 19 | password: this.password, 20 | email: this.email, 21 | head: head 22 | }; 23 | //打开数据库 24 | mongodb.open(function (err, db) { 25 | if (err) { 26 | return callback(err);//错误,返回 err 信息 27 | } 28 | //读取 users 集合 29 | db.collection('users', function (err, collection) { 30 | if (err) { 31 | mongodb.close(); 32 | return callback(err);//错误,返回 err 信息 33 | } 34 | //将用户数据插入 users 集合 35 | collection.insert(user, { 36 | safe: true 37 | }, function (err, user) { 38 | mongodb.close(); 39 | if (err) { 40 | return callback(err); 41 | } 42 | callback(null, user[0]);//成功!err 为 null,并返回存储后的用户文档 43 | }); 44 | }); 45 | }); 46 | }; 47 | 48 | //读取用户信息 49 | User.get = function(name, callback) { 50 | //打开数据库 51 | mongodb.open(function (err, db) { 52 | if (err) { 53 | return callback(err);//错误,返回 err 信息 54 | } 55 | //读取 users 集合 56 | db.collection('users', function (err, collection) { 57 | if (err) { 58 | mongodb.close(); 59 | return callback(err);//错误,返回 err 信息 60 | } 61 | //查找用户名(name键)值为 name 一个文档 62 | collection.findOne({ 63 | name: name 64 | }, function (err, user) { 65 | mongodb.close(); 66 | if (err) { 67 | return callback(err);//失败!返回 err 68 | } 69 | callback(null, user);//成功!返回查询的用户信息 70 | }); 71 | }); 72 | }); 73 | }; 74 | -------------------------------------------------------------------------------- /views/article.ejs: -------------------------------------------------------------------------------- 1 | <%- include header %> 2 |

3 | <% if (user && (user.name == post.name)) { %> 4 | 编辑 5 | 删除 6 | <% } %> 7 | <% var flag = 1 %> 8 | <% if (user && (user.name != post.name)) { %> 9 | <% if ((post.reprint_info.reprint_from != undefined) && (user.name == post.reprint_info.reprint_from.name)) { %> 10 | <% flag = 0 %> 11 | <% } %> 12 | <% if ((post.reprint_info.reprint_to != undefined)) { %> 13 | <% post.reprint_info.reprint_to.forEach(function (reprint_to, index) { %> 14 | <% if (user.name == reprint_to.name) { %> 15 | <% flag = 0 %> 16 | <% } %> 17 | <% }) %> 18 | <% } %> 19 | <% } else { %> 20 | <% flag = 0 %> 21 | <% } %> 22 | <% if (flag) { %> 23 | 转载 24 | <% } %> 25 |

26 |

27 | 作者:<%= post.name %> | 28 | 日期:<%= post.time.minute %> | 29 | 标签: 30 | <% post.tags.forEach(function (tag, index) { %> 31 | <% if (tag) { %> 32 | <%= tag %> 33 | <% } %> 34 | <% }) %> 35 | <% if (post.reprint_info.reprint_from) { %> 36 |
原文链接 37 | <% } %> 38 |

39 |

<%- post.post %>

40 |

41 | 阅读:<%= post.pv %> | 42 | 评论:<%= post.comments.length %> | 43 | 转载: 44 | <% if (post.reprint_info.reprint_to) { %> 45 | <%= post.reprint_info.reprint_to.length %> 46 | <% } else { %> 47 | <%= 0 %> 48 | <% } %> 49 |

50 | <%- include comment %> 51 | <%- include footer %> -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | *{padding:0;margin:0;} 2 | body{width:600px;margin:2em auto;padding:0 2em;font-size:14px;font-family:"Microsoft YaHei";} 3 | p{line-height:24px;margin:1em 0;} 4 | header{padding:.5em 0;border-bottom:1px solid #ccc;} 5 | nav{float:left;font-family:"Microsoft YaHei";font-size:1.1em;text-transform:uppercase;margin-left:-12em;width:3em;text-align:right;} 6 | nav a{display:block;text-decoration:none;padding:.7em 0.2em;color:#000;} 7 | nav a:hover{border-bottom: 1px solid black} 8 | article{font-size:16px;padding-top:.5em;} 9 | article a{color:#d00;text-decoration:none;} 10 | article a:hover{color:#333;text-decoration:underline;} 11 | .info{font-size:14px;} 12 | .edit{margin:3px;padding:2px 5px;border-radius:3px;background-color:#f3f3f3;color:#333;font-size:13px;} 13 | .edit:hover{text-decoration:none;background-color:#f00;color:#fff;-webkit-transition:color .2s linear;} 14 | .prepage a{float:left;text-decoration:none;padding:.5em 1em;color:#f00;font-weight:bold;} 15 | .nextpage a{float:right;text-decoration:none;padding:.5em 1em;color:#f00;font-weight:bold;} 16 | .prepage a:hover,.nextpage a:hover{text-decoration:none;background-color:#f00;color:#f9f9f9;-webkit-transition:color .2s linear;} 17 | .archive{list-style:none;line-height:28px;} 18 | .archive h3{margin:0.5em 0;} 19 | .archive time{float:left;font-size:14px;color:#999999;margin-right:1.2em;} 20 | .tag{background-color:#ff0000;border-radius:3px;font-size:14px;color:#ffffff;display:inline-block;padding:0 5px;margin-bottom:8px;} 21 | .tag:hover{text-decoration:none;background-color:#ffffff;color:#000000;-webkit-transition:color .2s linear;} 22 | .search{border:0;width:6em;text-align:center;font-size:1em;margin:0.5em 0;} 23 | .l_head{float:left;margin-left:-4em;box-shadow:0px 1px 4px #888;} 24 | .r_head{float:right;margin-top:-2.5em;box-shadow:0px 1px 4px #888;} 25 | .p_title{ 26 | width: 500px; 27 | outline: none; 28 | height: 30px; 29 | font-size: 20px; 30 | } 31 | .postBtn{ 32 | margin-top: 20px; 33 | height: auto; 34 | padding: 11px 66px; 35 | line-height: 18px; 36 | font-size: 16px; 37 | background-color: #ff635c; 38 | border-radius: 4px; 39 | color: #fff; 40 | border: 0; 41 | outline: 0; 42 | cursor: pointer; 43 | box-shadow: 0 2px 2px rgba(14,5,9,.16); 44 | margin-left: 220px; 45 | } 46 | textarea{ 47 | font-size: 18px; 48 | outline: none; 49 | width: 620px; 50 | height: 240px; 51 | } 52 | input{outline: none} 53 | .username,.password{ 54 | width: 300px; 55 | height: 30px; 56 | margin-top: 30px; 57 | margin-left: 20px; 58 | border-color: transparent; 59 | background: #f3f3f3; 60 | padding-left: 10px; 61 | } 62 | .pwd{ 63 | display: inline-block; 64 | white-space: pre-wrap; 65 | } 66 | .reg{ 67 | margin-top: 20px; 68 | height: auto; 69 | padding: 11px 66px; 70 | line-height: 18px; 71 | font-size: 16px; 72 | background-color: #ff635c; 73 | border-radius: 4px; 74 | color: #fff; 75 | border: 0; 76 | outline: 0; 77 | cursor: pointer; 78 | box-shadow: 0 2px 2px rgba(14,5,9,.16); 79 | margin-left: 150px; 80 | } 81 | .messages{ 82 | margin-top: 20px; 83 | height: auto; 84 | padding: 11px 66px; 85 | line-height: 18px; 86 | font-size: 16px; 87 | background-color: #ff635c; 88 | border-radius: 4px; 89 | color: #fff; 90 | border: 0; 91 | outline: 0; 92 | cursor: pointer; 93 | box-shadow: 0 2px 2px rgba(14,5,9,.16); 94 | margin-left: 250px; 95 | } 96 | .name{ 97 | width: 200px; 98 | height: 15px; 99 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 使用Node.js可以做什么 2 | 3 | Web开发:`Express + EJS + Mongoose` 4 | 5 | ``` 6 | Express 是轻量灵活的Nodejs Web应用框架,它可以快速地搭建网站。 7 | Express框架建立在Nodejs内置的Http模块上,并对Http模块再包装,从而实际Web请求处理的功能。 8 | 9 | ejs是一个嵌入的Javascript模板引擎,通过编译生成HTML的代码。 10 | 11 | mongoose 是MongoDB的对象模型工具,通过Mongoose框架,可以进行访问MongoDB的操作。 12 | ``` 13 | mysql 是连接MySQL数据库的通信API,可以进行访问MySQL的操作。 14 | 通常用Node.js做Web开发,需要3个框架配合使用,就像Java中的SSH。 15 | 16 | ### `Express` 17 | 18 | `Express`框架是一款基于 Node.js 平台,实现快速开发、极简的 web 开发框架。它提供一系列强大的特性,可以帮助你创建各种 Web 和移动设备应用。可以通过执行如下命令安装: 19 | 20 | ```js 21 | $ npm install express --save 22 | ``` 23 | 24 | ### MongoDB 25 | 26 | `MongoDB` 是一个跨平台的,面向文档的非关系型数据库,具有高性能,高可用性和可扩展性方便等优点。他支持的数据结构非常松散,是类似于json的格式,因此可以存储比较复杂的数据类型。 27 | 28 | 在MongoDB里,数据模型是这样的: 29 | ``` 30 | 1):Mongo系统含有多个数据库 31 | 2)数据库汇总含有多个集合 32 | 3)一个集合含有多个文件 33 | 4)一个文件还有多个域 34 | 5)一个域含有多个键/值对 35 | 7) 一个值可以是:基本数据类型,如string,integer,float,时间戳,二进制,一个文件,一个数组 36 | ``` 37 | ### Mongoose 38 | 39 | `Mongoose`是在Node环境中操作`MongoDB`数据库的一种便捷封装,是一种对象模型工具,类似于`MySQL`的ORM。`Mongoose`将数据库中的数据转换为`JavaScript`对象以供你在应用中使用。 40 | 41 | 更多详情可以浏览器官方主页:http://www.nodeclass.com/api/mongoose.html 42 | 43 | ### ejs模板引擎 44 | 45 | 模板引擎(Template Engine)是一个将页面模板和要显示的数据结合起来生成 HTML 页面的工具。它既可以运 行在服务器端又可以运行在客户端,大多数时候它都在服务器端直接被解析为 HTML,解析完成后再传输给客户端,因此客户端甚至无法判断页面是否是模板引擎生成的。 46 | 47 | ejs 是模板引擎的一种, 可以将数据和模板合并然后生成 HTML 文本。因为它使用起来十分简单,而且与 express 集成良好。所以非常适合用来做Node系统的模板引擎。 48 | 49 | 安装ejs: 50 | 51 | ```js 52 | npm install ejs 53 | ``` 54 | 55 | ejs 的标签系统非常简单,它只有以下三种标签: 56 | 57 | ```js 58 | <% code %>:JavaScript 代码。 59 | <%= code %>:显示替换过 HTML 特殊字符的内容。 60 | <%- code %>:显示原始 HTML 内容。 61 | ``` 62 | 63 | ## 开发环境搭建 64 | ```js 65 | Node.js版本:v5.9.0 66 | Express版本:v4.13.1 67 | MongoDB版本:v3.2.0 68 | ``` 69 | 70 | ### 在Mac系统上安装Node.js 71 | 72 | `Node.js`是一个内嵌在Chrome的V8 js 引擎上,Node.js使用一个事件驱动,非阻塞`I/O`模型。安装Node.js可以到官网去下载:https://nodejs.org/en/ 73 | 74 | 安装完后在Mac终端输入`$ node -v `,当看到`v4.2.4`这样的版本号表示安装成功。 75 | 76 | 77 | ### 安装`Express`框架 78 | 首先在系统根目录新建一个名为‘Nblog’的的文件夹,然后在该文件夹下安装`Express`框架,执行如下命令: 79 | ``` 80 | $ sudo npm install -g express-generator 81 | $ express -e 82 | $ npm install 83 | ``` 84 | 运行该app输入:`$ DEBUG=N-blog:* npm start`,成功后,打开浏览器输入localhost:3000,看到如下图所示说明安装成功: 85 | 86 | ![](img/express-start.png) 87 | 88 | 成功后,我们来看一下该项目的主要目录结构: 89 | 90 | ![](img/mulu.png) 91 | 92 | 其中: 93 | ``` 94 | app.js:启动文件,或者说入口文件 95 | package.json:存储着工程的信息及模块依赖,当在 dependencies 中添加依赖的模块时,运行 npm install,npm 会检查当前目录下的 package.json,并自动安装所有指定的模块 96 | node_modules:存放 package.json 中安装的模块,当你在 package.json 添加依赖的模块并安装后,存放在这个文件夹下 97 | public:存放 image、css、js 等文件 98 | routes:存放路由文件 99 | views:存放视图文件或者说模版文件 100 | bin:存放可执行文件 101 | ``` 102 | 103 | ### 安装MongoDB数据库 104 | 首先安装`homebrew`,通过打开Mac终端输入如下命令行: 105 | ``` 106 | $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 107 | ``` 108 | 有关homebrew的使用详情请见官方文档:http://brew.sh/ 109 | 110 | 然后通过命令行 `$ brew install mongodb `来安装`MongoDB`数据库,这里我们下载了`mongodb-osx-x86_64-3.2.0`文件,通过如下命令行解压相关文件: 111 | ``` 112 | $ tar -zxvf mongodb-osx-x86_64-3.0.7.tgz 113 | ``` 114 | 这时我们可以在根目录看到相应的解压的文件夹,然后通过命令将其移动到另外一个文件夹: 115 | 116 | ``` 117 | $ mv -n ~/mongodb-osx-x86_64-3.2.0 ~/usr/local 118 | ``` 119 | 在根目录 `/ `下创建 `data/db` 目录,用于放置`mongodb`数据,并且给该目录设置权限 120 | 121 | ``` 122 | sudo mkdir -p /data/db 123 | sudo chown -R trigkit4 /data 124 | ``` 125 | 其中,`trigkit4`是我电脑的用户名 126 | 127 | 运行`MongoDB`,只需要在终端输入`mongod`命令,然后打开另一个终端窗口输入`mongo`,默认运行在:http://127.0.0.1:27017/ 128 | 129 | 130 | ### Mongoose的安装和使用 131 | 132 | `Mongoose` 是一款`MongoDB` 对象模型工具被用于异步环境中。 133 | 134 | >`github`托管地址:https://github.com/Automattic/mongoose 135 | 136 | 137 | 138 | 首先新建一个名为`mongo`的文件夹,该文件夹就是数据库目录。然后在该目录下安装`mongoose`: 139 | 140 | ``` 141 | $ npm install mongoose 142 | ``` 143 | 144 | 145 | ## 博客功能分析 146 | 147 | 我们要搭建的是一款简单的具有多人注册、登录、发表文章、登出功能的基于Node.js的博客。 148 | 149 | >设计目标 150 | 151 | 未登录:主页左侧导航显示 home、login、register,右侧显示已发表的文章、发表日期及作者。 152 | 登陆后:主页左侧导航显示 home、post、logout,右侧显示已发表的文章、发表日期及作者。 153 | 用户登录、注册、发表成功以及登出后都返回到主页。 154 | 155 | >路由规划 156 | 157 | ``` 158 | / :首页 159 | /login :用户登录 160 | /reg :用户注册 161 | /post :发表文章 162 | /logout :登出 163 | ``` 164 | 我们要求 `/login` 和 `/reg` 只能是未登录的用户访问,而 `/post` 和 `/logout` 只能是已登录的用户访问。 165 | 166 | 167 | 168 | Sponsor 169 | 170 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const fs = require('fs'); 3 | const User = require('../models/user.js'); 4 | const Post = require('../models/post.js'); 5 | const Comment = require('../models/comment.js'); 6 | 7 | module.exports = function(app) { 8 | app.get('/', function (req, res) { 9 | //判断是否是第一页,并把请求的页数转换成 number 类型 10 | var page = req.query.p ? parseInt(req.query.p) : 1; 11 | //查询并返回第 page 页的 10 篇文章 12 | Post.getTen(null, page, function (err, posts, total) { 13 | if (err) { 14 | posts = []; 15 | } 16 | res.render('index', { 17 | title: '主页', 18 | posts: posts, 19 | page: page, 20 | isFirstPage: (page - 1) === 0, 21 | isLastPage: ((page - 1) * 10 + posts.length) === total, 22 | user: req.session.user, 23 | success: req.flash('success').toString(), 24 | error: req.flash('error').toString() 25 | }); 26 | }); 27 | }); 28 | 29 | app.get('/reg', checkNotLogin); 30 | app.get('/reg', function (req, res) { 31 | res.render('reg', { 32 | title: '注册', 33 | user: req.session.user, 34 | success: req.flash('success').toString(), 35 | error: req.flash('error').toString() 36 | }); 37 | }); 38 | 39 | app.post('/reg', checkNotLogin); 40 | app.post('/reg', function (req, res) { 41 | var password = req.body.password, 42 | password_re = req.body['password-repeat']; 43 | //检验用户两次输入的密码是否一致 44 | if (password_re !== password) { 45 | req.flash('error', '两次输入的密码不一致!'); 46 | return res.redirect('/reg');//返回主册页 47 | } 48 | //生成密码的 md5 值 49 | var md5 = crypto.createHash('md5'); 50 | password = md5.update(req.body.password).digest('hex'); 51 | var newUser = new User({ 52 | name: req.body.name, 53 | password: password, 54 | email: req.body.email 55 | }); 56 | //检查用户名是否已经存在 57 | User.get(newUser.name, function (err, user) { 58 | if (user) { 59 | req.flash('error', '用户已存在!'); 60 | return res.redirect('/reg');//返回注册页 61 | } 62 | //如果不存在则新增用户 63 | newUser.save(function (err, user) { 64 | if (err) { 65 | req.flash('error', err); 66 | return res.redirect('/reg');//注册失败返回主册页 67 | } 68 | req.session.user = user;//用户信息存入 session 69 | req.flash('success', '注册成功!'); 70 | res.redirect('/');//注册成功后返回主页 71 | }); 72 | }); 73 | }); 74 | 75 | app.get('/login', checkNotLogin); 76 | app.get('/login', function (req, res) { 77 | res.render('login', { 78 | title: '登录', 79 | user: req.session.user, 80 | success: req.flash('success').toString(), 81 | error: req.flash('error').toString() 82 | }); 83 | }); 84 | 85 | app.post('/login', checkNotLogin); 86 | app.post('/login', function (req, res) { 87 | //生成密码的 md5 值 88 | var md5 = crypto.createHash('md5'), 89 | password = md5.update(req.body.password).digest('hex'); 90 | //检查用户是否存在 91 | User.get(req.body.name, function (err, user) { 92 | if (!user) { 93 | req.flash('error', '用户不存在!'); 94 | return res.redirect('/login');//用户不存在则跳转到登录页 95 | } 96 | //检查密码是否一致 97 | if (user.password !== password) { 98 | req.flash('error', '密码错误!'); 99 | return res.redirect('/login');//密码错误则跳转到登录页 100 | } 101 | //用户名密码都匹配后,将用户信息存入 session 102 | req.session.user = user; 103 | req.flash('success', '登陆成功!'); 104 | res.redirect('/');//登陆成功后跳转到主页 105 | }); 106 | }); 107 | 108 | app.get('/post', checkLogin); 109 | app.get('/post', function (req, res) { 110 | res.render('post', { 111 | title: '发表', 112 | user: req.session.user, 113 | success: req.flash('success').toString(), 114 | error: req.flash('error').toString() 115 | }); 116 | }); 117 | 118 | app.post('/post', checkLogin); 119 | app.post('/post', function (req, res) { 120 | var currentUser = req.session.user, 121 | tags = [req.body.tag1, req.body.tag2, req.body.tag3], 122 | post = new Post(currentUser.name, currentUser.head, req.body.title, tags, req.body.post); 123 | post.save(function (err) { 124 | if (err) { 125 | req.flash('error', err); 126 | return res.redirect('/'); 127 | } 128 | req.flash('success', '发布成功!'); 129 | res.redirect('/');//发表成功跳转到主页 130 | }); 131 | }); 132 | 133 | app.get('/logout', checkLogin); 134 | app.get('/logout', function (req, res) { 135 | req.session.user = null; 136 | req.flash('success', '登出成功!'); 137 | res.redirect('/');//登出成功后跳转到主页 138 | }); 139 | 140 | app.get('/upload', checkLogin); 141 | app.get('/upload', function (req, res) { 142 | res.render('upload', { 143 | title: '文件上传', 144 | user: req.session.user, 145 | success: req.flash('success').toString(), 146 | error: req.flash('error').toString() 147 | }); 148 | }); 149 | 150 | app.post('/upload', checkLogin); 151 | app.post('/upload', function (req, res) { 152 | req.flash('success', '文件上传成功!'); 153 | res.redirect('/upload'); 154 | }); 155 | 156 | app.get('/archive', function (req, res) { 157 | Post.getArchive(function (err, posts) { 158 | if (err) { 159 | req.flash('error', err); 160 | return res.redirect('/'); 161 | } 162 | res.render('archive', { 163 | title: '存档', 164 | posts: posts, 165 | user: req.session.user, 166 | success: req.flash('success').toString(), 167 | error: req.flash('error').toString() 168 | }); 169 | }); 170 | }); 171 | 172 | app.get('/tags', function (req, res) { 173 | Post.getTags(function (err, posts) { 174 | if (err) { 175 | req.flash('error', err); 176 | return res.redirect('/'); 177 | } 178 | res.render('tags', { 179 | title: '标签', 180 | posts: posts, 181 | user: req.session.user, 182 | success: req.flash('success').toString(), 183 | error: req.flash('error').toString() 184 | }); 185 | }); 186 | }); 187 | 188 | app.get('/tags/:tag', function (req, res) { 189 | Post.getTag(req.params.tag, function (err, posts) { 190 | if (err) { 191 | req.flash('error',err); 192 | return res.redirect('/'); 193 | } 194 | res.render('tag', { 195 | title: 'TAG:' + req.params.tag, 196 | posts: posts, 197 | user: req.session.user, 198 | success: req.flash('success').toString(), 199 | error: req.flash('error').toString() 200 | }); 201 | }); 202 | }); 203 | 204 | app.get('/links', function (req, res) { 205 | res.render('links', { 206 | title: '友情链接', 207 | user: req.session.user, 208 | success: req.flash('success').toString(), 209 | error: req.flash('error').toString() 210 | }); 211 | }); 212 | 213 | app.get('/search', function (req, res) { 214 | Post.search(req.query.keyword, function (err, posts) { 215 | if (err) { 216 | req.flash('error', err); 217 | return res.redirect('/'); 218 | } 219 | res.render('search', { 220 | title: "SEARCH:" + req.query.keyword, 221 | posts: posts, 222 | user: req.session.user, 223 | success: req.flash('success').toString(), 224 | error: req.flash('error').toString() 225 | }); 226 | }); 227 | }); 228 | 229 | app.get('/u/:name', function (req, res) { 230 | var page = req.query.p ? parseInt(req.query.p) : 1; 231 | //检查用户是否存在 232 | User.get(req.params.name, function (err, user) { 233 | if (err) { 234 | req.flash('error', err); 235 | return res.redirect('/'); 236 | } 237 | if (!user) { 238 | req.flash('error', '用户不存在!'); 239 | return res.redirect('/'); 240 | } 241 | //查询并返回该用户第 page 页的 10 篇文章 242 | Post.getTen(user.name, page, function (err, posts, total) { 243 | if (err) { 244 | req.flash('error', err); 245 | return res.redirect('/'); 246 | } 247 | res.render('user', { 248 | title: user.name, 249 | posts: posts, 250 | page: page, 251 | isFirstPage: (page - 1) === 0, 252 | isLastPage: ((page - 1) * 10 + posts.length) === total, 253 | user: req.session.user, 254 | success: req.flash('success').toString(), 255 | error: req.flash('error').toString() 256 | }); 257 | }); 258 | }); 259 | }); 260 | 261 | app.get('/u/:name/:day/:title', function (req, res) { 262 | Post.getOne(req.params.name, req.params.day, req.params.title, function (err, post) { 263 | if (err) { 264 | req.flash('error', err); 265 | return res.redirect('/'); 266 | } 267 | res.render('article', { 268 | title: req.params.title, 269 | post: post, 270 | user: req.session.user, 271 | success: req.flash('success').toString(), 272 | error: req.flash('error').toString() 273 | }); 274 | }); 275 | }); 276 | 277 | app.post('/u/:name/:day/:title', function (req, res) { 278 | var date = new Date(), 279 | time = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + 280 | date.getHours() + ":" + (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()); 281 | var md5 = crypto.createHash('md5'), 282 | email_MD5 = md5.update(req.body.email.toLowerCase()).digest('hex'), 283 | head = "http://www.gravatar.com/avatar/" + email_MD5 + "?s=48"; 284 | var comment = { 285 | name: req.body.name, 286 | head: head, 287 | email: req.body.email, 288 | website: req.body.website, 289 | time: time, 290 | content: req.body.content 291 | }; 292 | var newComment = new Comment(req.params.name, req.params.day, req.params.title, comment); 293 | newComment.save(function (err) { 294 | if (err) { 295 | req.flash('error', err); 296 | return res.redirect('back'); 297 | } 298 | req.flash('success', '留言成功!'); 299 | res.redirect('back'); 300 | }); 301 | }); 302 | 303 | app.get('/edit/:name/:day/:title', checkLogin); 304 | app.get('/edit/:name/:day/:title', function (req, res) { 305 | var currentUser = req.session.user; 306 | Post.edit(currentUser.name, req.params.day, req.params.title, function (err, post) { 307 | if (err) { 308 | req.flash('error', err); 309 | return res.redirect('back'); 310 | } 311 | res.render('edit', { 312 | title: '编辑', 313 | post: post, 314 | user: req.session.user, 315 | success: req.flash('success').toString(), 316 | error: req.flash('error').toString() 317 | }); 318 | }); 319 | }); 320 | 321 | app.post('/edit/:name/:day/:title', checkLogin); 322 | app.post('/edit/:name/:day/:title', function (req, res) { 323 | var currentUser = req.session.user; 324 | Post.update(currentUser.name, req.params.day, req.params.title, req.body.post, function (err) { 325 | var url = encodeURI('/u/' + req.params.name + '/' + req.params.day + '/' + req.params.title); 326 | if (err) { 327 | req.flash('error', err); 328 | return res.redirect(url);//出错!返回文章页 329 | } 330 | req.flash('success', '修改成功!'); 331 | res.redirect(url);//成功!返回文章页 332 | }); 333 | }); 334 | 335 | app.get('/remove/:name/:day/:title', checkLogin); 336 | app.get('/remove/:name/:day/:title', function (req, res) { 337 | var currentUser = req.session.user; 338 | Post.remove(currentUser.name, req.params.day, req.params.title, function (err) { 339 | if (err) { 340 | req.flash('error', err); 341 | return res.redirect('back'); 342 | } 343 | req.flash('success', '删除成功!'); 344 | res.redirect('/'); 345 | }); 346 | }); 347 | 348 | app.get('/reprint/:name/:day/:title', checkLogin); 349 | app.get('/reprint/:name/:day/:title', function (req, res) { 350 | Post.edit(req.params.name, req.params.day, req.params.title, function (err, post) { 351 | if (err) { 352 | req.flash('error', err); 353 | return res.redirect('back'); 354 | } 355 | var currentUser = req.session.user, 356 | reprint_from = {name: post.name, day: post.time.day, title: post.title}, 357 | reprint_to = {name: currentUser.name, head: currentUser.head}; 358 | Post.reprint(reprint_from, reprint_to, function (err, post) { 359 | if (err) { 360 | req.flash('error', err); 361 | return res.redirect('back'); 362 | } 363 | req.flash('success', '转载成功!'); 364 | var url = encodeURI('/u/' + post.name + '/' + post.time.day + '/' + post.title); 365 | res.redirect(url); 366 | }); 367 | }); 368 | }); 369 | 370 | app.use(function (req, res) { 371 | res.render("404"); 372 | }); 373 | 374 | function checkLogin(req, res, next) { 375 | if (!req.session.user) { 376 | req.flash('error', '未登录!'); 377 | return res.redirect('/login'); 378 | } 379 | next(); 380 | } 381 | 382 | function checkNotLogin(req, res, next) { 383 | if (req.session.user) { 384 | req.flash('error', '已登录!'); 385 | return res.redirect('back');//返回之前的页面 386 | } 387 | next(); 388 | } 389 | }; 390 | -------------------------------------------------------------------------------- /models/post.js: -------------------------------------------------------------------------------- 1 | const mongodb = require('./db'); 2 | const markdown = require('markdown').markdown; 3 | 4 | function Post(name, head, title, tags, post) { 5 | this.name = name; 6 | this.head = head; 7 | this.title = title; 8 | this.tags = tags; 9 | this.post = post; 10 | } 11 | 12 | module.exports = Post; 13 | 14 | //存储一篇文章及其相关信息 15 | Post.prototype.save = function(callback) { 16 | let date = new Date(); 17 | //存储各种时间格式 18 | let time = { 19 | date: date, 20 | year : date.getFullYear(), 21 | month : date.getFullYear() + "-" + (date.getMonth() + 1), 22 | day : date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(), 23 | minute : date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + 24 | date.getHours() + ":" + (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) 25 | } 26 | //要存入数据库的文档 27 | const post = { 28 | name: this.name, 29 | head: this.head, 30 | time: time, 31 | title:this.title, 32 | tags: this.tags, 33 | post: this.post, 34 | comments: [], 35 | reprint_info: {}, 36 | pv: 0 37 | }; 38 | //打开数据库 39 | mongodb.open(function (err, db) { 40 | if (err) { 41 | return callback(err); 42 | } 43 | //读取 posts 集合 44 | db.collection('posts', function (err, collection) { 45 | if (err) { 46 | mongodb.close(); 47 | return callback(err); 48 | } 49 | //将文档插入 posts 集合 50 | collection.insert(post, { 51 | safe: true 52 | }, function (err) { 53 | mongodb.close(); 54 | if (err) { 55 | return callback(err);//失败!返回 err 56 | } 57 | callback(null);//返回 err 为 null 58 | }); 59 | }); 60 | }); 61 | }; 62 | 63 | //一次获取十篇文章 64 | Post.getTen = function(name, page, callback) { 65 | //打开数据库 66 | mongodb.open(function (err, db) { 67 | if (err) { 68 | return callback(err); 69 | } 70 | //读取 posts 集合 71 | db.collection('posts', function (err, collection) { 72 | if (err) { 73 | mongodb.close(); 74 | return callback(err); 75 | } 76 | var query = {}; 77 | if (name) { 78 | query.name = name; 79 | } 80 | //使用 count 返回特定查询的文档数 total 81 | collection.count(query, function (err, total) { 82 | //根据 query 对象查询,并跳过前 (page-1)*10 个结果,返回之后的 10 个结果 83 | collection.find(query, { 84 | skip: (page - 1)*10, 85 | limit: 10 86 | }).sort({ 87 | time: -1 88 | }).toArray(function (err, docs) { 89 | mongodb.close(); 90 | if (err) { 91 | return callback(err); 92 | } 93 | //解析 markdown 为 html 94 | docs.forEach(function (doc) { 95 | doc.post = markdown.toHTML(doc.post); 96 | }); 97 | callback(null, docs, total); 98 | }); 99 | }); 100 | }); 101 | }); 102 | }; 103 | 104 | //获取一篇文章 105 | Post.getOne = function(name, day, title, callback) { 106 | //打开数据库 107 | mongodb.open(function (err, db) { 108 | if (err) { 109 | return callback(err); 110 | } 111 | //读取 posts 集合 112 | db.collection('posts', function (err, collection) { 113 | if (err) { 114 | mongodb.close(); 115 | return callback(err); 116 | } 117 | //根据用户名、发表日期及文章名进行查询 118 | collection.findOne({ 119 | "name": name, 120 | "time.day": day, 121 | "title": title 122 | }, function (err, doc) { 123 | if (err) { 124 | mongodb.close(); 125 | return callback(err); 126 | } 127 | if (doc) { 128 | //每访问 1 次,pv 值增加 1 129 | collection.update({ 130 | "name": name, 131 | "time.day": day, 132 | "title": title 133 | }, { 134 | $inc: {"pv": 1} 135 | }, function (err) { 136 | mongodb.close(); 137 | if (err) { 138 | return callback(err); 139 | } 140 | }); 141 | //解析 markdown 为 html 142 | doc.post = markdown.toHTML(doc.post); 143 | doc.comments.forEach(function (comment) { 144 | comment.content = markdown.toHTML(comment.content); 145 | }); 146 | callback(null, doc);//返回查询的一篇文章 147 | } 148 | }); 149 | }); 150 | }); 151 | }; 152 | 153 | //返回原始发表的内容(markdown 格式) 154 | Post.edit = function(name, day, title, callback) { 155 | //打开数据库 156 | mongodb.open(function (err, db) { 157 | if (err) { 158 | return callback(err); 159 | } 160 | //读取 posts 集合 161 | db.collection('posts', function (err, collection) { 162 | if (err) { 163 | mongodb.close(); 164 | return callback(err); 165 | } 166 | //根据用户名、发表日期及文章名进行查询 167 | collection.findOne({ 168 | "name": name, 169 | "time.day": day, 170 | "title": title 171 | }, function (err, doc) { 172 | mongodb.close(); 173 | if (err) { 174 | return callback(err); 175 | } 176 | callback(null, doc);//返回查询的一篇文章(markdown 格式) 177 | }); 178 | }); 179 | }); 180 | }; 181 | 182 | //更新一篇文章及其相关信息 183 | Post.update = function(name, day, title, post, callback) { 184 | //打开数据库 185 | mongodb.open(function (err, db) { 186 | if (err) { 187 | return callback(err); 188 | } 189 | //读取 posts 集合 190 | db.collection('posts', function (err, collection) { 191 | if (err) { 192 | mongodb.close(); 193 | return callback(err); 194 | } 195 | //更新文章内容 196 | collection.update({ 197 | "name": name, 198 | "time.day": day, 199 | "title": title 200 | }, { 201 | $set: {post: post} 202 | }, function (err) { 203 | mongodb.close(); 204 | if (err) { 205 | return callback(err); 206 | } 207 | callback(null); 208 | }); 209 | }); 210 | }); 211 | }; 212 | 213 | //删除一篇文章 214 | Post.remove = function(name, day, title, callback) { 215 | //打开数据库 216 | mongodb.open(function (err, db) { 217 | if (err) { 218 | return callback(err); 219 | } 220 | //读取 posts 集合 221 | db.collection('posts', function (err, collection) { 222 | if (err) { 223 | mongodb.close(); 224 | return callback(err); 225 | } 226 | //查询要删除的文档 227 | collection.findOne({ 228 | "name": name, 229 | "time.day": day, 230 | "title": title 231 | }, function (err, doc) { 232 | if (err) { 233 | mongodb.close(); 234 | return callback(err); 235 | } 236 | //如果有 reprint_from,即该文章是转载来的,先保存下来 reprint_from 237 | var reprint_from = ""; 238 | if (doc.reprint_info.reprint_from) { 239 | reprint_from = doc.reprint_info.reprint_from; 240 | } 241 | if (reprint_from != "") { 242 | //更新原文章所在文档的 reprint_to 243 | collection.update({ 244 | "name": reprint_from.name, 245 | "time.day": reprint_from.day, 246 | "title": reprint_from.title 247 | }, { 248 | $pull: { 249 | "reprint_info.reprint_to": { 250 | "name": name, 251 | "day": day, 252 | "title": title 253 | }} 254 | }, function (err) { 255 | if (err) { 256 | mongodb.close(); 257 | return callback(err); 258 | } 259 | }); 260 | } 261 | 262 | //删除转载来的文章所在的文档 263 | collection.remove({ 264 | "name": name, 265 | "time.day": day, 266 | "title": title 267 | }, { 268 | w: 1 269 | }, function (err) { 270 | mongodb.close(); 271 | if (err) { 272 | return callback(err); 273 | } 274 | callback(null); 275 | }); 276 | }); 277 | }); 278 | }); 279 | }; 280 | 281 | //返回所有文章存档信息 282 | Post.getArchive = function(callback) { 283 | //打开数据库 284 | mongodb.open(function (err, db) { 285 | if (err) { 286 | return callback(err); 287 | } 288 | //读取 posts 集合 289 | db.collection('posts', function (err, collection) { 290 | if (err) { 291 | mongodb.close(); 292 | return callback(err); 293 | } 294 | //返回只包含 name、time、title 属性的文档组成的存档数组 295 | collection.find({}, { 296 | "name": 1, 297 | "time": 1, 298 | "title": 1 299 | }).sort({ 300 | time: -1 301 | }).toArray(function (err, docs) { 302 | mongodb.close(); 303 | if (err) { 304 | return callback(err); 305 | } 306 | callback(null, docs); 307 | }); 308 | }); 309 | }); 310 | }; 311 | 312 | //返回所有标签 313 | Post.getTags = function(callback) { 314 | //打开数据库 315 | mongodb.open(function (err, db) { 316 | if (err) { 317 | return callback(err); 318 | } 319 | //读取 posts 集合 320 | db.collection('posts', function (err, collection) { 321 | if (err) { 322 | mongodb.close(); 323 | return callback(err); 324 | } 325 | //distinct 用来找出给定键的所有不同值 326 | collection.distinct("tags", function (err, docs) { 327 | mongodb.close(); 328 | if (err) { 329 | return callback(err); 330 | } 331 | callback(null, docs); 332 | }); 333 | }); 334 | }); 335 | }; 336 | 337 | //返回含有特定标签的所有文章 338 | Post.getTag = function(tag, callback) { 339 | mongodb.open(function (err, db) { 340 | if (err) { 341 | return callback(err); 342 | } 343 | db.collection('posts', function (err, collection) { 344 | if (err) { 345 | mongodb.close(); 346 | return callback(err); 347 | } 348 | //查询所有 tags 数组内包含 tag 的文档 349 | //并返回只含有 name、time、title 组成的数组 350 | collection.find({ 351 | "tags": tag 352 | }, { 353 | "name": 1, 354 | "time": 1, 355 | "title": 1 356 | }).sort({ 357 | time: -1 358 | }).toArray(function (err, docs) { 359 | mongodb.close(); 360 | if (err) { 361 | return callback(err); 362 | } 363 | callback(null, docs); 364 | }); 365 | }); 366 | }); 367 | }; 368 | 369 | //返回通过标题关键字查询的所有文章信息 370 | Post.search = function(keyword, callback) { 371 | mongodb.open(function (err, db) { 372 | if (err) { 373 | return callback(err); 374 | } 375 | db.collection('posts', function (err, collection) { 376 | if (err) { 377 | mongodb.close(); 378 | return callback(err); 379 | } 380 | var pattern = new RegExp(keyword, "i"); 381 | collection.find({ 382 | "title": pattern 383 | }, { 384 | "name": 1, 385 | "time": 1, 386 | "title": 1 387 | }).sort({ 388 | time: -1 389 | }).toArray(function (err, docs) { 390 | mongodb.close(); 391 | if (err) { 392 | return callback(err); 393 | } 394 | callback(null, docs); 395 | }); 396 | }); 397 | }); 398 | }; 399 | 400 | //转载一篇文章 401 | Post.reprint = function(reprint_from, reprint_to, callback) { 402 | mongodb.open(function (err, db) { 403 | if (err) { 404 | return callback(err); 405 | } 406 | db.collection('posts', function (err, collection) { 407 | if (err) { 408 | mongodb.close(); 409 | return callback(err); 410 | } 411 | //找到被转载的文章的原文档 412 | collection.findOne({ 413 | "name": reprint_from.name, 414 | "time.day": reprint_from.day, 415 | "title": reprint_from.title 416 | }, function (err, doc) { 417 | if (err) { 418 | mongodb.close(); 419 | return callback(err); 420 | } 421 | 422 | var date = new Date(); 423 | var time = { 424 | date: date, 425 | year : date.getFullYear(), 426 | month : date.getFullYear() + "-" + (date.getMonth() + 1), 427 | day : date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(), 428 | minute : date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + 429 | date.getHours() + ":" + (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) 430 | } 431 | 432 | delete doc._id;//注意要删掉原来的 _id 433 | 434 | doc.name = reprint_to.name; 435 | doc.head = reprint_to.head; 436 | doc.time = time; 437 | doc.title = (doc.title.search(/[转载]/) > -1) ? doc.title : "[转载]" + doc.title; 438 | doc.comments = []; 439 | doc.reprint_info = {"reprint_from": reprint_from}; 440 | doc.pv = 0; 441 | 442 | //更新被转载的原文档的 reprint_info 内的 reprint_to 443 | collection.update({ 444 | "name": reprint_from.name, 445 | "time.day": reprint_from.day, 446 | "title": reprint_from.title 447 | }, { 448 | $push: { 449 | "reprint_info.reprint_to": { 450 | "name": doc.name, 451 | "day": time.day, 452 | "title": doc.title 453 | }} 454 | }, function (err) { 455 | if (err) { 456 | mongodb.close(); 457 | return callback(err); 458 | } 459 | }); 460 | 461 | //将转载生成的副本修改后存入数据库,并返回存储后的文档 462 | collection.insert(doc, { 463 | safe: true 464 | }, function (err, post) { 465 | mongodb.close(); 466 | if (err) { 467 | return callback(err); 468 | } 469 | callback(err, post[0]); 470 | }); 471 | }); 472 | }); 473 | }); 474 | }; -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abbrev@1: 6 | version "1.1.0" 7 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" 8 | 9 | accepts@~1.3.3: 10 | version "1.3.3" 11 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" 12 | dependencies: 13 | mime-types "~2.1.11" 14 | negotiator "0.6.1" 15 | 16 | any-promise@^1.0.0: 17 | version "1.3.0" 18 | resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" 19 | 20 | array-flatten@1.1.1: 21 | version "1.1.1" 22 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 23 | 24 | base64-url@1: 25 | version "1.3.3" 26 | resolved "https://registry.yarnpkg.com/base64-url/-/base64-url-1.3.3.tgz#f8b6c537f09a4fc58c99cb86e0b0e9c61461a20f" 27 | 28 | basic-auth@1.0.0: 29 | version "1.0.0" 30 | resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.0.0.tgz#111b2d9ff8e4e6d136b8c84ea5e096cb87351637" 31 | 32 | body-parser@^1.17.2: 33 | version "1.17.2" 34 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee" 35 | dependencies: 36 | bytes "2.4.0" 37 | content-type "~1.0.2" 38 | debug "2.6.7" 39 | depd "~1.1.0" 40 | http-errors "~1.6.1" 41 | iconv-lite "0.4.15" 42 | on-finished "~2.3.0" 43 | qs "6.4.0" 44 | raw-body "~2.2.0" 45 | type-is "~1.6.15" 46 | 47 | bson@0.2.5: 48 | version "0.2.5" 49 | resolved "https://registry.yarnpkg.com/bson/-/bson-0.2.5.tgz#500d26d883ddc8e02f2c88011627636111c105c5" 50 | 51 | bson@~1.0.4: 52 | version "1.0.4" 53 | resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.4.tgz#93c10d39eaa5b58415cbc4052f3e53e562b0b72c" 54 | 55 | buffer-shims@~1.0.0: 56 | version "1.0.0" 57 | resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" 58 | 59 | busboy@~0.2.9: 60 | version "0.2.14" 61 | resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" 62 | dependencies: 63 | dicer "0.2.5" 64 | readable-stream "1.1.x" 65 | 66 | bytes@2.4.0: 67 | version "2.4.0" 68 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" 69 | 70 | connect-flash@0.1.1: 71 | version "0.1.1" 72 | resolved "https://registry.yarnpkg.com/connect-flash/-/connect-flash-0.1.1.tgz#d8630f26d95a7f851f9956b1e8cc6732f3b6aa30" 73 | 74 | connect-mongo@0.4.1: 75 | version "0.4.1" 76 | resolved "https://registry.yarnpkg.com/connect-mongo/-/connect-mongo-0.4.1.tgz#01ed3e71558fb3f0fdc97b784ef974f9909ddd11" 77 | dependencies: 78 | mongodb "1.3.x" 79 | 80 | content-disposition@0.5.2: 81 | version "0.5.2" 82 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 83 | 84 | content-type@~1.0.2: 85 | version "1.0.2" 86 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" 87 | 88 | cookie-parser@1.3.3: 89 | version "1.3.3" 90 | resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.3.3.tgz#7e3a2c745f4b460d5a340e578a0baa5d7725fe37" 91 | dependencies: 92 | cookie "0.1.2" 93 | cookie-signature "1.0.5" 94 | 95 | cookie-signature@1.0.5: 96 | version "1.0.5" 97 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.5.tgz#a122e3f1503eca0f5355795b0711bb2368d450f9" 98 | 99 | cookie-signature@1.0.6: 100 | version "1.0.6" 101 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 102 | 103 | cookie@0.1.2: 104 | version "0.1.2" 105 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.1.2.tgz#72fec3d24e48a3432073d90c12642005061004b1" 106 | 107 | cookie@0.3.1: 108 | version "0.3.1" 109 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 110 | 111 | core-util-is@~1.0.0: 112 | version "1.0.2" 113 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 114 | 115 | crc@3.0.0: 116 | version "3.0.0" 117 | resolved "https://registry.yarnpkg.com/crc/-/crc-3.0.0.tgz#d11e97ec44a844e5eb15a74fa2c7875d0aac4b22" 118 | 119 | debug@2.6.7: 120 | version "2.6.7" 121 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" 122 | dependencies: 123 | ms "2.0.0" 124 | 125 | debug@2.6.8: 126 | version "2.6.8" 127 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" 128 | dependencies: 129 | ms "2.0.0" 130 | 131 | debug@~2.1.0: 132 | version "2.1.3" 133 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.1.3.tgz#ce8ab1b5ee8fbee2bfa3b633cab93d366b63418e" 134 | dependencies: 135 | ms "0.7.0" 136 | 137 | depd@0.4.5: 138 | version "0.4.5" 139 | resolved "https://registry.yarnpkg.com/depd/-/depd-0.4.5.tgz#1a664b53388b4a6573e8ae67b5f767c693ca97f1" 140 | 141 | depd@1.1.1, depd@~1.1.0, depd@~1.1.1: 142 | version "1.1.1" 143 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" 144 | 145 | depd@~1.0.0: 146 | version "1.0.1" 147 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.0.1.tgz#80aec64c9d6d97e65cc2a9caa93c0aa6abf73aaa" 148 | 149 | destroy@~1.0.4: 150 | version "1.0.4" 151 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 152 | 153 | dicer@0.2.5: 154 | version "0.2.5" 155 | resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" 156 | dependencies: 157 | readable-stream "1.1.x" 158 | streamsearch "0.1.2" 159 | 160 | ee-first@1.0.5: 161 | version "1.0.5" 162 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.0.5.tgz#8c9b212898d8cd9f1a9436650ce7be202c9e9ff0" 163 | 164 | ee-first@1.1.1: 165 | version "1.1.1" 166 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 167 | 168 | ejs@1.0.0: 169 | version "1.0.0" 170 | resolved "https://registry.yarnpkg.com/ejs/-/ejs-1.0.0.tgz#c9c60a48a46ee452fb32a71c317b95e5aa1fcb3d" 171 | 172 | encodeurl@~1.0.1: 173 | version "1.0.1" 174 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" 175 | 176 | es6-promise@3.2.1: 177 | version "3.2.1" 178 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.2.1.tgz#ec56233868032909207170c39448e24449dd1fc4" 179 | 180 | escape-html@~1.0.3: 181 | version "1.0.3" 182 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 183 | 184 | etag@~1.8.0: 185 | version "1.8.0" 186 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" 187 | 188 | express-session@1.9.1: 189 | version "1.9.1" 190 | resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.9.1.tgz#27f9192d04eec2c9106dfd3315cdc98b48fdfd3b" 191 | dependencies: 192 | cookie "0.1.2" 193 | cookie-signature "1.0.5" 194 | crc "3.0.0" 195 | debug "~2.1.0" 196 | depd "~1.0.0" 197 | on-headers "~1.0.0" 198 | parseurl "~1.3.0" 199 | uid-safe "1.0.1" 200 | utils-merge "1.0.0" 201 | 202 | express@^4.10.2: 203 | version "4.15.4" 204 | resolved "https://registry.yarnpkg.com/express/-/express-4.15.4.tgz#032e2253489cf8fce02666beca3d11ed7a2daed1" 205 | dependencies: 206 | accepts "~1.3.3" 207 | array-flatten "1.1.1" 208 | content-disposition "0.5.2" 209 | content-type "~1.0.2" 210 | cookie "0.3.1" 211 | cookie-signature "1.0.6" 212 | debug "2.6.8" 213 | depd "~1.1.1" 214 | encodeurl "~1.0.1" 215 | escape-html "~1.0.3" 216 | etag "~1.8.0" 217 | finalhandler "~1.0.4" 218 | fresh "0.5.0" 219 | merge-descriptors "1.0.1" 220 | methods "~1.1.2" 221 | on-finished "~2.3.0" 222 | parseurl "~1.3.1" 223 | path-to-regexp "0.1.7" 224 | proxy-addr "~1.1.5" 225 | qs "6.5.0" 226 | range-parser "~1.2.0" 227 | send "0.15.4" 228 | serve-static "1.12.4" 229 | setprototypeof "1.0.3" 230 | statuses "~1.3.1" 231 | type-is "~1.6.15" 232 | utils-merge "1.0.0" 233 | vary "~1.1.1" 234 | 235 | finalhandler@~1.0.4: 236 | version "1.0.4" 237 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.4.tgz#18574f2e7c4b98b8ae3b230c21f201f31bdb3fb7" 238 | dependencies: 239 | debug "2.6.8" 240 | encodeurl "~1.0.1" 241 | escape-html "~1.0.3" 242 | on-finished "~2.3.0" 243 | parseurl "~1.3.1" 244 | statuses "~1.3.1" 245 | unpipe "~1.0.0" 246 | 247 | forwarded@~0.1.0: 248 | version "0.1.0" 249 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" 250 | 251 | fresh@0.5.0: 252 | version "0.5.0" 253 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e" 254 | 255 | http-errors@~1.6.1, http-errors@~1.6.2: 256 | version "1.6.2" 257 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" 258 | dependencies: 259 | depd "1.1.1" 260 | inherits "2.0.3" 261 | setprototypeof "1.0.3" 262 | statuses ">= 1.3.1 < 2" 263 | 264 | iconv-lite@0.4.15: 265 | version "0.4.15" 266 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" 267 | 268 | inherits@2.0.3, inherits@~2.0.1: 269 | version "2.0.3" 270 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 271 | 272 | ipaddr.js@1.4.0: 273 | version "1.4.0" 274 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" 275 | 276 | isarray@0.0.1: 277 | version "0.0.1" 278 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 279 | 280 | isarray@~1.0.0: 281 | version "1.0.0" 282 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 283 | 284 | kerberos@0.0.3: 285 | version "0.0.3" 286 | resolved "https://registry.yarnpkg.com/kerberos/-/kerberos-0.0.3.tgz#4285d92a0748db2784062f5adcec9f5956cb818a" 287 | 288 | markdown@^0.5.0: 289 | version "0.5.0" 290 | resolved "https://registry.yarnpkg.com/markdown/-/markdown-0.5.0.tgz#28205b565a8ae7592de207463d6637dc182722b2" 291 | dependencies: 292 | nopt "~2.1.1" 293 | 294 | media-typer@0.3.0: 295 | version "0.3.0" 296 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 297 | 298 | merge-descriptors@1.0.1: 299 | version "1.0.1" 300 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 301 | 302 | methods@~1.1.2: 303 | version "1.1.2" 304 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 305 | 306 | mime-db@~1.29.0: 307 | version "1.29.0" 308 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878" 309 | 310 | mime-types@~2.1.11, mime-types@~2.1.15: 311 | version "2.1.16" 312 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23" 313 | dependencies: 314 | mime-db "~1.29.0" 315 | 316 | mime@1.3.4: 317 | version "1.3.4" 318 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" 319 | 320 | mkdirp@~0.3.5: 321 | version "0.3.5" 322 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" 323 | 324 | mongodb-core@2.1.15: 325 | version "2.1.15" 326 | resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.15.tgz#841f53b87ffff4c7458189c35c8ae827e1169764" 327 | dependencies: 328 | bson "~1.0.4" 329 | require_optional "~1.0.0" 330 | 331 | mongodb@1.3.x: 332 | version "1.3.23" 333 | resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-1.3.23.tgz#874a5212162b16188aeeaee5e06067766c8e9e86" 334 | dependencies: 335 | bson "0.2.5" 336 | optionalDependencies: 337 | kerberos "0.0.3" 338 | 339 | mongodb@^2.2.31: 340 | version "2.2.31" 341 | resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.31.tgz#1940445c661e19217bb3bf8245d9854aaef548db" 342 | dependencies: 343 | es6-promise "3.2.1" 344 | mongodb-core "2.1.15" 345 | readable-stream "2.2.7" 346 | 347 | morgan@1.3.1: 348 | version "1.3.1" 349 | resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.3.1.tgz#5c2ae66ef1da03f0ac9f0f42840cca5d8bfec23f" 350 | dependencies: 351 | basic-auth "1.0.0" 352 | depd "0.4.5" 353 | on-finished "2.1.0" 354 | 355 | ms@0.7.0: 356 | version "0.7.0" 357 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.0.tgz#865be94c2e7397ad8a57da6a633a6e2f30798b83" 358 | 359 | ms@2.0.0: 360 | version "2.0.0" 361 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 362 | 363 | multer@0.1.6: 364 | version "0.1.6" 365 | resolved "https://registry.yarnpkg.com/multer/-/multer-0.1.6.tgz#06e22a8434262976fd27f3b475b43e83990a0490" 366 | dependencies: 367 | busboy "~0.2.9" 368 | mkdirp "~0.3.5" 369 | qs "~1.2.2" 370 | 371 | mz@1: 372 | version "1.3.0" 373 | resolved "https://registry.yarnpkg.com/mz/-/mz-1.3.0.tgz#06f093fdd9956a06d37e1b1e81344e27478c42f0" 374 | dependencies: 375 | native-or-bluebird "1" 376 | thenify "3" 377 | thenify-all "1" 378 | 379 | native-or-bluebird@1: 380 | version "1.2.0" 381 | resolved "https://registry.yarnpkg.com/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz#39c47bfd7825d1fb9ffad32210ae25daadf101c9" 382 | 383 | negotiator@0.6.1: 384 | version "0.6.1" 385 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 386 | 387 | nopt@~2.1.1: 388 | version "2.1.2" 389 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-2.1.2.tgz#6cccd977b80132a07731d6e8ce58c2c8303cf9af" 390 | dependencies: 391 | abbrev "1" 392 | 393 | on-finished@2.1.0: 394 | version "2.1.0" 395 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.1.0.tgz#0c539f09291e8ffadde0c8a25850fb2cedc7022d" 396 | dependencies: 397 | ee-first "1.0.5" 398 | 399 | on-finished@~2.3.0: 400 | version "2.3.0" 401 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 402 | dependencies: 403 | ee-first "1.1.1" 404 | 405 | on-headers@~1.0.0: 406 | version "1.0.1" 407 | resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" 408 | 409 | parseurl@~1.3.0, parseurl@~1.3.1: 410 | version "1.3.1" 411 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" 412 | 413 | path-to-regexp@0.1.7: 414 | version "0.1.7" 415 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 416 | 417 | process-nextick-args@~1.0.6: 418 | version "1.0.7" 419 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 420 | 421 | proxy-addr@~1.1.5: 422 | version "1.1.5" 423 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" 424 | dependencies: 425 | forwarded "~0.1.0" 426 | ipaddr.js "1.4.0" 427 | 428 | qs@6.4.0: 429 | version "6.4.0" 430 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" 431 | 432 | qs@6.5.0: 433 | version "6.5.0" 434 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49" 435 | 436 | qs@~1.2.2: 437 | version "1.2.2" 438 | resolved "https://registry.yarnpkg.com/qs/-/qs-1.2.2.tgz#19b57ff24dc2a99ce1f8bdf6afcda59f8ef61f88" 439 | 440 | range-parser@~1.2.0: 441 | version "1.2.0" 442 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 443 | 444 | raw-body@~2.2.0: 445 | version "2.2.0" 446 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" 447 | dependencies: 448 | bytes "2.4.0" 449 | iconv-lite "0.4.15" 450 | unpipe "1.0.0" 451 | 452 | readable-stream@1.1.x: 453 | version "1.1.14" 454 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" 455 | dependencies: 456 | core-util-is "~1.0.0" 457 | inherits "~2.0.1" 458 | isarray "0.0.1" 459 | string_decoder "~0.10.x" 460 | 461 | readable-stream@2.2.7: 462 | version "2.2.7" 463 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.7.tgz#07057acbe2467b22042d36f98c5ad507054e95b1" 464 | dependencies: 465 | buffer-shims "~1.0.0" 466 | core-util-is "~1.0.0" 467 | inherits "~2.0.1" 468 | isarray "~1.0.0" 469 | process-nextick-args "~1.0.6" 470 | string_decoder "~1.0.0" 471 | util-deprecate "~1.0.1" 472 | 473 | require_optional@~1.0.0: 474 | version "1.0.1" 475 | resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" 476 | dependencies: 477 | resolve-from "^2.0.0" 478 | semver "^5.1.0" 479 | 480 | resolve-from@^2.0.0: 481 | version "2.0.0" 482 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" 483 | 484 | safe-buffer@5.0.1: 485 | version "5.0.1" 486 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" 487 | 488 | safe-buffer@~5.1.0: 489 | version "5.1.1" 490 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 491 | 492 | semver@^5.1.0: 493 | version "5.4.1" 494 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" 495 | 496 | send@0.15.4: 497 | version "0.15.4" 498 | resolved "https://registry.yarnpkg.com/send/-/send-0.15.4.tgz#985faa3e284b0273c793364a35c6737bd93905b9" 499 | dependencies: 500 | debug "2.6.8" 501 | depd "~1.1.1" 502 | destroy "~1.0.4" 503 | encodeurl "~1.0.1" 504 | escape-html "~1.0.3" 505 | etag "~1.8.0" 506 | fresh "0.5.0" 507 | http-errors "~1.6.2" 508 | mime "1.3.4" 509 | ms "2.0.0" 510 | on-finished "~2.3.0" 511 | range-parser "~1.2.0" 512 | statuses "~1.3.1" 513 | 514 | serve-favicon@^2.4.3: 515 | version "2.4.3" 516 | resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.4.3.tgz#5986b17b0502642b641c21f818b1acce32025d23" 517 | dependencies: 518 | etag "~1.8.0" 519 | fresh "0.5.0" 520 | ms "2.0.0" 521 | parseurl "~1.3.1" 522 | safe-buffer "5.0.1" 523 | 524 | serve-static@1.12.4: 525 | version "1.12.4" 526 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.4.tgz#9b6aa98eeb7253c4eedc4c1f6fdbca609901a961" 527 | dependencies: 528 | encodeurl "~1.0.1" 529 | escape-html "~1.0.3" 530 | parseurl "~1.3.1" 531 | send "0.15.4" 532 | 533 | setprototypeof@1.0.3: 534 | version "1.0.3" 535 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" 536 | 537 | "statuses@>= 1.3.1 < 2", statuses@~1.3.1: 538 | version "1.3.1" 539 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" 540 | 541 | streamsearch@0.1.2: 542 | version "0.1.2" 543 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" 544 | 545 | string_decoder@~0.10.x: 546 | version "0.10.31" 547 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 548 | 549 | string_decoder@~1.0.0: 550 | version "1.0.3" 551 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" 552 | dependencies: 553 | safe-buffer "~5.1.0" 554 | 555 | thenify-all@1: 556 | version "1.6.0" 557 | resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" 558 | dependencies: 559 | thenify ">= 3.1.0 < 4" 560 | 561 | thenify@3, "thenify@>= 3.1.0 < 4": 562 | version "3.3.0" 563 | resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" 564 | dependencies: 565 | any-promise "^1.0.0" 566 | 567 | type-is@~1.6.15: 568 | version "1.6.15" 569 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" 570 | dependencies: 571 | media-typer "0.3.0" 572 | mime-types "~2.1.15" 573 | 574 | uid-safe@1.0.1: 575 | version "1.0.1" 576 | resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-1.0.1.tgz#5bd148460a2e84f54f193fd20352c8c3d7de6ac8" 577 | dependencies: 578 | base64-url "1" 579 | mz "1" 580 | 581 | unpipe@1.0.0, unpipe@~1.0.0: 582 | version "1.0.0" 583 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 584 | 585 | util-deprecate@~1.0.1: 586 | version "1.0.2" 587 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 588 | 589 | utils-merge@1.0.0: 590 | version "1.0.0" 591 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" 592 | 593 | vary@~1.1.1: 594 | version "1.1.1" 595 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" 596 | --------------------------------------------------------------------------------