├── README.md ├── app.js ├── bin └── www ├── common ├── email.js ├── helps.js └── tools.js ├── config.js ├── controllers ├── topic.js ├── upload.js └── users.js ├── middlewares └── auth.js ├── models ├── base.js ├── comment.js ├── index.js ├── tag.js ├── topic.js └── users.js ├── npm-debug.log ├── package.json ├── proxy ├── comment.js ├── index.js ├── tag.js ├── topic.js └── users.js ├── public ├── images │ ├── default-avatar.png │ └── loading.gif ├── javascripts │ ├── ajax-upload.js │ ├── bootstrap.min.js │ ├── common.js │ ├── editor.js │ ├── jquery-ui.min.js │ ├── jquery.min.js │ ├── marked.js │ └── tag-it.js └── stylesheets │ ├── bootstrap.min.css │ ├── editor-self.css │ ├── editor.css │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── glyphicons-halflings-regular.woff2 │ └── icomoon.woff │ ├── images │ ├── loading.gif │ ├── ui-bg_diagonals-thick_18_b81900_40x40.png │ ├── ui-bg_diagonals-thick_20_666666_40x40.png │ ├── ui-bg_flat_10_000000_40x100.png │ ├── ui-bg_glass_100_f6f6f6_1x400.png │ ├── ui-bg_glass_100_fdf5ce_1x400.png │ ├── ui-bg_glass_65_ffffff_1x400.png │ ├── ui-bg_gloss-wave_35_f6a828_500x100.png │ ├── ui-bg_highlight-soft_100_eeeeee_1x100.png │ ├── ui-bg_highlight-soft_75_ffe45c_1x100.png │ ├── ui-icons_222222_256x240.png │ ├── ui-icons_228ef1_256x240.png │ ├── ui-icons_ef8c08_256x240.png │ ├── ui-icons_ffd27a_256x240.png │ └── ui-icons_ffffff_256x240.png │ ├── jquery-ui.css │ ├── jquery.tagit.css │ ├── main.css │ └── styles.css ├── routes ├── index.js ├── topic.js ├── upload.js └── users.js └── views ├── error.jade ├── header.jade ├── index.jade ├── layout.jade ├── sidebar-tag.jade ├── sidebar-user.jade ├── topic ├── create.jade ├── home.jade ├── list.jade ├── page.jade ├── show.jade ├── tag.jade └── topic.jade └── users ├── active.jade ├── home.jade ├── login.jade ├── register.jade └── setting.jade /README.md: -------------------------------------------------------------------------------- 1 | # NodeJS-forum 2 | 最近学习NodeJS,利用ExpressJS框架和MongoDB数据库搭建了一个简单的Forum! 3 | 4 | 演示地址:[Demo](http://nodejs.luckybird.me/) 5 | 6 | **功能简介** 7 | 8 | * 用户注册,发送激活邮件,用户登录,上传头像 9 | * 发起话题,创建话题标签,用户评论话题 10 | * Markdown编辑器,在线预览,支持图片 11 | 12 | **安装方法** 13 | 14 | ``` 15 | $ git clone https://github.com/luckybirdme/NodeJS-forum.git 16 | $ npm install 17 | $ DEBUG=myExpressApp:* npm start 18 | 19 | ``` 20 | 注意事项: 21 | 22 | 1. 请确保安装了NodeJS,npm,MongoDB 23 | 2. 请根据环境修改根目录的config.js配置文件 24 | 25 | **备注** 26 | 27 | 使用过程中如有疑问,可查看本人的学习笔记,博客地址:[LuckyBird](http://www.luckybird.me/) 28 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var config = require("./config"); 2 | var helps = require("./common/helps"); 3 | 4 | 5 | var express = require('express'); 6 | var path = require('path'); 7 | var favicon = require('serve-favicon'); 8 | var logger = require('morgan'); 9 | 10 | 11 | 12 | var cookieParser = require('cookie-parser'); 13 | var bodyParser = require('body-parser'); 14 | 15 | var csrf = require('csurf'); 16 | 17 | var routes = require('./routes/index'); 18 | var users = require('./routes/users'); 19 | var topic = require('./routes/topic'); 20 | var upload = require('./routes/upload'); 21 | 22 | 23 | var app = express(); 24 | 25 | // view engine setup 26 | app.set('views', path.join(__dirname, 'views')); 27 | app.set('view engine', 'jade'); 28 | 29 | // uncomment after placing your favicon in /public 30 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 31 | app.use(logger('dev')); 32 | app.use(bodyParser.json()); 33 | app.use(bodyParser.urlencoded({ extended: false })); 34 | app.use(cookieParser()); 35 | app.use(express.static(path.join(__dirname, 'public'))); 36 | 37 | app.use('/upload',express.static(path.join(__dirname, 'upload'))); 38 | global.appRoot = path.resolve(__dirname); 39 | 40 | const session = require('express-session'); 41 | const MongoStore = require('connect-mongo')(session); 42 | app.use(session({ 43 | secret: config.session.cookie.secret, 44 | name: config.session.cookie.name, 45 | cookie: { 46 | maxAge:config.session.cookie.maxAge 47 | }, 48 | resave: false, 49 | saveUninitialized: true, 50 | store: new MongoStore({ url:config.session.database.address }) 51 | })); 52 | 53 | var csrfProtection = csrf(); 54 | app.use(csrfProtection); 55 | 56 | app.use(function (err, req, res, next) { 57 | if (err.code !== 'EBADCSRFTOKEN') return next(err) 58 | 59 | // handle CSRF token errors here 60 | var name = 'submit'; 61 | var notice = 'Invalid csrf token , please refresh'; 62 | helps.resJsonError(req,res,name,notice); 63 | }) 64 | 65 | 66 | app.use(function(req,res,next){ 67 | res.locals.session = req.session; 68 | res.locals.webName = config.web.name; 69 | next(); 70 | }); 71 | 72 | 73 | app.use('/', routes); 74 | app.use('/users', users); 75 | app.use('/topic', topic); 76 | app.use('/upload', upload); 77 | 78 | // catch 404 and forward to error handler 79 | app.use(function(req, res, next) { 80 | var err = new Error('Not Found'); 81 | err.status = 404; 82 | next(err); 83 | }); 84 | 85 | // error handlers 86 | 87 | // development error handler 88 | // will print stacktrace 89 | if (app.get('env') === 'development') { 90 | app.use(function(err, req, res, next) { 91 | res.status(err.status || 500); 92 | res.render('error', { 93 | message: err.message, 94 | error: err 95 | }); 96 | }); 97 | } 98 | 99 | // production error handler 100 | // no stacktraces leaked to user 101 | app.use(function(err, req, res, next) { 102 | res.status(err.status || 500); 103 | res.render('error', { 104 | message: err.message, 105 | error: {} 106 | }); 107 | }); 108 | 109 | 110 | module.exports = app; 111 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('myExpressApp: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 | -------------------------------------------------------------------------------- /common/email.js: -------------------------------------------------------------------------------- 1 | var nodemailer = require('nodemailer'); 2 | var config = require("../config"); 3 | 4 | var transporter = nodemailer.createTransport({ 5 | host: config.email.host, 6 | port: config.email.port, 7 | auth: { 8 | user: config.email.user, 9 | pass: config.email.pass 10 | } 11 | }); 12 | 13 | exports.sendMail = sendMail; 14 | 15 | function sendMail(from,email,subject,html){ 16 | 17 | var mailOptions = { 18 | from:from, 19 | to: email, 20 | subject: subject, 21 | html: html 22 | }; 23 | // send mail with defined transport object 24 | transporter.sendMail(mailOptions, function(error, info){ 25 | if(error){ 26 | return console.log("sendMail error: "+error); 27 | } 28 | console.log('sendMail success: ' + info.response); 29 | 30 | }); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /common/helps.js: -------------------------------------------------------------------------------- 1 | exports.jsonRedirect = jsonRedirect; 2 | exports.resJsonSuccess = resJsonSuccess; 3 | exports.resJsonError = resJsonError; 4 | 5 | function jsonRedirect(res,url){ 6 | var data = { 7 | redirect : url 8 | }; 9 | res.json(data); 10 | } 11 | 12 | function resJsonSuccess(req,res,name,notice,url){ 13 | var data = { 14 | success : { 15 | name : name, 16 | notice : notice, 17 | url:url 18 | } 19 | }; 20 | resJsonOut(req,res,data); 21 | } 22 | 23 | function resJsonError(req,res,name,notice){ 24 | var data = { 25 | error : { 26 | name : name, 27 | notice : notice 28 | } 29 | }; 30 | resJsonOut(req,res,data); 31 | } 32 | 33 | function resJsonOut(req,res,data){ 34 | res.json(data); 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /common/tools.js: -------------------------------------------------------------------------------- 1 | var config = require("../config") 2 | var moment = require("moment"); 3 | var bcrypt = require('bcrypt'); 4 | var utility = require('utility'); 5 | var mkdirp = require('mkdirp'); 6 | 7 | exports.mkDirpath = mkDirpath; 8 | exports.formatDate = formatDate; 9 | exports.bcryptGenSalt = bcryptGenSalt; 10 | exports.bcryptCompare = bcryptCompare; 11 | exports.getActiveKey = getActiveKey; 12 | 13 | function getActiveKey(user){ 14 | var activeKey = utility.md5(user.userEmail+user.password+config.auth.activeKey+user.update_at); 15 | return activeKey; 16 | } 17 | 18 | 19 | 20 | function bcryptCompare(password,hash,callback){ 21 | bcrypt.compare(password, hash, callback); 22 | } 23 | 24 | function bcryptGenSalt(password,callback){ 25 | bcrypt.genSalt(10, function(err, salt) { 26 | bcrypt.hash(password, salt,callback); 27 | }); 28 | } 29 | 30 | 31 | function formatDate (date, friendly) { 32 | date = moment(date); 33 | if (friendly) { 34 | return date.fromNow(); 35 | } else { 36 | return date.format('YYYY-MM-DD HH:mm'); 37 | } 38 | 39 | } 40 | 41 | function mkDirpath(dir,callback){ 42 | mkdirp(dir,callback); 43 | } 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var appRoot = path.resolve(__dirname); 3 | var config = { 4 | web:{ 5 | name:"LuckyBird", 6 | appRoot:appRoot 7 | }, 8 | url:{ 9 | //host:"http://10.118.27.156:3000" 10 | host:"http://172.20.10.2:3000" 11 | }, 12 | upload:{ 13 | uploadPath:"", 14 | imageFloder:"/upload/images/", 15 | defaultAvatar:"/images/default-avatar.png" 16 | }, 17 | auth:{ 18 | activeKey : "activeKey", 19 | checkActive: false 20 | }, 21 | database :{ 22 | address : "mongodb://localhost/myExpressApp" 23 | }, 24 | session :{ 25 | cookie : { 26 | secret: 'sessionSecret', 27 | name: 'sessionName', 28 | maxAge: 1000 * 60 * 60 * 24 * 30 29 | }, 30 | database:{ 31 | address: 'mongodb://localhost/myExpressAppSession' 32 | } 33 | 34 | 35 | }, 36 | email:{ 37 | host: 'smtp.qq.com', 38 | port: 25, 39 | user: '123456789@qq.com', 40 | pass: '123456789', 41 | from: 'LuckyBird<123456789@qq.com>' 42 | }, 43 | topic:{ 44 | page:{ 45 | limit:10 46 | } 47 | } 48 | }; 49 | 50 | module.exports = config; 51 | -------------------------------------------------------------------------------- /controllers/topic.js: -------------------------------------------------------------------------------- 1 | var config = require("../config") 2 | var validator = require('validator'); 3 | var eventproxy = require('eventproxy'); 4 | var helps = require("../common/helps"); 5 | var marked = require('marked'); 6 | var proxy = require('../proxy'); 7 | var Tag = proxy.Tag; 8 | var Topic = proxy.Topic; 9 | var Comment = proxy.Comment; 10 | var auth = require("../middlewares/auth"); 11 | var mongoose = require('mongoose'); 12 | 13 | exports.create = create; 14 | exports.home = home; 15 | exports.show = show; 16 | exports.add = add; 17 | exports.comment = comment; 18 | exports.getTags = getTags; 19 | exports.getComments = getComments; 20 | 21 | function getComments(req,res,next){ 22 | 23 | var topicId = req.query.topicId; 24 | var sort = '-update_at'; 25 | 26 | Comment.getTopicComments(topicId,sort,function(error,comments){ 27 | if(error){ 28 | return next(error); 29 | } 30 | var showComments = []; 31 | if(comments){ 32 | comments.forEach(function(comment){ 33 | showComments.push({ 34 | userName:comment.userName, 35 | content:comment.content, 36 | update_at_ago:comment.update_at_ago() 37 | }); 38 | }); 39 | } 40 | var data = { 41 | showComments : showComments 42 | }; 43 | res.json(data); 44 | }) 45 | } 46 | 47 | function comment(req,res,next){ 48 | var content = validator.trim(req.body.content); 49 | var topicId = req.body.topicId; 50 | 51 | var ep = new eventproxy(); 52 | ep.fail(next); 53 | ep.once('comment_error', function (name,notice) { 54 | helps.resJsonError(req,res,name,notice); 55 | }); 56 | 57 | if(validator.isNull(content)){ 58 | ep.emit("comment_error","content","Content can't be empty"); 59 | return; 60 | } 61 | 62 | var comment = { 63 | content:content, 64 | topicId:topicId 65 | } 66 | 67 | var checkEvent = new eventproxy(); 68 | checkEvent.all('checkComment', function (comment) { 69 | Topic.getById(comment.topicId,function(error,topic){ 70 | if(error){ 71 | return next(error); 72 | } 73 | if(topic){ 74 | var update = { 75 | update_at:new Date(), 76 | commentCount:topic.commentCount+1 77 | } 78 | Topic.update(topic._id,update,function(error,raw){ 79 | if(error){ 80 | return next(error); 81 | } 82 | var url = config.url.host+"/topic/show?_id="+topic._id; 83 | helps.jsonRedirect(res,url); 84 | }) 85 | }else{ 86 | ep.emit("comment_error","submit","Error occurred when get topic"); 87 | } 88 | 89 | }) 90 | }); 91 | 92 | auth.getUserBySession(req,function(user){ 93 | if(user){ 94 | comment.userName = user.userName; 95 | Comment.insert(comment,function(error,comment){ 96 | if(error){ 97 | return next(error); 98 | } 99 | if(comment){ 100 | checkEvent.emit('checkComment',comment); 101 | }else{ 102 | ep.emit("comment_error","submit","Error occurred when save comment"); 103 | } 104 | 105 | }); 106 | 107 | }else{ 108 | ep.emit("comment_error","submit","Session is timeout, please login"); 109 | } 110 | }); 111 | } 112 | 113 | function getTags(req,res,next){ 114 | Tag.getAllTags(function(error,tags){ 115 | if(error){ 116 | return next(error); 117 | } 118 | var showTags = []; 119 | if(tags){ 120 | tags.forEach(function(tag){ 121 | showTags.push({ 122 | _id:tag._id, 123 | showName:tag.showName 124 | }); 125 | }); 126 | } 127 | var data = { 128 | showTags : showTags 129 | }; 130 | res.json(data); 131 | 132 | }); 133 | } 134 | 135 | 136 | function add(req,res,next){ 137 | var _id = req.query._id; 138 | 139 | var checkEvent = new eventproxy(); 140 | checkEvent.all('checkTopic','checkTag', function (topicTags,hasTags) { 141 | var resData = { 142 | title : "Add", 143 | hasTags : hasTags 144 | }; 145 | if(topicTags){ 146 | resData.topic = topicTags.topic; 147 | resData.tagsName = topicTags.tagsName; 148 | } 149 | res.render('topic/create', resData); 150 | }); 151 | 152 | 153 | 154 | 155 | Topic.getById(_id,function(error,topic){ 156 | if(error){ 157 | return next(error); 158 | } 159 | if(topic){ 160 | var tagsId = topic.tagsId; 161 | var ep = new eventproxy(); 162 | ep.after('getTags',tagsId.length,function (tagsName) { 163 | var topicTags = { 164 | topic : topic, 165 | tagsName : tagsName 166 | } 167 | checkEvent.emit('checkTopic',topicTags); 168 | }); 169 | 170 | 171 | tagsId.forEach(function(tagId){ 172 | Tag.getById(tagId,function(error,tag){ 173 | if(error){ 174 | return next(error); 175 | } 176 | if(tag){ 177 | ep.emit("getTags",tag.showName); 178 | }else{ 179 | ep.emit("getTags"); 180 | } 181 | 182 | }) 183 | }); 184 | }else{ 185 | checkEvent.emit('checkTopic'); 186 | } 187 | 188 | }) 189 | 190 | Tag.getAllTags(function(error,tags){ 191 | if(error){ 192 | return next(error); 193 | } 194 | var hasTags=[]; 195 | if(tags){ 196 | tags.forEach(function(tag){ 197 | hasTags.push(tag.showName); 198 | }); 199 | } 200 | checkEvent.emit('checkTag',hasTags); 201 | }); 202 | } 203 | 204 | function show(req,res,next){ 205 | var _id = req.query._id; 206 | Topic.getById(_id,function(error,topic){ 207 | if (error) { 208 | return next(error); 209 | } 210 | if(topic){ 211 | 212 | topic.openCount = topic.openCount + 1; 213 | var update = {openCount:topic.openCount}; 214 | 215 | Topic.update(topic._id,update,function(error,raw){ 216 | if (error) { 217 | return next(error); 218 | } 219 | 220 | var tagsId = topic.tagsId; 221 | var ep = new eventproxy(); 222 | ep.after('getTags',tagsId.length,function (tags) { 223 | 224 | var resData = { 225 | title : topic.title, 226 | topic : topic, 227 | tags : tags 228 | }; 229 | 230 | res.render('topic/show', resData); 231 | 232 | }); 233 | 234 | tagsId.forEach(function(tagId){ 235 | Tag.getById(tagId,function(error,tag){ 236 | if(error){ 237 | return next(error); 238 | } 239 | if(tag){ 240 | ep.emit("getTags",tag); 241 | }else{ 242 | ep.emit("getTags"); 243 | } 244 | 245 | }) 246 | }); 247 | }) 248 | 249 | 250 | }else{ 251 | var url = config.url.host; 252 | res.redirect(url); 253 | } 254 | 255 | }) 256 | } 257 | 258 | function home(req,res,next){ 259 | var query = {}; 260 | var fields={}; 261 | var limit = config.topic.page.limit; 262 | var skip = 0; 263 | var sort = '-update_at'; 264 | 265 | pageUrl = "/topic/home?page=true"; 266 | var tagId = req.query.tagId; 267 | if(!validator.isNull(tagId)){ 268 | query.tagsId = mongoose.Types.ObjectId(tagId); 269 | pageUrl+=+pageUrl+"&tagId="+tagId; 270 | } 271 | 272 | 273 | var pageNum = req.query.pageNum; 274 | if(!validator.isNull(pageNum) && pageNum > 1){ 275 | var prevNum = parseInt(pageNum)-1; 276 | skip = limit*prevNum; 277 | }else{ 278 | pageNum = 1; 279 | } 280 | 281 | 282 | 283 | 284 | Topic.getByPage(query,fields,skip,limit,sort,function(error,topics){ 285 | if (error) { 286 | return next(error); 287 | } 288 | var pageInfo={ 289 | pageNum : pageNum 290 | }; 291 | if(pageNum > 1){ 292 | pageInfo.prevUrl = pageUrl+"&pageNum="+(parseInt(pageNum)-1); 293 | } 294 | 295 | if(topics && topics.length == limit){ 296 | pageInfo.nextUrl = pageUrl+"&pageNum="+(parseInt(pageNum)+1); 297 | } 298 | 299 | Tag.getByPage({},{},0,10,'-useCount',function(error,tags){ 300 | if (error) { 301 | return next(error); 302 | } 303 | var resData = { 304 | title : 'Home', 305 | topics : topics, 306 | pageInfo : pageInfo, 307 | tags:tags, 308 | tagId:tagId 309 | }; 310 | res.render('topic/home', resData); 311 | }) 312 | 313 | 314 | }); 315 | 316 | 317 | } 318 | 319 | 320 | 321 | function create(req,res,next){ 322 | var _id = req.body._id; 323 | var title = validator.trim(req.body.title); 324 | var tags = validator.trim(req.body.tags); 325 | var Markdown = req.body.markdown; 326 | 327 | var ep = new eventproxy(); 328 | ep.fail(next); 329 | ep.once('create_error', function (name,notice) { 330 | helps.resJsonError(req,res,name,notice); 331 | }); 332 | 333 | if(validator.isNull(title)){ 334 | ep.emit("create_error","title","Title can't be empty"); 335 | return; 336 | } 337 | 338 | if(validator.isNull(tags)){ 339 | ep.emit("create_error","tags","Tags can't be empty"); 340 | return; 341 | } 342 | 343 | if(validator.isNull(Markdown)){ 344 | ep.emit("create_error","content","Content can't be empty"); 345 | return; 346 | } 347 | 348 | var content = marked(Markdown); 349 | 350 | var showNameArray = tags.split(","); 351 | var tagNameArray = []; 352 | var tagArray = []; 353 | showNameArray.forEach(function (showName) { 354 | tagName = showName.toUpperCase(); 355 | if(!validator.isNull(tagName) && tagNameArray.indexOf(tagName) == '-1' ){ 356 | tagNameArray.push(tagName); 357 | tagArray.push({ 358 | tagName:tagName, 359 | showName:showName 360 | }); 361 | } 362 | }); 363 | 364 | var checkEvent = new eventproxy(); 365 | checkEvent.after('checkTag',tagArray.length, function (tagsId) { 366 | var newTopic = { 367 | title : title, 368 | tagsId : tagsId, 369 | Markdown : Markdown, 370 | content : content, 371 | update_at : new Date() 372 | }; 373 | 374 | auth.getUserBySession(req,function(user){ 375 | if(user){ 376 | newTopic.userName = user.userName; 377 | saveTopic(_id,newTopic,req,function(error,topic){ 378 | if (error) { 379 | return next(error); 380 | } 381 | if(topic){ 382 | var url = config.url.host+"/topic/home"; 383 | helps.jsonRedirect(res,url); 384 | }else{ 385 | ep.emit("create_error","submit","Error occurred when save topic"); 386 | } 387 | }); 388 | }else{ 389 | ep.emit("create_error","submit","Session is timeout, please login"); 390 | } 391 | }) 392 | 393 | 394 | }); 395 | 396 | tagArray.forEach(function (tag) { 397 | 398 | saveTag(tag,function(error,tag){ 399 | if (error) { 400 | return next(error); 401 | } 402 | if(tag){ 403 | checkEvent.emit('checkTag', tag._id); 404 | }else{ 405 | ep.emit("create_error","tags","Error occurred when save tag"); 406 | } 407 | }); 408 | 409 | }); 410 | 411 | 412 | 413 | } 414 | 415 | 416 | function saveTag(newTag,callback){ 417 | Tag.getByName(newTag.tagName,function(error,tag){ 418 | if (error) { 419 | return next(error); 420 | } 421 | if(tag){ 422 | tag.useCount = tag.useCount+1; 423 | Tag.save(tag,callback); 424 | }else{ 425 | Tag.insert(newTag,callback); 426 | } 427 | 428 | }); 429 | } 430 | 431 | function saveTopic(_id,newTopic,req,callback){ 432 | 433 | Topic.getById(_id,function(error,topic){ 434 | if (error) { 435 | return next(error); 436 | } 437 | 438 | if(topic){ 439 | if(topic.userName == newTopic.userName){ 440 | Topic.update(_id,newTopic,callback) 441 | }else{ 442 | var url = config.url.host; 443 | helps.jsonRedirect(res,url); 444 | } 445 | 446 | }else{ 447 | Topic.insert(newTopic,callback); 448 | } 449 | }); 450 | 451 | } 452 | -------------------------------------------------------------------------------- /controllers/upload.js: -------------------------------------------------------------------------------- 1 | var auth = require("../middlewares/auth"); 2 | var helps = require("../common/helps"); 3 | var tools = require("../common/tools"); 4 | var config = require("../config"); 5 | var multer = require('multer') 6 | 7 | 8 | exports.image = image; 9 | 10 | function image(req,res,next){ 11 | auth.getUserBySession(req,function(user){ 12 | if (user) { 13 | var d = new Date(); 14 | var fileFloder = config.upload.imageFloder+d.getFullYear()+"/"+(d.getMonth()+1)+"/"; 15 | var fileName = user.userName+ '-' + d.valueOf()+'.png'; 16 | var uploadPath = config.upload.uploadPath; 17 | if(uploadPath == ""||uploadPath==null||undefined){ 18 | uploadPath = config.web.appRoot; 19 | } 20 | if(uploadPath == ""||uploadPath==null||undefined){ 21 | uploadPath = appRoot;; 22 | } 23 | var filePath = uploadPath+fileFloder; 24 | var fileUrl = fileFloder+fileName; 25 | 26 | tools.mkDirpath(filePath,function(error){ 27 | if (error) { 28 | return next(error); 29 | } 30 | var storage = multer.diskStorage({ 31 | destination: function (req, file, cb) { 32 | cb(null, filePath) 33 | }, 34 | filename: function (req, file, cb) { 35 | cb(null, fileName) 36 | } 37 | }) 38 | 39 | var limits = { 40 | fileSize:10*1024*1024 41 | }; 42 | var options = { 43 | storage: storage, 44 | limits:limits 45 | }; 46 | 47 | var upload = multer(options).single("imageInput"); 48 | 49 | upload(req, res, function (error) { 50 | if (error) { 51 | // An error occurred when uploading 52 | console.log("upload error:"+error); 53 | helps.resJsonError(req,res,"upload",error.code); 54 | return; 55 | } 56 | // Everything went fine 57 | helps.resJsonSuccess(req,res,'upload','Successfully',fileUrl); 58 | }); 59 | }); 60 | 61 | 62 | }else{ 63 | helps.resJsonError(req,res,"upload","User is not exists"); 64 | } 65 | }); 66 | 67 | 68 | 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /controllers/users.js: -------------------------------------------------------------------------------- 1 | var config = require("../config") 2 | var validator = require('validator'); 3 | var eventproxy = require('eventproxy'); 4 | 5 | var auth = require("../middlewares/auth"); 6 | var tools = require("../common/tools"); 7 | var email = require("../common/email"); 8 | var helps = require("../common/helps"); 9 | var proxy = require('../proxy'); 10 | var Users = proxy.Users; 11 | var Topic = proxy.Topic; 12 | 13 | exports.register = register; 14 | exports.login = login; 15 | exports.logout = logout; 16 | exports.active = active; 17 | exports.setting = setting; 18 | exports.home = home; 19 | 20 | function home(req,res,next){ 21 | var userName = req.query.userName; 22 | 23 | Users.getByName(userName,function(error,user){ 24 | if (error) { 25 | return next(error); 26 | } 27 | if(user){ 28 | Topic.getUserTopics(userName,function(error,topics){ 29 | if (error) { 30 | return next(error); 31 | } 32 | 33 | var resData = { 34 | title : 'Home', 35 | topics : topics, 36 | user:user 37 | }; 38 | res.render('users/home', resData); 39 | 40 | }) 41 | }else{ 42 | var url = config.url.host; 43 | res.redirect(url); 44 | } 45 | }) 46 | 47 | } 48 | 49 | function setting(req,res,next){ 50 | var userName = validator.trim(req.body.userName); 51 | var userEmail = validator.trim(req.body.userEmail); 52 | var userAvatar = validator.trim(req.body.userAvatar); 53 | 54 | var ep = new eventproxy(); 55 | ep.fail(next); 56 | ep.once('setting_error', function (name,notice) { 57 | helps.resJsonError(req,res,name,notice); 58 | }); 59 | 60 | 61 | var checkEvent = new eventproxy(); 62 | checkEvent.all('checkName', function (user) { 63 | user.update_at = new Date(); 64 | Users.save(user,function(error,user){ 65 | if (error) { 66 | return next(error); 67 | } 68 | auth.saveUserSession(req,user); 69 | var url = config.url.host+"/users/setting"; 70 | helps.jsonRedirect(res,url); 71 | 72 | }); 73 | 74 | 75 | }); 76 | 77 | 78 | 79 | Users.getByEmail(userEmail,function(error,originalUser){ 80 | if (error) { 81 | return next(error); 82 | } 83 | if (originalUser) { 84 | if(!validator.isNull(userAvatar) && userAvatar != originalUser.userAvatar){ 85 | originalUser.userAvatar = userAvatar; 86 | } 87 | if(!validator.isNull(userName) && userName != originalUser.userName){ 88 | Users.getByName(userName,function(error,user){ 89 | if (error) { 90 | return next(error); 91 | } 92 | if (user) { 93 | ep.emit("setting_error","userName","Name had been registered"); 94 | }else{ 95 | originalUser.userName = userName; 96 | checkEvent.emit("checkName",originalUser); 97 | } 98 | 99 | }); 100 | }else{ 101 | checkEvent.emit("checkName",originalUser); 102 | } 103 | 104 | }else{ 105 | ep.emit("setting_error","submit","User is not exists"); 106 | } 107 | }); 108 | 109 | 110 | } 111 | 112 | function active(req,res,next){ 113 | var activeKey = validator.trim(req.body.activeKey); 114 | var userEmail = validator.trim(req.body.userEmail); 115 | 116 | var ep = new eventproxy(); 117 | ep.fail(next); 118 | ep.once('active_error', function (name,notice) { 119 | helps.resJsonError(req,res,name,notice); 120 | }); 121 | 122 | Users.getByEmail(userEmail,function(error,user){ 123 | if (error) { 124 | return next(error); 125 | } 126 | if (user) { 127 | var getActiveKey = tools.getActiveKey(user); 128 | if(getActiveKey == activeKey){ 129 | var update = {activeState : true,update_at : new Date()}; 130 | Users.update(user._id,update,function(error){ 131 | if (error) { 132 | return next(error); 133 | } 134 | 135 | helps.resJsonSuccess(req,res,"userEmail","Active successfully , please login !"); 136 | 137 | }) 138 | 139 | }else{ 140 | ep.emit("active_error","userEmail","ActiveKey is not right"); 141 | } 142 | 143 | }else{ 144 | ep.emit("active_error","userEmail","Email is not exists"); 145 | } 146 | 147 | return; 148 | 149 | }); 150 | } 151 | 152 | 153 | 154 | function logout(req,res,next){ 155 | auth.removeUserSession(req,function(error){ 156 | if (error) { 157 | return next(error); 158 | } 159 | var url = config.url.host; 160 | res.redirect(url); 161 | }); 162 | 163 | } 164 | 165 | function login(req, res, next){ 166 | var userEmail = validator.trim(req.body.userEmail); 167 | var passWord = validator.trim(req.body.passWord); 168 | var ep = new eventproxy(); 169 | ep.fail(next); 170 | ep.once('login_error', function (name,notice) { 171 | helps.resJsonError(req,res,name,notice); 172 | }); 173 | 174 | if(validator.isNull(userEmail)){ 175 | ep.emit("login_error","userEmail","Email can't be empty"); 176 | return; 177 | } 178 | 179 | if(validator.isNull(passWord)){ 180 | ep.emit("login_error","passWord","Password can't be empty"); 181 | return; 182 | } 183 | 184 | Users.getByEmail(userEmail,function(error,user){ 185 | if (error) { 186 | return next(error); 187 | } 188 | if (user) { 189 | if(config.auth.checkActive && !user.activeState){ 190 | ep.emit("login_error","userEmail","Email is not active , please check your register email"); 191 | return; 192 | } 193 | 194 | tools.bcryptCompare(passWord,user.passWord,function(error,state){ 195 | if (error) { 196 | return next(error); 197 | } 198 | if(state){ 199 | auth.saveUserSession(req,user); 200 | var url = config.url.host; 201 | helps.jsonRedirect(res,url); 202 | }else{ 203 | ep.emit("login_error","passWord","Password is not right"); 204 | } 205 | 206 | }); 207 | 208 | }else{ 209 | ep.emit("login_error","userEmail","Email is not exists"); 210 | } 211 | 212 | return; 213 | 214 | }); 215 | 216 | } 217 | 218 | 219 | function register(req, res, next){ 220 | var userName = validator.trim(req.body.userName); 221 | var userEmail = validator.trim(req.body.userEmail); 222 | var passWord = validator.trim(req.body.passWord); 223 | var confirmPassword = validator.trim(req.body.confirmPassword); 224 | 225 | var ep = new eventproxy(); 226 | ep.fail(next); 227 | ep.once('register_error', function (name,notice) { 228 | helps.resJsonError(req,res,name,notice); 229 | }); 230 | 231 | if(userName.length < 6){ 232 | ep.emit("register_error","userName","Name's length is too short"); 233 | return; 234 | } 235 | 236 | if(!validator.isEmail(userEmail)){ 237 | ep.emit("register_error","userEmail","Email is not right"); 238 | return; 239 | } 240 | 241 | if(validator.isNull(passWord) || passWord.length < 6){ 242 | ep.emit("register_error","passWord","Password is too shorts"); 243 | return; 244 | } 245 | 246 | if(validator.isNull(passWord) || passWord != confirmPassword){ 247 | ep.emit("register_error","confirmPassword","ConfirmPassword is not the same as passWord"); 248 | return; 249 | } 250 | 251 | 252 | var checkEvent = new eventproxy(); 253 | checkEvent.all('checkName','checkEmail', function () { 254 | 255 | 256 | tools.bcryptGenSalt(passWord,function(error, hash){ 257 | if (error) { 258 | return next(error); 259 | } 260 | 261 | var newUser = { 262 | userName : userName, 263 | userEmail : userEmail, 264 | passWord : hash, 265 | userAvatar : config.upload.defaultAvatar 266 | }; 267 | 268 | Users.insert(newUser,function(error,user){ 269 | if (error) { 270 | return next(error); 271 | } 272 | 273 | if (user) { 274 | sendActiveEmail(user); 275 | var url = config.url.host+"/users/login" 276 | helps.jsonRedirect(res,url); 277 | }else{ 278 | ep.emit("register_error","userEmail","Error occurred when save user"); 279 | } 280 | }); 281 | 282 | }); 283 | 284 | 285 | 286 | }); 287 | 288 | Users.getByEmail(userEmail,function(error,user){ 289 | if (error) { 290 | return next(error); 291 | } 292 | if (user) { 293 | ep.emit("register_error","userEmail","Email had been registered"); 294 | }else{ 295 | checkEvent.emit("checkEmail"); 296 | } 297 | 298 | return; 299 | 300 | }); 301 | 302 | Users.getByName(userName,function(error,user){ 303 | 304 | if (error) { 305 | return next(error); 306 | } 307 | if (user) { 308 | ep.emit("register_error","userName","Name had been registered"); 309 | }else{ 310 | checkEvent.emit("checkName"); 311 | } 312 | 313 | return; 314 | 315 | }); 316 | } 317 | 318 | 319 | function sendActiveEmail(user){ 320 | var activeKey = tools.getActiveKey(user); 321 | 322 | var activeUrl = config.url.host+"/users/active?activeKey="+activeKey 323 | var from = config.email.from; 324 | var subject = "Active Email from LuckyBird"; 325 | var html = "

Wellcome to LuckyBird

This is your active url : "+activeUrl+"

"; 326 | email.sendMail(from,user.userEmail,subject,html); 327 | } 328 | 329 | 330 | 331 | -------------------------------------------------------------------------------- /middlewares/auth.js: -------------------------------------------------------------------------------- 1 | var config = require("../config"); 2 | var helps = require("../common/helps"); 3 | 4 | exports.saveUserSession = saveUserSession; 5 | exports.removeUserSession = removeUserSession; 6 | exports.getUserBySession = getUserBySession; 7 | exports.requiredLogin = requiredLogin; 8 | exports.getCsrfToken = getCsrfToken; 9 | 10 | function getUserBySession(req,callback){ 11 | if(req.session && req.session.user){ 12 | return callback(req.session.user); 13 | }else{ 14 | return callback(false);; 15 | } 16 | } 17 | 18 | function requiredLogin(req,res,next){ 19 | if(req.session && req.session.user){ 20 | return next(); 21 | }else{ 22 | var url = config.url.host; 23 | if(req.xhr){ 24 | helps.jsonRedirect(res,url); 25 | }else{ 26 | res.redirect(url); 27 | } 28 | 29 | } 30 | } 31 | 32 | function saveUserSession(req,user){ 33 | var session = req.session; 34 | session.user = user; 35 | } 36 | 37 | function removeUserSession(req,callback){ 38 | req.session.destroy(callback); 39 | } 40 | 41 | function getCsrfToken(req,res,next){ 42 | req.session.csrfSecret = undefined; 43 | res.locals.csrfToken = req.csrfToken(); 44 | return next(); 45 | } 46 | 47 | -------------------------------------------------------------------------------- /models/base.js: -------------------------------------------------------------------------------- 1 | var tools = require('../common/tools'); 2 | 3 | module.exports = function (schema) { 4 | schema.methods.create_at_ago = function () { 5 | return tools.formatDate(this.create_at, true); 6 | }; 7 | 8 | schema.methods.update_at_ago = function () { 9 | return tools.formatDate(this.update_at, true); 10 | }; 11 | }; -------------------------------------------------------------------------------- /models/comment.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var ObjectId = Schema.ObjectId; 4 | var Base = require("./base"); 5 | var CommentSchema = new Schema({ 6 | content: { type: String}, 7 | userName:{type:String}, 8 | topicId:{type:ObjectId}, 9 | create_at: { type: Date, default: Date.now }, 10 | update_at: { type: Date, default: Date.now } 11 | 12 | }); 13 | CommentSchema.plugin(Base); 14 | mongoose.model('Comment', CommentSchema); -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | require('./users'); 2 | require('./tag'); 3 | require('./topic'); 4 | require('./comment'); 5 | 6 | var mongoose = require('mongoose'); 7 | var config = require('../config'); 8 | var database = config.database; 9 | 10 | mongoose.connect(database.address, { 11 | server: {poolSize: 20} 12 | }, function (error) { 13 | if (error) { 14 | console.log('connect to '+database.address+' error: '+error.message); 15 | process.exit(1); 16 | } 17 | }); 18 | 19 | exports.Users = mongoose.model('Users'); 20 | exports.Tag = mongoose.model('Tag'); 21 | exports.Topic = mongoose.model('Topic'); 22 | exports.Comment = mongoose.model('Comment'); 23 | 24 | 25 | -------------------------------------------------------------------------------- /models/tag.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var TagSchema = new Schema({ 5 | tagName: { type: String}, 6 | showName:{type:String}, 7 | useCount:{type:Number,default:0}, 8 | create_at: { type: Date, default: Date.now }, 9 | update_at: { type: Date, default: Date.now } 10 | 11 | }); 12 | 13 | TagSchema.index({tagName: 1}, {unique: true}); 14 | 15 | mongoose.model('Tag', TagSchema); -------------------------------------------------------------------------------- /models/topic.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var Base = require("./base"); 4 | var TopicSchema = new Schema({ 5 | title: { type: String}, 6 | content:{type:String}, 7 | tagsId:{type:Array}, 8 | userName:{type:String}, 9 | Markdown:{type:String}, 10 | openCount:{type:Number,default:0}, 11 | commentCount:{type:Number,default:0}, 12 | create_at: { type: Date, default: Date.now }, 13 | update_at: { type: Date, default: Date.now } 14 | 15 | }); 16 | 17 | TopicSchema.plugin(Base); 18 | 19 | mongoose.model('Topic', TopicSchema); -------------------------------------------------------------------------------- /models/users.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var Base = require("./base"); 4 | var UserSchema = new Schema({ 5 | userName: { type: String}, 6 | userEmail: { type: String}, 7 | passWord: { type: String }, 8 | userAvatar:{type:String}, 9 | activeState : {type: Boolean, default: false}, 10 | create_at: { type: Date, default: Date.now }, 11 | update_at: { type: Date, default: Date.now } 12 | 13 | }); 14 | 15 | UserSchema.index({userName: 1}, {unique: true}); 16 | UserSchema.index({userEmail: 1}, {unique: true}); 17 | UserSchema.plugin(Base); 18 | 19 | mongoose.model('Users', UserSchema); 20 | 21 | -------------------------------------------------------------------------------- /npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/usr/bin/nodejs', '/usr/bin/npm', 'start' ] 3 | 2 info using npm@2.14.12 4 | 3 info using node@v4.2.4 5 | 4 verbose run-script [ 'prestart', 'start', 'poststart' ] 6 | 5 info prestart myExpressApp@0.0.0 7 | 6 info start myExpressApp@0.0.0 8 | 7 verbose unsafe-perm in lifecycle true 9 | 8 info myExpressApp@0.0.0 Failed to exec start script 10 | 9 verbose stack Error: myExpressApp@0.0.0 start: `node ./bin/www` 11 | 9 verbose stack Exit status 1 12 | 9 verbose stack at EventEmitter. (/usr/lib/node_modules/npm/lib/utils/lifecycle.js:214:16) 13 | 9 verbose stack at emitTwo (events.js:87:13) 14 | 9 verbose stack at EventEmitter.emit (events.js:172:7) 15 | 9 verbose stack at ChildProcess. (/usr/lib/node_modules/npm/lib/utils/spawn.js:24:14) 16 | 9 verbose stack at emitTwo (events.js:87:13) 17 | 9 verbose stack at ChildProcess.emit (events.js:172:7) 18 | 9 verbose stack at maybeClose (internal/child_process.js:818:16) 19 | 9 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:211:5) 20 | 10 verbose pkgid myExpressApp@0.0.0 21 | 11 verbose cwd /mnt/hgfs/ShareFolder/web/NodeJS-learn-forum 22 | 12 error Linux 3.19.0-25-generic 23 | 13 error argv "/usr/bin/nodejs" "/usr/bin/npm" "start" 24 | 14 error node v4.2.4 25 | 15 error npm v2.14.12 26 | 16 error code ELIFECYCLE 27 | 17 error myExpressApp@0.0.0 start: `node ./bin/www` 28 | 17 error Exit status 1 29 | 18 error Failed at the myExpressApp@0.0.0 start script 'node ./bin/www'. 30 | 18 error This is most likely a problem with the myExpressApp package, 31 | 18 error not with npm itself. 32 | 18 error Tell the author that this fails on your system: 33 | 18 error node ./bin/www 34 | 18 error You can get their info via: 35 | 18 error npm owner ls myExpressApp 36 | 18 error There is likely additional logging output above. 37 | 19 verbose exit [ 1, true ] 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myExpressApp", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "bcrypt": "^0.8.5", 10 | "body-parser": "~1.13.2", 11 | "connect-mongo": "~1.1.0", 12 | "cookie-parser": "~1.3.5", 13 | "csurf": "^1.8.3", 14 | "debug": "~2.2.0", 15 | "eventproxy": "~0.3.4", 16 | "express": "~4.13.1", 17 | "express-session": "~1.12.1", 18 | "marked": "^0.3.5", 19 | "mkdirp": "^0.5.1", 20 | "moment": "~2.10.6", 21 | "mongoose": "~4.3.3", 22 | "morgan": "~1.6.1", 23 | "multer": "^1.1.0", 24 | "nodemailer": "^1.10.0", 25 | "serve-favicon": "~2.3.0", 26 | "utility": "^1.6.0", 27 | "validator": "~4.4.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /proxy/comment.js: -------------------------------------------------------------------------------- 1 | var models = require('../models'); 2 | var Comment = models.Comment; 3 | 4 | 5 | 6 | exports.insert = function(comment,callback){ 7 | var newComment = new Comment(comment); 8 | newComment.save(callback); 9 | } 10 | 11 | exports.save = function(comment,callback){ 12 | comment.save(callback); 13 | } 14 | 15 | exports.getById = function(_id,callback){ 16 | Comment.findById(_id, callback); 17 | } 18 | 19 | exports.getTopicComments = function(topicId,sort,callback){ 20 | Comment.find({topicId:topicId},'',{sort:sort}, callback); 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /proxy/index.js: -------------------------------------------------------------------------------- 1 | exports.Users = require('./users'); 2 | exports.Tag = require('./tag'); 3 | exports.Topic = require('./topic'); 4 | exports.Comment = require('./comment'); 5 | -------------------------------------------------------------------------------- /proxy/tag.js: -------------------------------------------------------------------------------- 1 | var models = require('../models'); 2 | var Tag = models.Tag; 3 | 4 | 5 | exports.getByName = function (tagName,callback){ 6 | Tag.findOne({tagName: tagName}, callback); 7 | }; 8 | 9 | exports.insert = function(tag,callback){ 10 | var newTag = new Tag(tag); 11 | newTag.save(callback); 12 | } 13 | 14 | exports.save = function(tag,callback){ 15 | tag.save(callback); 16 | } 17 | 18 | exports.update = function(_id,update,callback){ 19 | Tag.update({_id:_id},{$set:update},callback); 20 | } 21 | 22 | exports.getAllTags = function(callback){ 23 | Tag.find({},callback); 24 | } 25 | 26 | exports.getById = function(_id,callback){ 27 | Tag.findById(_id, callback); 28 | } 29 | 30 | exports.getByPage = function(query,fields,skip,limit,sort,callback){ 31 | Tag.find(query, fields, { skip: skip, limit: limit,sort:sort }, callback); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /proxy/topic.js: -------------------------------------------------------------------------------- 1 | var models = require('../models'); 2 | var Topic = models.Topic; 3 | 4 | 5 | exports.insert = function(topic,callback){ 6 | var newTopic = new Topic(topic); 7 | newTopic.save(callback); 8 | } 9 | 10 | exports.save = function(topic,callback){ 11 | Topic.save(callback); 12 | } 13 | 14 | exports.update = function(_id,update,callback){ 15 | Topic.update({_id:_id},{$set:update},callback); 16 | } 17 | 18 | exports.getByPage = function(query,fields,skip,limit,sort,callback){ 19 | Topic.find(query, fields, { skip: skip, limit: limit,sort:sort}, callback); 20 | } 21 | 22 | exports.getById = function(_id,callback){ 23 | Topic.findById(_id, callback); 24 | } 25 | 26 | exports.getUserTopics = function(userName,callback){ 27 | Topic.find({userName:userName}, callback); 28 | } 29 | -------------------------------------------------------------------------------- /proxy/users.js: -------------------------------------------------------------------------------- 1 | var models = require('../models'); 2 | var Users = models.Users; 3 | 4 | 5 | 6 | exports.insert = function (user,callback) { 7 | var newUser = new Users(user); 8 | newUser.save(callback); 9 | }; 10 | 11 | exports.save = function(user,callback){ 12 | user.save(callback); 13 | } 14 | 15 | exports.update = function(_id,update,callback){ 16 | Users.update({_id:_id},{$set:update},callback); 17 | } 18 | 19 | exports.getByEmail = function (userEmail,callback){ 20 | Users.findOne({userEmail: userEmail}, callback); 21 | }; 22 | 23 | exports.getByName = function (userName,callback){ 24 | Users.findOne({userName: userName}, callback); 25 | }; 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/images/default-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/images/default-avatar.png -------------------------------------------------------------------------------- /public/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/images/loading.gif -------------------------------------------------------------------------------- /public/javascripts/ajax-upload.js: -------------------------------------------------------------------------------- 1 | jQuery.extend({ 2 | createUploadIframe: function(id, uri) 3 | { 4 | //create frame 5 | var frameId = 'jUploadFrame' + id; 6 | var iframeHtml = '