Coverage

18%
2015
373
1642

app.js

65%
26
17
9
LineHitsSource
1/*!
2 * nodeclub - app.js
3 */
4
5/**
6 * Module dependencies.
7 */
8
91var path = require('path');
101var connect = require('connect');
111var urlrouter = require('urlrouter');
121var render = require('connect-render');
131var onehost = require('onehost');
14
151var config = require('./conf');
161var routes = require('./routes');
17
181var app = connect(
19 connect.cookieParser(),
20 connect.session({
21 secret: config.session_secret
22 }),
23 connect.csrf(),
24 render({
25 root: path.join(__dirname, 'views'),
26 layout: 'layout.html',
27 cache: config.debug, // `false` for debug
28 helpers: {
29 config: config,
30 sitename: 'connect-render demo site',
31 starttime: new Date(),
32 _csrf: function (req, res) {
331 return req.session._csrf;
34 },
35 now: function (req, res) {
361 return new Date();
37 }
38 }
39 })
40);
41
42/**
43 * Static files
44 */
45
461app.use('/public', connect.static(path.join(__dirname, 'public')));
47
48/**
49 * URL Routing
50 */
511app.use(urlrouter(routes));
52
53/**
54 * One host binding
55 */
56
571if (config.onehost) {
580 onehost({
59 host: config.hostname
60 });
61}
62
631if (process.env.NODE_ENV !== 'test') {
64 // plugins
650 var plugins = config.plugins || [];
660 for (var i = 0, l = plugins.length; i < l; i++) {
670 var p = plugins[i];
680 app.use(require('./plugins/' + p.name)(p.options));
69 }
70}
71
721var maxAge = 3600000 * 24 * 30;
73// app.use('/upload/', express.static(config.upload_dir, { maxAge: maxAge }));
74// // old image url: http://host/user_data/images/xxxx
75// app.use('/user_data/', express.static(path.join(__dirname, 'public', 'user_data'), { maxAge: maxAge }));
76
77// var staticDir = path.join(__dirname, 'public');
78// app.configure('development', function () {
79// app.use(express.static(staticDir));
80// app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
81// });
82
83// app.configure('production', function () {
84// app.use(express.static(staticDir, { maxAge: maxAge }));
85// app.use(express.errorHandler());
86// app.set('view cache', true);
87// });
88
89
901if (process.env.NODE_ENV !== 'test') {
910 app.listen(config.port);
92
930 console.log("NodeClub listening on port %d", config.port);
940 console.log("God bless love....");
950 console.log("You can debug your app with http://" + config.hostname + ':' + config.port);
96}
97
981module.exports = app;

conf/index.js

76%
13
10
3
LineHitsSource
1/*!
2 * nodeclub - conf/index.js
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * MIT Licensed
5 */
6
71"use strict";
8
9/**
10 * Module dependencies.
11 */
12
131var path = require('path');
141var fs = require('fs');
151fs.existsSync = fs.existsSync || path.existsSync;
16
171var settings = {
18 debug: true,
19 name: 'NodeClub',
20 version: '0.2.2',
21
22 // site settings
23 site_headers: [
24 '<meta name="author" content="EDP@TAOBAO" />',
25 ],
26 host: 'localhost.cnodejs.org',
27 port: 3000,
28 site_logo: '', // default is `name`
29 site_navs: [
30 // [ path, title, [target=''] ]
31 [ '/about', '关于' ],
32 ],
33 site_static_host: '/public', // 静态文件存储域名
34 site_enable_search_preview: false, // 开启google search preview
35 site_google_search_domain: 'cnodejs.org', // google search preview中要搜索的域名
36
37 upload_dir: path.join(__dirname, 'public', 'user_data', 'images'),
38
39 db: 'mongodb://127.0.0.1/node_club_dev',
40 session_secret: 'node_club',
41 auth_cookie_name: 'node_club',
42
43 // 话题列表显示的话题数量
44 list_topic_count: 20,
45
46 // RSS
47 rss: {
48 title: 'CNode:Node.js专业中文社区',
49 link: 'http://cnodejs.org',
50 language: 'zh-cn',
51 description: 'CNode:Node.js专业中文社区',
52
53 //最多获取的RSS Item数量
54 max_rss_items: 50
55 },
56
57 // site links
58 site_links: [
59 {
60 'text': 'Node 官方网站',
61 'url': 'http://nodejs.org/'
62 },
63 {
64 'text': 'Node Party',
65 'url': 'http://party.cnodejs.net/'
66 },
67 {
68 'text': 'Node 入门',
69 'url': 'http://nodebeginner.org/index-zh-cn.html'
70 },
71 {
72 'text': 'Node 中文文档',
73 'url': 'http://docs.cnodejs.net/cman/'
74 }
75 ],
76
77 // sidebar ads
78 side_ads: [
79 {
80 'url': 'http://www.upyun.com/?utm_source=nodejs&utm_medium=link&utm_campaign=upyun&md=nodejs',
81 'image': 'http://site-cnode.b0.upaiyun.com/images/upyun_logo.png',
82 'text': ''
83 },
84 {
85 'url': 'http://ruby-china.org/?utm_source=nodejs&utm_medium=link&utm_campaign=upyun&md=nodejs',
86 'image': 'http://site-cnode.b0.upaiyun.com/images/ruby_china_logo.png',
87 'text': ''
88 },
89 {
90 'url': 'http://adc.taobao.com/',
91 'image': 'http://adc.taobao.com/bundles/devcarnival/images/d2_180x250.jpg',
92 'text': ''
93 }
94 ],
95
96 // mail SMTP
97 mail_port: 25,
98 mail_user: 'club',
99 mail_pass: 'club',
100 mail_host: 'smtp.126.com',
101 mail_sender: 'club@126.com',
102 mail_use_authentication: true,
103
104 //weibo app key
105 weibo_key: 10000000,
106
107 // admin 可删除话题,编辑标签,设某人为达人
108 admins: { admin: true },
109
110 // [ { name: 'plugin_name', options: { ... }, ... ]
111 plugins: [
112 // { name: 'onehost', options: { host: 'localhost.cnodejs.org' } },
113 // { name: 'wordpress_redirect', options: {} }
114 ]
115};
116
117/**
118 * Loading custom settings
119 */
120
1211var configPath = path.join(__dirname, 'config.js');
1221if (fs.existsSync(configPath)) {
1230 var config = require('configPath');
1240 for (var k in config) {
1250 settings[k] = config[k];
126 }
127}
128
129// host: http://127.0.0.1
1301var urlinfo = require('url').parse(settings.host);
1311settings.hostname = urlinfo.hostname || settings.host;
132
1331module.exports = settings;

routes.js

100%
67
67
0
LineHitsSource
1/*!
2 * nodeclub - route.js
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * MIT Licensed
5 */
6
7/**
8 * Module dependencies.
9 */
10
111var home = require('./controllers/home');
121var sign = require('./controllers/sign');
131var site = require('./controllers/site');
141var user = require('./controllers/user');
151var message = require('./controllers/message');
161var tag = require('./controllers/tag');
171var topic = require('./controllers/topic');
181var reply = require('./controllers/reply');
191var rss = require('./controllers/rss');
201var upload = require('./controllers/upload');
211var static = require('./controllers/static');
221var tools = require('./controllers/tools');
23// var status = require('./controllers/status');
24
251module.exports = function routes(app) {
26 // home page
271 app.get('/', home);
28
29 // sign up, login, logout
301 app.get('/signup', sign.signup);
311 app.post('/signup', sign.signup);
321 app.get('/signout', sign.signout);
331 app.get('/signin', sign.showLogin);
341 app.post('/signin', sign.login);
351 app.get('/active_account', sign.active_account);
36
37 // password
381 app.get('/search_pass', sign.search_pass);
391 app.post('/search_pass', sign.search_pass);
401 app.get('/reset_pass', sign.reset_pass);
411 app.post('/reset_pass', sign.reset_pass);
42
43 // user
441 app.get('/user/:name', user.index);
451 app.get('/setting', user.setting);
461 app.post('/setting', user.setting);
471 app.get('/stars', user.show_stars);
481 app.get('/users/top100', user.top100);
491 app.get('/my/tags', user.get_collect_tags);
501 app.get('/my/topics', user.get_collect_topics);
511 app.get('/my/messages', message.index);
521 app.get('/my/follower', user.get_followers);
531 app.get('/my/following', user.get_followings);
541 app.get('/user/:name/topics', user.list_topics);
551 app.get('/user/:name/replies', user.list_replies);
561 app.post('/user/follow', user.follow);
571 app.post('/user/un_follow', user.un_follow);
581 app.post('/user/set_star', user.toggle_star);
591 app.post('/user/cancel_star', user.toggle_star);
60
61 // message
621 app.post('/messages/mark_read', message.mark_read);
631 app.post('/messages/mark_all_read', message.mark_all_read);
64
65 // tag
661 app.get('/tags/edit', tag.edit_tags);
671 app.get('/tag/:name', tag.list_topic);
681 app.get('/tag/:name/edit', tag.edit);
691 app.get('/tag/:name/delete', tag.delete);
701 app.post('/tag/add', tag.add);
711 app.post('/tag/:name/edit', tag.edit);
721 app.post('/tag/collect', tag.collect);
731 app.post('/tag/de_collect', tag.de_collect);
74
75 // topic
761 app.get('/topic/create', topic.create);
771 app.get('/topic/:tid', topic.index);
781 app.get('/topic/:tid/top/:is_top?', topic.top);
791 app.get('/topic/:tid/edit', topic.edit);
801 app.get('/topic/:tid/delete', topic.delete);
811 app.post('/topic/create', topic.create);
821 app.post('/topic/:tid/edit', topic.edit);
831 app.post('/topic/collect', topic.collect);
841 app.post('/topic/de_collect', topic.de_collect);
85
86 // reply
871 app.post('/:topic_id/reply', reply.add);
881 app.post('/:topic_id/reply2', reply.add_reply2);
891 app.post('/reply/:reply_id/delete', reply.delete);
90
91 // upload
921 app.post('/upload/image', upload.uploadImage);
93
94 // tools
951 app.get('/site_tools', tools.run_site_tools);
96
97 // static
981 app.get('/about', static.about);
991 app.get('/faq', static.faq);
100
101 //rss
1021 app.get('/rss', rss.index);
103
104 // site status
105 // app.get('/status', status.status);
106};

controllers/home.js

100%
3
3
0
LineHitsSource
1/*!
2 * nodeclub - controllers/home.js
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * MIT Licensed
5 */
6
71"use strict";
8
9/**
10 * Module dependencies.
11 */
12
131module.exports = function (req, res, next) {
141 res.render('index.html');
15};

controllers/sign.js

10%
225
23
202
LineHitsSource
11var models = require('../models');
21var User = models.User;
3
41var check = require('validator').check;
51var sanitize = require('validator').sanitize;
6
71var crypto = require('crypto');
81var config = require('../conf');
9
101var message_ctrl = require('./message');
111var mail_ctrl = require('./mail');
12
13//sign up
141exports.signup = function(req,res,next){
150 var method = req.method.toLowerCase();
160 if (method === 'get'){
170 res.render('sign/signup');
180 return;
19 }
200 if(method === 'post'){
210 var name = sanitize(req.body.name).trim();
220 name = sanitize(name).xss();
230 var loginname = name.toLowerCase();
240 var pass = sanitize(req.body.pass).trim();
250 pass = sanitize(pass).xss();
260 var email = sanitize(req.body.email).trim();
270 email = email.toLowerCase();
280 email = sanitize(email).xss();
290 var re_pass = sanitize(req.body.re_pass).trim();
300 re_pass = sanitize(re_pass).xss();
31
320 if(name == '' || pass =='' || re_pass == '' || email ==''){
330 res.render('sign/signup', {error:'信息不完整。',name:name,email:email});
340 return;
35 }
36
370 if(name.length < 5){
380 res.render('sign/signup', {error:'用户名至少需要5个字符。',name:name,email:email});
390 return;
40 }
41
420 try{
430 check(name, '用户名只能使用0-9,a-z,A-Z。').isAlphanumeric();
44 }catch(e){
450 res.render('sign/signup', {error:e.message,name:name,email:email});
460 return;
47 }
48
490 if(pass != re_pass){
500 res.render('sign/signup', {error:'两次密码输入不一致。',name:name,email:email});
510 return;
52 }
53
540 try{
550 check(email, '不正确的电子邮箱。').isEmail();
56 }catch(e){
570 res.render('sign/signup', {error:e.message,name:name,email:email});
580 return;
59 }
60
610 User.find({'$or':[{'loginname':loginname},{'email':email}]},function(err,users){
620 if(err) return next(err);
630 if(users.length > 0){
640 res.render('sign/signup', {error:'用户名或邮箱已被使用。',name:name,email:email});
650 return;
66 }
67
68 // md5 the pass
690 pass = md5(pass);
70 // create gavatar
710 var avatar_url = 'http://www.gravatar.com/avatar/' + md5(email) + '?size=48';
72
730 var user = new User();
740 user.name = name;
750 user.loginname = loginname;
760 user.pass = pass;
770 user.email = email;
780 user.avatar = avatar_url;
790 user.active = false;
800 user.save(function(err){
810 if (err) {
820 return next(err);
83 }
840 mail_ctrl.send_active_mail(email,md5(email + config.session_secret), name,email,function(err,success){
850 if(success){
860 res.render('sign/signup', {success:'欢迎加入 ' + config.name + '!我们已给您的注册邮箱发送了一封邮件,请点击里面的链接来激活您的帐号。'});
870 return;
88 }
89 });
90 });
91 });
92 }
93};
94
95/**
96 * Show user login page.
97 *
98 * @param {HttpRequest} req
99 * @param {HttpResponse} res
100 */
1011exports.showLogin = function(req, res) {
1020 req.session._loginReferer = req.headers.referer;
1030 res.render('sign/signin');
104};
105/**
106 * define some page when login just jump to the home page
107 * @type {Array}
108 */
1091var notJump = [
110 '/active_account', //active page
111 '/reset_pass', //reset password page, avoid to reset twice
112 '/signup', //regist page
113 '/search_pass' //serch pass page
114];
115/**
116 * Handle user login.
117 *
118 * @param {HttpRequest} req
119 * @param {HttpResponse} res
120 * @param {Function} next
121 */
1221exports.login = function(req, res, next) {
1230 var loginname = sanitize(req.body.name).trim().toLowerCase();
1240 var pass = sanitize(req.body.pass).trim();
125
1260 if (!loginname || !pass) {
1270 return res.render('sign/signin', { error: '信息不完整。' });
128 }
129
1300 User.findOne({ 'loginname': loginname }, function (err, user) {
1310 if (err) return next(err);
1320 if (!user) {
1330 return res.render('sign/signin', { error:'这个用户不存在。' });
134 }
1350 pass = md5(pass);
1360 if (pass !== user.pass) {
1370 return res.render('sign/signin', { error:'密码错误。' });
138 }
1390 if (!user.active) {
1400 res.render('sign/signin', { error:'此帐号还没有被激活。' });
1410 return;
142 }
143 // store session cookie
1440 gen_session(user, res);
145 //check at some page just jump to home page
1460 var refer = req.session._loginReferer || 'home';
1470 for (var i=0, len=notJump.length; i!=len; ++i) {
1480 if (refer.indexOf(notJump[i]) >= 0) {
1490 refer = 'home';
1500 break;
151 }
152 }
1530 res.redirect(refer);
154 });
155};
156
157// sign out
1581exports.signout = function(req, res, next) {
1590 req.session.destroy();
1600 res.clearCookie(config.auth_cookie_name, { path: '/' });
1610 res.redirect(req.headers.referer || 'home');
162};
163
1641exports.active_account = function(req,res,next) {
1650 var key = req.query.key;
1660 var name = req.query.name;
1670 var email = req.query.email;
168
1690 User.findOne({name:name},function(err,user){
1700 if(!user || md5(email+config.session_secret) != key){
1710 res.render('notify/notify',{error: '信息有误,帐号无法被激活。'});
1720 return;
173 }
1740 if(user.active){
1750 res.render('notify/notify',{error: '帐号已经是激活状态。'});
1760 return;
177 }
1780 user.active = true;
1790 user.save(function(err){
1800 res.render('notify/notify',{success: '帐号已被激活,请登录'});
181 });
182 });
183}
184
1851exports.search_pass = function(req,res,next){
1860 var method = req.method.toLowerCase();
1870 if(method == 'get'){
1880 res.render('sign/search_pass');
189 }
1900 if(method == 'post'){
1910 var email = req.body.email;
1920 email = email.toLowerCase();
193
1940 try{
1950 check(email, '不正确的电子邮箱。').isEmail();
196 }catch(e){
1970 res.render('sign/search_pass', {error:e.message,email:email});
1980 return;
199 }
200
201 // User.findOne({email:email},function(err,user){
202 //动态生成retrive_key和timestamp到users collection,之后重置密码进行验证
2030 var retrieveKey = randomString(15);
2040 var retrieveTime = new Date().getTime();
2050 User.findOne({email : email}, function(err, user) {
2060 if(!user) {
2070 res.render('sign/search_pass', {error:'没有这个电子邮箱。',email:email});
2080 return;
209 }
2100 user.retrieve_key = retrieveKey;
2110 user.retrieve_time = retrieveTime;
2120 user.save(function(err) {
2130 if(err) {
2140 return next(err);
215 }
2160 mail_ctrl.send_reset_pass_mail(email, retrieveKey, user.name, function(err,success) {
2170 res.render('notify/notify',{success: '我们已给您填写的电子邮箱发送了一封邮件,请在24小时内点击里面的链接来重置密码。'});
218 });
219 });
220 });
221 }
222}
223/**
224 * reset password
225 * 'get' to show the page, 'post' to reset password
226 * after reset password, retrieve_key&time will be destroy
227 * @param {http.req} req
228 * @param {http.res} res
229 * @param {Function} next
230 */
2311exports.reset_pass = function(req,res,next) {
2320 var method = req.method.toLowerCase();
2330 if(method === 'get') {
2340 var key = req.query.key;
2350 var name = req.query.name;
2360 User.findOne({name:name, retrieve_key:key},function(err,user) {
2370 if(!user) {
2380 return res.render('notify/notify',{error: '信息有误,密码无法重置。'});
239 }
2400 var now = new Date().getTime();
2410 var oneDay = 1000 * 60 * 60 * 24;
2420 if(!user.retrieve_time || now - user.retrieve_time > oneDay) {
2430 return res.render('notify/notify', {error : '该链接已过期,请重新申请。'});
244 }
2450 return res.render('sign/reset', {name : name, key : key});
246 });
247 } else {
2480 var psw = req.body.psw || '';
2490 var repsw = req.body.repsw || '';
2500 var key = req.body.key || '';
2510 var name = req.body.name || '';
2520 if(psw !== repsw) {
2530 return res.render('sign/reset', {name : name, key : key, error : '两次密码输入不一致。'});
254 }
2550 User.findOne({name:name, retrieve_key: key}, function(err, user) {
2560 if(!user) {
2570 return res.render('notify/notify', {error : '错误的激活链接'});
258 }
2590 user.pass = md5(psw);
2600 user.retrieve_key = null;
2610 user.retrieve_time = null;
2620 user.active = true; // 用户激活
2630 user.save(function(err) {
2640 if(err) {
2650 return next(err);
266 }
2670 return res.render('notify/notify', {success: '你的密码已重置。'});
268 })
269 })
270 }
271}
272
2731function getAvatarURL(user) {
2740 if (user.avatar_url) {
2750 return user.avatar_url;
276 }
2770 var avatar_url = user.profile_image_url || user.avatar;
2780 if (!avatar_url) {
2790 avatar_url = config.site_static_host + '/images/user_icon&48.png';
280 }
2810 return avatar_url;
282}
283
284// auth_user middleware
2851exports.auth_user = function(req, res, next) {
2860 if (req.session.user) {
2870 if (config.admins[req.session.user.name]) {
2880 req.session.user.is_admin = true;
289 }
2900 message_ctrl.get_messages_count(req.session.user._id, function (err, count) {
2910 if (err) {
2920 return next(err);
293 }
2940 req.session.user.messages_count = count;
2950 if (!req.session.user.avatar_url) {
2960 req.session.user.avatar_url = getAvatarURL(req.session.user);
297 }
2980 res.local('current_user', req.session.user);
2990 return next();
300 });
301 } else {
3020 var cookie = req.cookies[config.auth_cookie_name];
3030 if (!cookie) return next();
304
3050 var auth_token = decrypt(cookie, config.session_secret);
3060 var auth = auth_token.split('\t');
3070 var user_id = auth[0];
3080 User.findOne({_id:user_id},function (err,user){
3090 if (err) {
3100 return next(err);
311 }
3120 if (user) {
3130 if(config.admins[user.name]){
3140 user.is_admin = true;
315 }
3160 message_ctrl.get_messages_count(user._id,function(err,count){
3170 if(err) return next(err);
3180 user.messages_count = count;
3190 req.session.user = user;
3200 req.session.user.avatar_url = user.avatar_url;
3210 res.local('current_user',req.session.user);
3220 return next();
323 });
324 }else{
3250 return next();
326 }
327 });
328 }
329};
330
331// private
3321function gen_session(user,res) {
3330 var auth_token = encrypt(user._id + '\t'+user.name + '\t' + user.pass +'\t' + user.email, config.session_secret);
3340 res.cookie(config.auth_cookie_name, auth_token, {path: '/',maxAge: 1000*60*60*24*30}); //cookie 有效期30天
335}
3361function encrypt(str,secret) {
3370 var cipher = crypto.createCipher('aes192', secret);
3380 var enc = cipher.update(str,'utf8','hex');
3390 enc += cipher.final('hex');
3400 return enc;
341}
3421function decrypt(str,secret) {
3430 var decipher = crypto.createDecipher('aes192', secret);
3440 var dec = decipher.update(str,'hex','utf8');
3450 dec += decipher.final('utf8');
3460 return dec;
347}
3481function md5(str) {
3490 var md5sum = crypto.createHash('md5');
3500 md5sum.update(str);
3510 str = md5sum.digest('hex');
3520 return str;
353}
3541function randomString(size) {
3550 size = size || 6;
3560 var code_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
3570 var max_num = code_string.length + 1;
3580 var new_pass = '';
3590 while(size>0){
3600 new_pass += code_string.charAt(Math.floor(Math.random()* max_num));
3610 size--;
362 }
3630 return new_pass;
364}

models/index.js

91%
24
22
2
LineHitsSource
11var mongoose = require('mongoose');
21var config = require('../conf');
3
41mongoose.connect(config.db, function (err) {
51 if (err) {
60 console.error('connect to %s error: ', config.db, err.message);
70 process.exit(1);
8 }
9});
10
11// models
121require('./tag');
131require('./user');
141require('./topic');
151require('./topic_tag');
161require('./reply');
171require('./topic_collect');
181require('./tag_collect');
191require('./relation');
201require('./message');
21
221exports.Tag = mongoose.model('Tag');
231exports.User = mongoose.model('User');
241exports.Topic = mongoose.model('Topic');
251exports.TopicTag = mongoose.model('TopicTag');
261exports.Reply = mongoose.model('Reply');
271exports.TopicCollect = mongoose.model('TopicCollect');
281exports.TagCollect = mongoose.model('TagCollect');
291exports.Relation = mongoose.model('Relation');
301exports.Message = mongoose.model('Message');

models/tag.js

100%
4
4
0
LineHitsSource
11var mongoose = require('mongoose');
21var Schema = mongoose.Schema;
3
41var TagSchema = new Schema({
5 name: { type: String },
6 order: { type: Number, default: 1 },
7 description: { type: String },
8 background: { type: String },
9 topic_count: { type: Number, default: 0 },
10 collect_count: { type: Number, default: 0 },
11 create_at: { type: Date, default: Date.now }
12});
13
141mongoose.model('Tag', TagSchema);

models/user.js

60%
10
6
4
LineHitsSource
11var mongoose = require('mongoose');
21var Schema = mongoose.Schema;
31var config = require('../conf');
4
51var UserSchema = new Schema({
6 name: { type: String, index: true },
7 loginname: { type: String, unique: true },
8 pass: { type: String },
9 email: { type: String, unique: true },
10 url: { type: String },
11 profile_image_url: {type: String},
12 location: { type: String },
13 signature: { type: String },
14 profile: { type: String },
15 weibo: { type: String },
16 avatar: { type: String },
17
18 score: { type: Number, default: 0 },
19 topic_count: { type: Number, default: 0 },
20 reply_count: { type: Number, default: 0 },
21 follower_count: { type: Number, default: 0 },
22 following_count: { type: Number, default: 0 },
23 collect_tag_count: { type: Number, default: 0 },
24 collect_topic_count: { type: Number, default: 0 },
25 create_at: { type: Date, default: Date.now },
26 update_at: { type: Date, default: Date.now },
27 is_star: { type: Boolean },
28 level: { type: String },
29 active: { type: Boolean, default: true },
30
31 receive_reply_mail: {type: Boolean, default: false },
32 receive_at_mail: { type: Boolean, default: false },
33 from_wp: { type: Boolean },
34
35 retrieve_time : {type: Number},
36 retrieve_key : {type: String}
37});
38
391UserSchema.virtual('avatar_url').get(function () {
400 var avatar_url = this.profile_image_url || this.avatar;
410 if (!avatar_url) {
420 avatar_url = config.site_static_host + '/images/user_icon&48.png';
43 }
440 return avatar_url;
45});
46
471mongoose.model('User', UserSchema);

models/topic.js

100%
5
5
0
LineHitsSource
11var mongoose = require('mongoose');
21var Schema = mongoose.Schema;
31var ObjectId = Schema.ObjectId;
4
51var TopicSchema = new Schema({
6 title: { type: String },
7 content: { type: String },
8 author_id: { type: ObjectId },
9 top: { type: Boolean, default: false },
10 reply_count: { type: Number, default: 0 },
11 visit_count: { type: Number, default: 0 },
12 collect_count: { type: Number, default: 0 },
13 create_at: { type: Date, default: Date.now },
14 update_at: { type: Date, default: Date.now },
15 last_reply: { type: ObjectId },
16 last_reply_at: { type: Date, default: Date.now },
17 content_is_html: { type: Boolean }
18});
19
201mongoose.model('Topic', TopicSchema);

models/topic_tag.js

100%
5
5
0
LineHitsSource
11var mongoose = require('mongoose');
21var Schema = mongoose.Schema;
31var ObjectId = Schema.ObjectId;
4
51var TopicTagSchema = new Schema({
6 topic_id: { type: ObjectId },
7 tag_id: { type: ObjectId },
8 create_at: { type: Date, default: Date.now }
9});
10
111mongoose.model('TopicTag', TopicTagSchema);

models/reply.js

100%
5
5
0
LineHitsSource
11var mongoose = require('mongoose');
21var Schema = mongoose.Schema;
31var ObjectId = Schema.ObjectId;
4
51var ReplySchema = new Schema({
6 content: { type: String },
7 topic_id: { type: ObjectId, index: true },
8 author_id: { type: ObjectId },
9 reply_id : { type: ObjectId },
10 create_at: { type: Date, default: Date.now },
11 update_at: { type: Date, default: Date.now },
12 content_is_html: { type: Boolean }
13});
14
151mongoose.model('Reply', ReplySchema);

models/topic_collect.js

100%
5
5
0
LineHitsSource
11var mongoose = require('mongoose');
21var Schema = mongoose.Schema;
31var ObjectId = Schema.ObjectId;
4
51var TopicCollectSchema = new Schema({
6 user_id: { type: ObjectId },
7 topic_id: { type: ObjectId },
8 create_at: { type: Date, default: Date.now }
9});
10
111mongoose.model('TopicCollect', TopicCollectSchema);

models/tag_collect.js

100%
5
5
0
LineHitsSource
11var mongoose = require('mongoose');
21var Schema = mongoose.Schema;
31var ObjectId = Schema.ObjectId;
4
51var TagCollectSchema = new Schema({
6 user_id: { type: ObjectId, index: true },
7 tag_id: { type: ObjectId },
8 create_at: { type: Date, default: Date.now }
9});
10
111mongoose.model('TagCollect', TagCollectSchema);

models/relation.js

100%
5
5
0
LineHitsSource
11var mongoose = require('mongoose');
21var Schema = mongoose.Schema;
31var ObjectId = Schema.ObjectId;
4
51var RelationSchema = new Schema({
6 user_id: { type: ObjectId },
7 follow_id: { type: ObjectId },
8 create_at: { type: Date, default: Date.now }
9});
10
111mongoose.model('Relation', RelationSchema);

models/message.js

100%
5
5
0
LineHitsSource
11var mongoose = require('mongoose');
21var Schema = mongoose.Schema;
31var ObjectId = Schema.ObjectId;
4
5/*
6 * type:
7 * reply: xx 回复了你的话题
8 * reply2: xx 在话题中回复了你
9 * follow: xx 关注了你
10 * at: xx @了你
11 */
12
131var MessageSchema = new Schema({
14 type: { type: String },
15 master_id: { type: ObjectId, index: true },
16 author_id: { type: ObjectId },
17 topic_id: { type: ObjectId },
18 has_read: { type: Boolean, default: false },
19 create_at: { type: Date, default: Date.now }
20});
21
221mongoose.model('Message', MessageSchema);

controllers/message.js

13%
153
20
133
LineHitsSource
11var models = require('../models'),
2 Message = models.Message;
3
41var user_ctrl = require('./user');
51var mail_ctrl = require('./mail');
61var topic_ctrl = require('./topic');
7
81var EventProxy = require('eventproxy');
9
101exports.index = function(req,res,next){
110 if(!req.session.user){
120 res.redirect('home');
130 return;
14 }
15
160 var message_ids = [];
170 var user_id = req.session.user._id;
180 Message.find({master_id:user_id},[],{sort:[['create_at','desc']]},function(err,docs){
190 if(err) return next(err);
200 for(var i=0; i<docs.length; i++){
210 message_ids.push(docs[i]._id);
22 }
230 var messages = [];
240 if(message_ids.length == 0){
250 res.render('message/index',{has_read_messages:[],hasnot_read_messages:[]});
260 return;
27 }
280 var proxy = EventProxy.create();
290 var render = function(){
300 var has_read_messages = [];
310 var hasnot_read_messages = [];
320 for(var i=0; i<messages.length; i++){
330 if(messages[i].is_invalid){
340 messages[i].remove();
35 }else{
360 if(messages[i].has_read){
370 has_read_messages.push(messages[i]);
38 }else{
390 hasnot_read_messages.push(messages[i]);
40 }
41 }
42 }
430 res.render('message/index',{has_read_messages:has_read_messages,hasnot_read_messages:hasnot_read_messages});
440 return;
45 };
460 proxy.after('message_ready',message_ids.length,render);
470 for(var i=0; i<message_ids.length; i++){
480 (function(i){
490 get_message_by_id(message_ids[i],function(err,message){
500 if(err) return next(err);
510 messages[i] = message;
520 proxy.trigger('message_ready');
53 });
54 })(i);
55 }
56 });
57};
58
591exports.mark_read = function(req,res,next){
600 if(!req.session || !req.session.user){
610 res.send('forbidden!');
620 return;
63 }
64
650 var message_id = req.body.message_id;
660 Message.findOne({_id:message_id},function(err,message){
670 if(err) return next(err);
680 if(!message){
690 res.json({status:'failed'});
700 return;
71 }
720 if(message.master_id.toString() != req.session.user._id.toString()){
730 res.json({status:'failed'});
740 return;
75 }
760 message.has_read = true;
770 message.save(function(err){
780 if(err) return next(err);
790 res.json({status:'success'});
80 });
81 });
82};
83
841exports.mark_all_read = function(req,res,next){
850 if(!req.session || !req.session.user){
860 res.send('forbidden!');
870 return;
88 }
89
900 Message.find({master_id:req.session.user._id,has_read:false},function(err,messages){
910 if(messages.length == 0){
920 res.json({'status':'success'});
930 return;
94 }
950 var proxy = EventProxy.create();
960 var done = function(){
970 res.json({'status':'success'});
98 }
990 proxy.after('marked',messages.length,done);
1000 for(var i=0; i<messages.length; i++){
1010 var message = messages[i];
1020 message.has_read = true;
1030 message.save(function(err){
1040 proxy.trigger('marked');
105 });
106 }
107 });
108};
109
1101function send_reply_message(master_id,author_id,topic_id){
1110 var message = new Message();
1120 message.type = 'reply';
1130 message.master_id = master_id;
1140 message.author_id = author_id;
1150 message.topic_id = topic_id;
1160 message.save(function(err){
1170 user_ctrl.get_user_by_id(master_id,function(err,master){
1180 if(master && master.receive_reply_mail){
1190 message.has_read = true;
1200 message.save();
1210 get_message_by_id(message._id,function(err,msg){
1220 mail_ctrl.send_reply_mail(master.email,msg);
123 });
124 }
125 });
126 });
127}
128
1291function send_reply2_message(master_id,author_id,topic_id){
1300 var message = new Message();
1310 message.type = 'reply2';
1320 message.master_id = master_id;
1330 message.author_id = author_id;
1340 message.topic_id = topic_id;
1350 message.save(function(err){
1360 user_ctrl.get_user_by_id(master_id,function(err,master){
1370 if(master && master.receive_reply_mail){
1380 message.has_read = true;
1390 message.save();
1400 get_message_by_id(message._id,function(err,msg){
1410 mail_ctrl.send_reply_mail(master.email,msg);
142 });
143 }
144 });
145 });
146}
147
1481function send_at_message(master_id, author_id, topic_id, callback) {
1490 var message = new Message();
1500 message.type = 'at';
1510 message.master_id = master_id;
1520 message.author_id = author_id;
1530 message.topic_id = topic_id;
1540 message.save(function (err) {
1550 user_ctrl.get_user_by_id(master_id, function (err, master) {
1560 if (master && master.receive_at_mail) {
1570 message.has_read = true;
1580 message.save();
1590 get_message_by_id(message._id, function (err, msg) {
1600 mail_ctrl.send_at_mail(master.email,msg);
161 });
162 }
163 });
1640 callback(err);
165 });
166}
167
1681function send_follow_message(follow_id,author_id){
1690 var message = new Message();
1700 message.type = 'follow';
1710 message.master_id = follow_id;
1720 message.author_id = author_id;
1730 message.save();
174}
175
1761function get_message_by_id(id,cb){
1770 Message.findOne({_id:id},function(err,message){
1780 if(err) return cb(err);
1790 if(message.type == 'reply' || message.type == 'reply2' || message.type == 'at'){
1800 var proxy = EventProxy.create();
1810 var done = function(author,topic){
1820 message.author = author;
1830 message.topic = topic;
1840 if( !author || !topic){
1850 message.is_invalid =true;
186 }
1870 return cb(err,message);
188 }
1890 proxy.assign('author_found','topic_found',done);
1900 user_ctrl.get_user_by_id(message.author_id,function(err,author){
1910 if(err) return cb(err);
1920 proxy.trigger('author_found',author);
193 });
1940 topic_ctrl.get_topic_by_id(message.topic_id,function(err,topic,tags,author){
1950 if(err) return cb(err);
1960 proxy.trigger('topic_found',topic);
197 });
198 }
1990 if(message.type == 'follow'){
2000 user_ctrl.get_user_by_id(message.author_id,function(err,author){
2010 if(err) return cb(err);
2020 message.author = author;
2030 if(!author){
2040 message.is_invalid =true;
205 }
2060 return cb(err,message);
207 });
208 }
209 });
210}
211
2121function get_messages_count(master_id,cb){
2130 Message.count({master_id:master_id,has_read:false},function(err,messages_count){
2140 if(err) return cb(err);
2150 return cb(err,messages_count);
216 });
2171};
2181exports.get_messages_count = get_messages_count;
2191exports.send_reply_message = send_reply_message;
2201exports.send_reply2_message = send_reply2_message;
2211exports.send_follow_message = send_follow_message;
2221exports.send_at_message = send_at_message;

controllers/user.js

10%
368
39
329
LineHitsSource
11var models = require('../models');
21var User = models.User;
31var Reply = models.Reply;
41var Relation = models.Relation;
51var Message = models.Message;
61var TagCollect = models.TagCollect;
71var TopicCollect = models.TopicCollect;
81var tag_ctrl = require('./tag');
91var topic_ctrl = require('./topic');
101var message_ctrl = require('./message');
111var Util = require('../libs/util');
121var config = require('../conf');
131var EventProxy = require('eventproxy');
141var check = require('validator').check;
151var sanitize = require('validator').sanitize;
161var crypto = require('crypto');
17
181exports.index = function (req, res, next) {
190 var user_name = req.params.name;
200 get_user_by_name(user_name, function (err, user) {
210 if (!user) {
220 res.render('notify/notify', {error: '这个用户不存在。'});
230 return;
24 }
25
260 var render = function (recent_topics, recent_replies, relation) {
270 user.friendly_create_at = Util.format_date(user.create_at, true);
280 res.render('user/index', {
29 user: user,
30 recent_topics: recent_topics,
31 recent_replies: recent_replies,
32 relation: relation
33 });
34 };
35
360 var proxy = EventProxy.create();
370 proxy.assign('recent_topics', 'recent_replies', 'relation', render);
38
390 var query = {author_id: user._id};
400 var opt = {limit: 5, sort: [['create_at', 'desc']]};
410 topic_ctrl.get_topics_by_query(query, opt, function (err, recent_topics) {
420 if (err) {
430 return next(err);
44 }
450 proxy.trigger('recent_topics', recent_topics);
46 });
47
480 Reply.find({author_id: user._id}, function (err, replies) {
490 if (err) {
500 return next(err);
51 }
520 var topic_ids = [];
530 for (var i = 0; i < replies.length; i++) {
540 if (topic_ids.indexOf(replies[i].topic_id.toString()) < 0) {
550 topic_ids.push(replies[i].topic_id.toString());
56 }
57 }
580 var query = {_id: {'$in': topic_ids}};
590 var opt = {limit: 5, sort: [['create_at', 'desc']]};
600 topic_ctrl.get_topics_by_query(query, opt, function (err, topics) {
610 if (err) {
620 return next(err);
63 }
640 proxy.trigger('recent_replies', topics);
65 });
66 });
67
680 if (!req.session.user) {
690 proxy.trigger('relation', null);
70 } else {
710 Relation.findOne({user_id: req.session.user._id, follow_id: user._id}, function (err, doc) {
720 if (err) {
730 return next(err);
74 }
750 proxy.trigger('relation', doc);
76 });
77 }
78 });
79};
80
811exports.show_stars = function (req, res, next) {
820 get_users_by_query({is_star: true}, {}, function (err, stars) {
830 if (err) {
840 return next(err);
85 }
860 res.render('user/stars', {stars: stars});
87 });
88};
89
901exports.setting = function (req, res, next) {
910 if (!req.session.user) {
920 res.redirect('home');
930 return;
94 }
950 var method = req.method.toLowerCase();
960 if (method !== 'post') {
970 get_user_by_id(req.session.user._id, function (err, user) {
980 if (err) {
990 return next(err);
100 }
1010 if (req.query.save === 'success') {
1020 user.success = '保存成功。';
103 }
1040 user.error = null;
1050 return res.render('user/setting', user);
106 });
1070 return;
108 }
109 // post
1100 var action = req.body.action;
1110 if (action === 'change_setting') {
1120 var name = sanitize(req.body.name).trim();
1130 name = sanitize(name).xss();
1140 var email = sanitize(req.body.email).trim();
1150 email = sanitize(email).xss();
1160 var url = sanitize(req.body.url).trim();
1170 url = sanitize(url).xss();
1180 var profile_image_url = sanitize(sanitize(req.body.profile_image_url).trim()).xss();
1190 var location = sanitize(req.body.location).trim();
1200 location = sanitize(location).xss();
1210 var signature = sanitize(req.body.signature).trim();
1220 signature = sanitize(signature).xss();
1230 var profile = sanitize(req.body.profile).trim();
1240 profile = sanitize(profile).xss();
1250 var weibo = sanitize(req.body.weibo).trim();
1260 weibo = sanitize(weibo).xss();
1270 var receive_at_mail = req.body.receive_at_mail === 'on' ? true: false;
1280 var receive_reply_mail = req.body.receive_reply_mail === 'on' ? true: false;
129
1300 if (url !== '') {
1310 try {
1320 if (url.indexOf('http://') < 0) {
1330 url = 'http://' + url;
134 }
1350 check(url, '不正确的个人网站。').isUrl();
136 } catch (e) {
1370 res.render('user/setting', {
138 error: e.message,
139 name: name,
140 email: email,
141 url: url,
142 profile_image_url: profile_image_url,
143 location: location,
144 signature: signature,
145 profile: profile,
146 weibo: weibo,
147 receive_at_mail: receive_at_mail,
148 receive_reply_mail: receive_reply_mail
149 });
1500 return;
151 }
152 }
1530 if (weibo) {
1540 try {
1550 if (weibo.indexOf('http://') < 0) {
1560 weibo = 'http://' + weibo;
157 }
1580 check(weibo, '不正确的微博地址。').isUrl();
159 } catch (e) {
1600 res.render('user/setting', {
161 error: e.message,
162 name: name,
163 email: email,
164 url: url,
165 profile_image_url: profile_image_url,
166 location: location,
167 signature: signature,
168 profile: profile,
169 weibo: weibo,
170 receive_at_mail: receive_at_mail,
171 receive_reply_mail: receive_reply_mail
172 });
1730 return;
174 }
175 }
176
1770 get_user_by_id(req.session.user._id, function (err, user) {
1780 if (err) {
1790 return next(err);
180 }
1810 user.url = url;
1820 user.profile_image_url = profile_image_url;
1830 user.location = location;
1840 user.signature = signature;
1850 user.profile = profile;
1860 user.weibo = weibo;
1870 user.receive_at_mail = receive_at_mail;
1880 user.receive_reply_mail = receive_reply_mail;
1890 user.save(function (err) {
1900 if (err) {
1910 return next(err);
192 }
1930 return res.redirect('/setting?save=success');
194 });
195 });
196
197 }
1980 if (action === 'change_password') {
1990 var old_pass = sanitize(req.body.old_pass).trim();
2000 var new_pass = sanitize(req.body.new_pass).trim();
201
2020 get_user_by_id(req.session.user._id, function (err, user) {
2030 if (err) {
2040 return next(err);
205 }
2060 var md5sum = crypto.createHash('md5');
2070 md5sum.update(old_pass);
2080 old_pass = md5sum.digest('hex');
209
2100 if (old_pass !== user.pass) {
2110 res.render('user/setting', {
212 error: '当前密码不正确。',
213 name: user.name,
214 email: user.email,
215 url: user.url,
216 profile_image_url: user.profile_image_url,
217 location: user.location,
218 signature: user.signature,
219 profile: user.profile,
220 weibo: user.weibo,
221 receive_at_mail: user.receive_at_mail,
222 receive_reply_mail: user.receive_reply_mail
223 });
2240 return;
225 }
226
2270 md5sum = crypto.createHash('md5');
2280 md5sum.update(new_pass);
2290 new_pass = md5sum.digest('hex');
230
2310 user.pass = new_pass;
2320 user.save(function (err) {
2330 if (err) {
2340 return next(err);
235 }
2360 res.render('user/setting', {
237 success: '密码已被修改。',
238 name: user.name,
239 email: user.email,
240 url: user.url,
241 profile_image_url: user.profile_image_url,
242 location: user.location,
243 signature: user.signature,
244 profile: user.profile,
245 weibo: user.weibo,
246 receive_at_mail: user.receive_at_mail,
247 receive_reply_mail: user.receive_reply_mail
248 });
2490 return;
250
251 });
252 });
253 }
254};
255
2561exports.follow = function (req, res, next) {
2570 if (!req.session || !req.session.user) {
2580 res.send('forbidden!');
2590 return;
260 }
2610 var follow_id = req.body.follow_id;
2620 get_user_by_id(follow_id, function (err, user) {
2630 if (err) {
2640 return next(err);
265 }
2660 if (!user) {
2670 res.json({status: 'failed'});
268 }
269
2700 var proxy = EventProxy.create();
2710 var done = function () {
2720 res.json({ status: 'success' });
273 };
2740 proxy.assign('relation_saved', 'message_saved', done);
2750 Relation.findOne({user_id: req.session.user._id, follow_id: user._id}, function (err, doc) {
2760 if (err) {
2770 return next(err);
278 }
2790 if (doc) {
2800 res.json({ status: 'success' });
2810 return;
282 }
283
2840 var relation = new Relation();
2850 relation.user_id = req.session.user._id;
2860 relation.follow_id = user._id;
2870 relation.save();
2880 proxy.trigger('relation_saved');
289
2900 get_user_by_id(req.session.user._id, function (err, me) {
2910 if (err) {
2920 return next(err);
293 }
2940 me.following_count += 1;
2950 me.save();
296 });
297
2980 user.follower_count += 1;
2990 user.save();
300
3010 req.session.user.following_count += 1;
302 });
303
3040 message_ctrl.send_follow_message(follow_id, req.session.user._id);
3050 proxy.trigger('message_saved');
306 });
307};
308
3091exports.un_follow = function (req, res, next) {
3100 if (!req.session || !req.session.user) {
3110 res.send('forbidden!');
3120 return;
313 }
3140 var follow_id = req.body.follow_id;
3150 get_user_by_id(follow_id, function (err, user) {
3160 if (err) {
3170 return next(err);
318 }
3190 if (!user) {
3200 res.json({status: 'failed'});
3210 return;
322 }
3230 Relation.remove({user_id: req.session.user._id, follow_id: user._id}, function (err) {
3240 if (err) {
3250 return next(err);
326 }
3270 res.json({status: 'success'});
328 });
329
3300 get_user_by_id(req.session.user._id, function (err, me) {
3310 if (err) {
3320 return next(err);
333 }
3340 me.following_count -= 1;
3350 me.save();
336 });
337
3380 user.follower_count -= 1;
3390 user.save();
340
3410 req.session.user.following_count -= 1;
342 });
343};
344
3451exports.toggle_star = function (req, res, next) {
3460 if (!req.session.user || !req.session.user.is_admin) {
3470 res.send('forbidden!</strong>');
3480 return;
349 }
3500 var user_id = req.body.user_id;
3510 get_user_by_id(user_id, function (err, user) {
3520 if (err) {
3530 return next(err);
354 }
3550 user.is_star = !!user.is_star;
3560 user.save(function (err) {
3570 if (err) {
3580 return next(err);
359 }
3600 res.json({ status: 'success' });
361 });
362 });
363};
364
3651exports.get_collect_tags = function (req, res, next) {
3660 if (!req.session.user) {
3670 res.redirect('home');
3680 return;
369 }
3700 TagCollect.find({ user_id: req.session.user._id }, function (err, docs) {
3710 if (err) {
3720 return next(err);
373 }
3740 var ids = [];
3750 for (var i = 0; i < docs.length; i++) {
3760 ids.push(docs[i].tag_id);
377 }
3780 tag_ctrl.get_tags_by_ids(ids, function (err, tags) {
3790 if (err) {
3800 return next(err);
381 }
3820 res.render('user/collect_tags', { tags: tags });
383 });
384 });
385};
386
3871exports.get_collect_topics = function (req, res, next) {
3880 if (!req.session.user) {
3890 res.redirect('home');
3900 return;
391 }
392
3930 var page = Number(req.query.page) || 1;
3940 var limit = config.list_topic_count;
395
3960 var render = function (topics, pages) {
3970 res.render('user/collect_topics', {
398 topics: topics,
399 current_page: page,
400 pages: pages
401 });
402 };
403
4040 var proxy = EventProxy.create();
4050 proxy.assign('topics', 'pages', render);
406
4070 TopicCollect.find({ user_id: req.session.user._id }, function (err, docs) {
4080 if (err) {
4090 return next(err);
410 }
4110 var ids = [];
4120 for (var i = 0; i < docs.length; i++) {
4130 ids.push(docs[i].topic_id);
414 }
4150 var query = { _id: { '$in': ids } };
4160 var opt = {
417 skip: (page - 1) * limit,
418 limit: limit,
419 sort: [ [ 'create_at', 'desc' ] ]
420 };
4210 topic_ctrl.get_topics_by_query(query, opt, function (err, topics) {
4220 if (err) {
4230 return next(err);
424 }
4250 proxy.trigger('topics', topics);
426 });
4270 topic_ctrl.get_count_by_query(query, function (err, all_topics_count) {
4280 if (err) {
4290 return next(err);
430 }
4310 var pages = Math.ceil(all_topics_count / limit);
4320 proxy.trigger('pages', pages);
433 });
434 });
435};
436
4371exports.get_followings = function (req, res, next) {
4380 if (!req.session.user) {
4390 res.redirect('home');
4400 return;
441 }
4420 Relation.find({user_id: req.session.user._id}, function (err, docs) {
4430 if (err) {
4440 return next(err);
445 }
4460 var ids = [];
4470 for (var i = 0; i < docs.length; i++) {
4480 ids.push(docs[i].follow_id);
449 }
4500 get_users_by_ids(ids, function (err, users) {
4510 if (err) {
4520 return next(err);
453 }
4540 res.render('user/followings', {users: users});
455 });
456 });
457};
458
4591exports.get_followers = function (req, res, next) {
4600 if (!req.session.user) {
4610 res.redirect('home');
4620 return;
463 }
4640 Relation.find({follow_id: req.session.user._id}, function (err, docs) {
4650 if (err) {
4660 return next(err);
467 }
4680 var ids = [];
4690 for (var i = 0; i < docs.length; i++) {
4700 ids.push(docs[i].user_id);
471 }
4720 get_users_by_ids(ids, function (err, users) {
4730 if (err) {
4740 return next(err);
475 }
4760 res.render('user/followers', {users: users});
477 });
478 });
479};
480
4811exports.top100 = function (req, res, next) {
4820 var opt = {limit: 100, sort: [['score', 'desc']]};
4830 get_users_by_query({}, opt, function (err, tops) {
4840 if (err) {
4850 return next(err);
486 }
4870 res.render('user/top100', {users: tops});
488 });
489};
490
4911exports.list_topics = function (req, res, next) {
4920 var user_name = req.params.name;
4930 var page = Number(req.query.page) || 1;
4940 var limit = config.list_topic_count;
495
4960 get_user_by_name(user_name, function (err, user) {
4970 if (!user) {
4980 res.render('notify/notify', {error: '这个用户不存在。'});
4990 return;
500 }
501
5020 var render = function (topics, relation, pages) {
5030 user.friendly_create_at = Util.format_date(user.create_at, true);
5040 res.render('user/topics', {
505 user: user,
506 topics: topics,
507 relation: relation,
508 current_page: page,
509 pages: pages
510 });
511 };
512
5130 var proxy = EventProxy.create();
5140 proxy.assign('topics', 'relation', 'pages', render);
515
5160 var query = {'author_id': user._id};
5170 var opt = {skip: (page - 1) * limit, limit: limit, sort: [['create_at', 'desc']]};
5180 topic_ctrl.get_topics_by_query(query, opt, function (err, topics) {
5190 if (err) {
5200 return next(err);
521 }
5220 proxy.trigger('topics', topics);
523 });
524
5250 if (!req.session.user) {
5260 proxy.trigger('relation', null);
527 } else {
5280 Relation.findOne({user_id: req.session.user._id, follow_id: user._id}, function (err, doc) {
5290 if (err) {
5300 return next(err);
531 }
5320 proxy.trigger('relation', doc);
533 });
534 }
535
5360 topic_ctrl.get_count_by_query(query, function (err, all_topics_count) {
5370 if (err) {
5380 return next(err);
539 }
5400 var pages = Math.ceil(all_topics_count / limit);
5410 proxy.trigger('pages', pages);
542 });
543 });
544};
545
5461exports.list_replies = function (req, res, next) {
5470 var user_name = req.params.name;
5480 var page = Number(req.query.page) || 1;
5490 var limit = config.list_topic_count;
550
5510 get_user_by_name(user_name, function (err, user) {
5520 if (!user) {
5530 res.render('notify/notify', {error: '这个用户不存在。'});
5540 return;
555 }
556
5570 var render = function (topics, relation, pages) {
5580 user.friendly_create_at = Util.format_date(user.create_at, true);
5590 res.render('user/replies', {
560 user: user,
561 topics: topics,
562 relation: relation,
563 current_page: page,
564 pages: pages
565 });
566 };
567
5680 var proxy = EventProxy.create();
5690 proxy.assign('topics', 'relation', 'pages', render);
570
5710 Reply.find({author_id: user._id}, function (err, replies) {
5720 if (err) {
5730 return next(err);
574 }
5750 var topic_ids = [];
5760 for (var i = 0; i < replies.length; i++) {
5770 if (topic_ids.indexOf(replies[i].topic_id.toString()) < 0) {
5780 topic_ids.push(replies[i].topic_id);
579 }
580 }
5810 var query = {'_id': {'$in': topic_ids}};
5820 var opt = {skip: (page - 1) * limit, limit: limit, sort: [['create_at', 'desc']]};
5830 topic_ctrl.get_topics_by_query(query, opt, function (err, topics) {
5840 if (err) {
5850 return next(err);
586 }
5870 proxy.trigger('topics', topics);
588 });
589
5900 topic_ctrl.get_count_by_query(query, function (err, all_topics_count) {
5910 if (err) {
5920 return next(err);
593 }
5940 var pages = Math.ceil(all_topics_count / limit);
5950 proxy.trigger('pages', pages);
596 });
597 });
598
5990 if (!req.session.user) {
6000 proxy.trigger('relation', null);
601 } else {
6020 Relation.findOne({user_id: req.session.user._id, follow_id: user._id}, function (err, doc) {
6030 if (err) {
6040 return next(err);
605 }
6060 proxy.trigger('relation', doc);
607 });
608 }
609 });
610};
611
6121function get_user_by_id(id, cb) {
6130 User.findOne({_id: id}, cb);
614}
6151function get_user_by_name(name, cb) {
6160 User.findOne({name: name}, cb);
617}
6181function get_user_by_loginname(name, cb) {
6190 User.findOne({loginname: name}, cb);
620}
621
6221function get_users_by_ids(ids, cb) {
6230 User.find({'_id': {'$in': ids}}, cb);
624}
6251function get_users_by_query(query, opt, cb) {
6260 User.find(query, [], opt, cb);
627}
6281exports.get_user_by_id = get_user_by_id;
6291exports.get_user_by_name = get_user_by_name;
6301exports.get_user_by_loginname = get_user_by_loginname;
6311exports.get_users_by_ids = get_users_by_ids;
6321exports.get_users_by_query = get_users_by_query;

controllers/tag.js

10%
224
24
200
LineHitsSource
11var models = require('../models'),
2 Tag = models.Tag,
3 TopicTag = models.TopicTag,
4 TagCollect = models.TagCollect;
5
61var check = require('validator').check,
7 sanitize = require('validator').sanitize;
8
91var user_ctrl = require('./user');
101var topic_ctrl = require('./topic');
111var config = require('../conf');
121var EventProxy = require('eventproxy');
13
141exports.list_topic = function(req,res,next){
150 var tag_name = req.params.name;
160 var page = Number(req.query.page) || 1;
170 var limit = config.list_topic_count;
18
190 Tag.findOne({name:tag_name},function(err,tag){
200 if(err) return next(err);
210 if(tag){
220 var done = function(topic_ids,collection,hot_topics,no_reply_topics,pages){
230 var query = {'_id':{'$in':topic_ids}};
240 var opt = {skip:(page-1)*limit, limit:limit, sort:[['create_at','desc']]};
25
260 topic_ctrl.get_topics_by_query(query,opt,function(err,topics){
270 for(var i=0; i<topics.length; i++){
280 for(var j=0; j<topics[i].tags.length; j++){
290 if(topics[i].tags[j].id == tag.id){
300 topics[i].tags[j].highlight = true;
31 }
32 }
33 }
34
350 if (tag.background=='') {
360 var style = null;
37 } else {
380 var style = '#wrapper {background-image: url("'+tag.background+'")}';
39 }
40
410 res.render('tag/list_topic',{
42 tag:tag,topics:topics,
43 current_page:page,
44 list_topic_count:limit,
45 in_collection:collection,
46 hot_topics:hot_topics,
47 no_reply_topics:no_reply_topics,
48 pages:pages,
49 extra_style:style
50 });
51 });
52 }
53
540 var proxy = EventProxy.create();
550 proxy.assign('topic_ids','collection','hot_topics','no_reply_topics','pages',done);
56
570 TopicTag.find({tag_id:tag._id},function(err,docs){
580 if(err) return next(err);
590 var topic_ids = [];
60
610 for(var i=0; i<docs.length; i++){
620 topic_ids.push(docs[i].topic_id);
63 }
640 proxy.trigger('topic_ids',topic_ids);
65
660 topic_ctrl.get_count_by_query({'_id':{'$in':topic_ids}},function(err,all_topics_count){
670 if(err) return next(err);
680 var pages = Math.ceil(all_topics_count/limit);
690 proxy.trigger('pages',pages);
70 });
71
72 });
73
740 if(!req.session.user){
750 proxy.trigger('collection',null);
76 }else{
770 TagCollect.findOne({user_id:req.session.user._id,tag_id:tag._id},function(err,doc){
780 if(err) return next(err);
790 proxy.trigger('collection',doc);
80 });
81 }
82
830 var opt = {limit:5, sort:[['visit_count','desc']]};
840 topic_ctrl.get_topics_by_query({},opt,function(err,hot_topics){
850 if(err) return next(err);
860 proxy.trigger('hot_topics',hot_topics);
87 });
88
890 opt = {limit:5, sort:[['create_at','desc']]};
900 topic_ctrl.get_topics_by_query({reply_count:0},opt,function(err,no_reply_topics){
910 if(err) return next(err);
920 proxy.trigger('no_reply_topics',no_reply_topics);
93 });
94 }else{
950 res.render('notify/notify',{error:'没有这个标签。'});
960 return;
97 }
98 });
99};
100
1011exports.edit_tags = function(req,res,next){
1020 if(!req.session.user){
1030 res.render('notify/notify',{error:'你还没有登录。'});
1040 return;
105 }
1060 if(!req.session.user.is_admin){
1070 res.render('notify/notify',{error:'管理员才能编辑标签。'});
1080 return;
109 }
1100 get_all_tags(function(err,tags){
1110 if(err) return next(err);
1120 res.render('tag/edit_all',{tags:tags});
1130 return;
114 });
115};
116
1171exports.add = function(req,res,next){
1180 if(!req.session || !req.session.user || !req.session.user.is_admin){
1190 res.send('fobidden!');
1200 return;
121 }
122
1230 var name = sanitize(req.body.name).trim();
1240 name = sanitize(name).xss();
1250 var description = sanitize(req.body.description).trim();
1260 description = sanitize(description).xss();
1270 var background = sanitize(req.body.background).trim();
1280 background = sanitize(background).xss();
1290 var order = req.body.order;
130
1310 if(name == ''){
1320 res.render('notify/notify', {error:'信息不完整。'});
1330 return;
134 }
135
1360 Tag.find({'name':name},function(err,tags){
1370 if(err) return next(err);
1380 if(tags.length > 0){
1390 res.render('notify/notify',{error:'这个标签已存在。'});
1400 return;
141 }
142
1430 var tag = new Tag();
1440 tag.name = name;
1450 tag.background = background;
1460 tag.order = order;
1470 tag.description = description;
1480 tag.save(function(err){
1490 if(err) return next(err);
1500 res.redirect('/tags/edit');
151 });
152 });
153};
154
1551exports.edit = function(req,res,next){
1560 if(!req.session.user){
1570 res.render('notify/notify',{error:'你还没有登录。'});
1580 return;
159 }
1600 if(!req.session.user.is_admin){
1610 res.render('notify/notify',{error:'管理员才能编辑标签。'});
1620 return;
163 }
1640 var tag_name = req.params.name;
1650 Tag.findOne({name:tag_name},function(err,tag){
1660 if(err) return next(err);
1670 if(tag){
1680 var method = req.method.toLowerCase();
1690 if(method == 'get'){
1700 get_all_tags(function(err,tags){
1710 if(err) return next(err);
1720 res.render('tag/edit',{tag:tag,tags:tags});
1730 return;
174 });
175 }
1760 if(method == 'post'){
1770 var name = sanitize(req.body.name).trim();
1780 name = sanitize(name).xss();
1790 var order = req.body.order;
1800 var background = sanitize(req.body.background).trim();
1810 background = sanitize(background).xss();
1820 var description = sanitize(req.body.description).trim();
1830 description = sanitize(description).xss();
1840 if(name == ''){
1850 res.render('notify/notify', {error:'信息不完整。'});
1860 return;
187 }
1880 tag.name = name;
1890 tag.order = order;
1900 tag.background = background;
1910 tag.description = description;
1920 tag.save(function(err){
1930 if(err) return next(err);
1940 res.redirect('/tags/edit');
195 })
196 }
197 }else{
1980 res.render('notify/notify',{error:'没有这个标签。'});
1990 return;
200 }
201 });
202}
203
2041exports.delete = function(req,res,next){
2050 if(!req.session.user){
2060 res.render('notify/notify',{error:'你还没有登录。'});
2070 return;
208 }
2090 if(!req.session.user.is_admin){
2100 res.render('notify/notify',{error:'管理员才能编辑标签。'});
2110 return;
212 }
2130 var tag_name = req.params.name;
2140 Tag.findOne({name:tag_name},function(err,tag){
2150 if(err) return next(err);
2160 if(tag){
2170 var proxy = EventProxy.create();
2180 var done = function(){
2190 tag.remove(function(err){
2200 if(err) return next(err);
2210 res.redirect('/');
222 });
223 }
2240 proxy.assign('topic_tag_removed','tag_collect_removed',done);
2250 TopicTag.remove({tag_id:tag._id},function(err){
2260 if(err) return next(err);
2270 proxy.trigger('topic_tag_removed');
228 });
2290 TagCollect.remove({tag_id:tag._id},function(err){
2300 if(err) return next(err);
2310 proxy.trigger('tag_collect_removed')
232 });
233 }else{
2340 res.render('notify/notify',{error:'没有这个标签。'});
2350 return;
236 }
237 });
238}
239
2401exports.collect = function(req,res,next){
2410 if(!req.session || !req.session.user){
2420 res.send('fobidden!');
2430 return;
244 }
2450 var tag_id = req.body.tag_id;
2460 Tag.findOne({_id: tag_id},function(err,tag){
2470 if(err) return next(err);
2480 if(!tag){
2490 res.json({status:'failed'});
250 }
251
2520 TagCollect.findOne({user_id:req.session.user._id,tag_id:tag._id},function(err,doc){
2530 if(err) return next(err);
2540 if(doc){
2550 res.json({status:'success'});
2560 return;
257 }
2580 var tag_collect = new TagCollect();
2590 tag_collect.user_id = req.session.user._id;
2600 tag_collect.tag_id = tag._id;
2610 tag_collect.save(function(err){
2620 if(err) return next(err);
263 //用户更新collect_tag_count
2640 user_ctrl.get_user_by_id(req.session.user._id,function(err,user){
2650 if(err) return next(err);
2660 user.collect_tag_count += 1;
2670 user.save();
2680 req.session.user.collect_tag_count += 1;
269 //标签更新collect_count
2700 tag.collect_count += 1;
2710 tag.save()
2720 res.json({status:'success'});
273 });
274 });
275 });
276 });
277};
278
2791exports.de_collect = function(req,res,next){
2800 if(!req.session || !req.session.user){
2810 res.send('fobidden!');
2820 return;
283 }
2840 var tag_id = req.body.tag_id;
2850 Tag.findOne({_id: tag_id},function(err,tag){
2860 if(err) return next(err);
2870 if(!tag){
2880 res.json({status:'failed'});
289 }
2900 TagCollect.remove({user_id:req.session.user._id,tag_id:tag._id},function(err){
2910 if(err) return next(err);
292 //用户更新collect_tag_count
2930 user_ctrl.get_user_by_id(req.session.user._id,function(err,user){
2940 if(err) return next(err);
2950 user.collect_tag_count -= 1;
2960 user.save()
2970 req.session.user.collect_tag_count -= 1;
2980 tag.collect_count -= 1;
2990 tag.save();
3000 res.json({status:'success'});
301 });
302 });
303 });
304};
305
3061function get_all_tags(cb){
3070 Tag.find({},[],{sort:[['order','asc']]},function(err,tags){
3080 if(err) return cb(err,[])
3090 return cb(err,tags);
310 });
3111};
3121function get_tag_by_name(name,cb){
3130 Tag.findOne({name:name},function(err,tag){
3140 if(err) return cb(err,null);
3150 return cb(err,tag);
316 });
317}
3181function get_tag_by_id(id,cb){
3190 Tag.findOne({_id:id},function(err,tag){
3200 if(err) return cb(err,null);
3210 return cb(err,tag);
322 });
323}
3241function get_tags_by_ids(ids,cb){
3250 Tag.find({_id:{'$in':ids}},function(err,tags){
3260 if(err) return cb(err);
3270 return cb(err,tags);
328 });
329}
3301function get_tags_by_query(query,opt,cb){
3310 Tag.find(query,[],opt,function(err,tags){
3320 if(err) return cb(err);
3330 return cb(err,tags);
334 });
335}
3361exports.get_all_tags = get_all_tags;
3371exports.get_tag_by_name = get_tag_by_name;
3381exports.get_tag_by_id = get_tag_by_id;
3391exports.get_tags_by_ids = get_tags_by_ids;
3401exports.get_tags_by_query = get_tags_by_query;

controllers/topic.js

7%
414
30
384
LineHitsSource
1/*!
2 * nodeclub - controllers/topic.js
3 */
4
5/**
6 * Module dependencies.
7 */
8
91var models = require('../models');
101var Tag = models.Tag;
111var Topic = models.Topic;
121var TopicTag = models.TopicTag;
131var TopicCollect = models.TopicCollect;
141var Relation = models.Relation;
151var check = require('validator').check;
161var sanitize = require('validator').sanitize;
171var at_ctrl = require('./at');
181var tag_ctrl = require('./tag');
191var user_ctrl = require('./user');
201var reply_ctrl = require('./reply');
211var EventProxy = require('eventproxy');
221var Showdown = require('../public/libs/showdown');
231var Util = require('../libs/util');
24
25/**
26 * Topic page
27 *
28 * @param {HttpRequest} req
29 * @param {HttpResponse} res
30 * @param {Function} next
31 */
321exports.index = function (req, res, next) {
330 var topic_id = req.params.tid;
340 if (topic_id.length !== 24) {
350 return res.render('notify/notify', {
36 error: '此话题不存在或已被删除。'
37 });
38 }
390 var events = [ 'topic', 'other_topics', 'no_reply_topics', 'get_relation', '@user'];
400 var ep = EventProxy.create(events, function (topic, other_topics, no_reply_topics, relation) {
410 res.render('topic/index', {
42 topic: topic,
43 author_other_topics: other_topics,
44 no_reply_topics: no_reply_topics,
45 relation : relation
46 });
47 });
48
490 ep.on('error', function (err) {
500 ep.unbind();
510 next(err);
52 });
53
540 ep.once('topic', function(topic) {
550 if (topic.content_is_html) {
560 return ep.emit('@user');
57 }
580 at_ctrl.link_at_who(topic.content, function (err, content) {
590 if (err) {
600 return ep.emit(err);
61 }
620 topic.content = Util.xss(Showdown.parse(content));
630 ep.emit('@user');
64 });
65 });
66
670 get_full_topic(topic_id, function (err, message, topic, tags, author, replies) {
680 if (err) {
690 return ep.emit('error', err);
70 }
710 if (message) {
720 ep.unbind();
730 return res.render('notify/notify', { error: message });
74 }
75
760 topic.visit_count += 1;
770 topic.save(function (err) {
78 // format date
790 topic.friendly_create_at = Util.format_date(topic.create_at, true);
800 topic.friendly_update_at = Util.format_date(topic.update_at, true);
81
820 topic.tags = tags;
830 topic.author = author;
840 topic.replies = replies;
85
860 if (!req.session.user) {
870 ep.emit('topic', topic);
88 } else {
890 var q = { user_id: req.session.user._id, topic_id: topic._id };
900 TopicCollect.findOne(q, function (err, doc) {
910 if (err) {
920 return ep.emit('error', err);
93 }
940 topic.in_collection = doc;
950 ep.emit('topic', topic);
96 });
97 }
98 });
99
100 //get author's relationship
1010 if (!req.session.user || req.session.user._id) {
1020 ep.emit('get_relation', null);
103 } else {
1040 Relation.findOne({user_id: req.session.user._id, follow_id: topic.author_id}, function (err, relation) {
1050 if (err) {
1060 return ep.emit('error', err);
107 }
1080 ep.emit('get_relation', relation);
109 });
110 }
111
112 // get author other topics
1130 var options = { limit: 5, sort: [ [ 'last_reply_at', 'desc' ] ]};
1140 var query = { author_id: topic.author_id, _id: { '$nin': [ topic._id ] } };
1150 get_topics_by_query(query, options, function (err, topics) {
1160 if (err) {
1170 return ep.emit('error', err);
118 }
1190 ep.emit('other_topics', topics);
120 });
121
122 // get no reply topics
1230 var options2 = { limit:5, sort: [ ['create_at', 'desc'] ] };
1240 get_topics_by_query({ reply_count: 0 }, options2, function(err, topics) {
1250 if (err) return ep.emit('error', err);
1260 ep.emit('no_reply_topics', topics);
127 });
128 });
129};
130
1311exports.create = function (req, res, next) {
1320 if (!req.session.user) {
1330 res.render('notify/notify', {error: '未登入用户不能发布话题。'});
1340 return;
135 }
136
1370 var method = req.method.toLowerCase();
1380 if(method == 'get'){
1390 tag_ctrl.get_all_tags(function(err,tags){
1400 if(err) return next(err);
1410 res.render('topic/edit',{tags:tags});
1420 return;
143 });
144 }
145
1460 if(method == 'post'){
1470 var title = sanitize(req.body.title).trim();
1480 title = sanitize(title).xss();
1490 var content = req.body.t_content;
1500 var topic_tags=[];
1510 if(req.body.topic_tags != ''){
1520 topic_tags = req.body.topic_tags.split(',');
153 }
154
1550 if(title == ''){
1560 tag_ctrl.get_all_tags(function(err,tags){
1570 if(err) return next(err);
1580 for(var i=0; i<topic_tags.length; i++){
1590 for(var j=0; j<tags.length; j++){
1600 if(topic_tags[i] == tags[j]._id){
1610 tags[j].is_selected = true;
162 }
163 }
164 }
1650 res.render('topic/edit',{tags:tags, edit_error:'标题不能是空的。', content:content});
1660 return;
167 });
1680 }else if(title.length<10 || title.length>100){
1690 tag_ctrl.get_all_tags(function(err,tags){
1700 if(err) return next(err);
1710 for(var i=0; i<topic_tags.length; i++){
1720 for(var j=0; j<tags.length; j++){
1730 if(topic_tags[i] == tags[j]._id){
1740 tags[j].is_selected = true;
175 }
176 }
177 }
1780 res.render('topic/edit',{tags:tags, edit_error:'标题字数太多或太少', title:title, content:content});
1790 return;
180 });
181 }else{
1820 var topic = new Topic();
1830 topic.title = title;
1840 topic.content = content;
1850 topic.author_id = req.session.user._id;
1860 topic.save(function(err){
1870 if(err) return next(err);
188
1890 var proxy = EventProxy.create();
1900 var render = function(){
1910 res.redirect('/topic/'+topic._id);
192 }
193
1940 proxy.assign('tags_saved','score_saved',render)
195 //话题可以没有标签
1960 if(topic_tags.length == 0){
1970 proxy.emit('tags_saved');
198 }
1990 var tags_saved_done = function(){
2000 proxy.emit('tags_saved');
201 };
2020 proxy.after('tag_saved',topic_tags.length,tags_saved_done);
203 //save topic tags
2040 for(var i=0; i<topic_tags.length; i++){
2050 (function(i){
2060 var topic_tag = new TopicTag();
2070 topic_tag.topic_id = topic._id;
2080 topic_tag.tag_id = topic_tags[i];
2090 topic_tag.save(function(err){
2100 if(err) return next(err);
2110 proxy.emit('tag_saved');
212 });
2130 tag_ctrl.get_tag_by_id(topic_tags[i],function(err,tag){
2140 if(err) return next(err);
2150 tag.topic_count += 1;
2160 tag.save();
217 });
218 })(i);
219 }
2200 user_ctrl.get_user_by_id(req.session.user._id,function(err,user){
2210 if(err) return next(err);
2220 user.score += 5;
2230 user.topic_count += 1;
2240 user.save();
2250 req.session.user.score += 5;
2260 proxy.emit('score_saved');
227 });
228
229 //发送at消息
2300 at_ctrl.send_at_message(content,topic._id,req.session.user._id);
231 });
232 }
233 }
234};
235
2361exports.edit = function(req,res,next){
2370 if(!req.session.user){
2380 res.redirect('home');
2390 return;
240 }
241
2420 var topic_id = req.params.tid;
2430 var method = req.method.toLowerCase();
2440 if(method == 'get'){
2450 if(topic_id.length != 24){
2460 res.render('notify/notify',{error: '此话题不存在或已被删除。'});
2470 return;
248 }
2490 get_topic_by_id(topic_id,function(err,topic,tags,author){
2500 if(!topic){
2510 res.render('notify/notify',{error: '此话题不存在或已被删除。'});
2520 return;
253 }
2540 if(topic.author_id == req.session.user._id || req.session.user.is_admin){
2550 tag_ctrl.get_all_tags(function(err,all_tags){
2560 if(err) return next(err);
2570 for(var i=0; i<tags.length; i++){
2580 for(var j=0; j<all_tags.length; j++){
2590 if(tags[i].id == all_tags[j].id){
2600 all_tags[j].is_selected = true;
261 }
262 }
263 }
264
2650 res.render('topic/edit',{action:'edit',topic_id:topic._id,title:topic.title,content:topic.content,tags:all_tags});
266 });
267 }else{
2680 res.render('notify/notify',{error:'对不起,你不能编辑此话题。'});
2690 return;
270 }
271 });
272 }
2730 if(method == 'post'){
2740 if(topic_id.length != 24){
2750 res.render('notify/notify',{error: '此话题不存在或已被删除。'});
2760 return;
277 }
2780 get_topic_by_id(topic_id,function(err,topic,tags,author){
2790 if(!topic){
2800 res.render('notify/notify',{error: '此话题不存在或已被删除。'});
2810 return;
282 }
2830 if(topic.author_id == req.session.user._id || req.session.user.is_admin){
2840 var title = sanitize(req.body.title).trim();
2850 title = sanitize(title).xss();
2860 var content = req.body.t_content;
2870 var topic_tags=[];
2880 if(req.body.topic_tags != ''){
2890 topic_tags = req.body.topic_tags.split(',');
290 }
291
2920 if(title == ''){
2930 tag_ctrl.get_all_tags(function(err,all_tags){
2940 if(err) return next(err);
2950 for(var i=0; i<topic_tags.length; i++){
2960 for(var j=0; j<all_tags.length; j++){
2970 if(topic_tags[i] == all_tags[j]._id){
2980 all_tags[j].is_selected = true;
299 }
300 }
301 }
3020 res.render('topic/edit',{action:'edit',edit_error:'标题不能是空的。',topic_id:topic._id, content:content,tags:all_tags});
3030 return;
304 });
305 }else{
306 //保存话题
307 //删除topic_tag,标签topic_count减1
308 //保存新topic_tag
3090 topic.title = title;
3100 topic.content = content;
3110 topic.update_at = new Date();
3120 topic.save(function(err){
3130 if(err) return next(err);
314
3150 var proxy = EventProxy.create();
3160 var render = function(){
3170 res.redirect('/topic/'+topic._id);
318 }
3190 proxy.assign('tags_removed_done','tags_saved_done',render);
320
321 // 删除topic_tag
3220 var tags_removed_done = function(){
3230 proxy.emit('tags_removed_done');
324 };
3250 TopicTag.find({topic_id:topic._id},function(err,docs){
3260 if(docs.length == 0){
3270 proxy.emit('tags_removed_done');
328 }else{
3290 proxy.after('tag_removed',docs.length,tags_removed_done);
330 // delete topic tags
3310 for(var i=0; i<docs.length; i++){
3320 (function(i){
3330 docs[i].remove(function(err){
3340 if(err) return next(err);
3350 tag_ctrl.get_tag_by_id(docs[i].tag_id,function(err,tag){
3360 if(err) return next(err);
3370 proxy.emit('tag_removed');
3380 tag.topic_count -= 1;
3390 tag.save();
340 });
341 });
342 })(i);
343 }
344 }
345 });
346
347 // 保存topic_tag
3480 var tags_saved_done = function(){
3490 proxy.emit('tags_saved_done');
350 }
351 //话题可以没有标签
3520 if(topic_tags.length == 0){
3530 proxy.emit('tags_saved_done');
354 }else{
3550 proxy.after('tag_saved',topic_tags.length,tags_saved_done);
356 //save topic tags
3570 for(var i=0; i<topic_tags.length; i++){
3580 (function(i){
3590 var topic_tag = new TopicTag();
3600 topic_tag.topic_id = topic._id;
3610 topic_tag.tag_id = topic_tags[i];
3620 topic_tag.save(function(err){
3630 if(err) return next(err);
3640 proxy.emit('tag_saved');
365 });
3660 tag_ctrl.get_tag_by_id(topic_tags[i],function(err,tag){
3670 if(err) return next(err);
3680 tag.topic_count += 1;
3690 tag.save();
370 });
371 })(i);
372 }
373 }
374
375 //发送at消息
3760 at_ctrl.send_at_message(content,topic._id,req.session.user._id);
377 });
378 }
379 }else{
3800 res.render('notify/notify',{error:'对不起,你不能编辑此话题。'});
3810 return;
382 }
383 });
384 }
385};
386
3871exports.delete = function(req,res,next){
388 //删除话题, 话题作者topic_count减1
389 //删除回复,回复作者reply_count减1
390 //删除topic_tag,标签topic_count减1
391 //删除topic_collect,用户collect_topic_count减1
3920 if(!req.session.user || !req.session.user.is_admin){
3930 res.redirect('home');
3940 return;
395 }
3960 var topic_id = req.params.tid;
3970 if(topic_id.length != 24){
3980 res.render('notify/notify',{error: '此话题不存在或已被删除。'});
3990 return;
400 }
4010 get_topic_by_id(topic_id,function(err,topic,tags,author){
4020 if(!topic){
4030 res.render('notify/notify',{error: '此话题不存在或已被删除。'});
4040 return;
405 }
4060 var proxy = EventProxy.create();
4070 var render = function(){
4080 res.render('notify/notify',{success: '话题已被删除。'});
4090 return;
410 }
4110 proxy.assign('topic_removed',render);
4120 topic.remove(function(err){
4130 proxy.emit('topic_removed');
414 });
415 });
416};
417
4181exports.top = function (req, res, next) {
4190 if (!req.session.user.is_admin) {
4200 res.redirect('home');
4210 return;
422 }
4230 var topic_id = req.params.tid;
4240 var is_top = req.params.is_top;
4250 if (topic_id.length !== 24) {
4260 res.render('notify/notify' , {error: '此话题不存在或已被删除。'} );
4270 return;
428 }
4290 get_topic_by_id(topic_id, function(err, topic, tags, author) {
4300 if (!topic) {
4310 res.render('notify/notify', {error: '此话题不存在或已被删除。'} );
4320 return;
433 }
4340 topic.top = is_top;
4350 var proxy = EventProxy.create();
4360 var render = function() {
4370 var msg = topic.top ? '此话题已经被置顶。' : '此话题已经被取消置顶。';
4380 res.render('notify/notify', {success: msg} );
4390 return;
440 }
4410 proxy.assign('topic_top', render);
4420 topic.save( function(err) {
4430 proxy.emit('topic_top');
444 });
445 });
446};
447
4481exports.collect = function(req,res,next){
4490 if(!req.session || !req.session.user){
4500 res.send('forbidden!');
4510 return;
452 }
4530 var topic_id = req.body.topic_id;
4540 Topic.findOne({_id: topic_id},function(err,topic){
4550 if(err) return next(err);
4560 if(!topic){
4570 res.json({status:'failed'});
458 }
459
4600 TopicCollect.findOne({user_id:req.session.user._id,topic_id:topic._id},function(err,doc){
4610 if(err) return next(err);
4620 if(doc){
4630 res.json({status:'success'});
4640 return;
465 }
466
4670 var topic_collect = new TopicCollect();
4680 topic_collect.user_id = req.session.user._id;
4690 topic_collect.topic_id = topic._id;
4700 topic_collect.save(function(err){
4710 if(err) return next(err);
4720 res.json({status:'success'});
473 });
4740 user_ctrl.get_user_by_id(req.session.user._id,function(err,user){
4750 if(err) return next(err);
4760 user.collect_topic_count += 1;
4770 user.save()
478 });
479
4800 req.session.user.collect_topic_count += 1;
4810 topic.collect_count += 1;
4820 topic.save();
483 });
484 });
485};
486
4871exports.de_collect = function(req,res,next){
4880 if(!req.session || !req.session.user){
4890 res.send('fobidden!');
4900 return;
491 }
4920 var topic_id = req.body.topic_id;
4930 Topic.findOne({_id: topic_id},function(err,topic){
4940 if(err) return next(err);
4950 if(!topic){
4960 res.json({status:'failed'});
497 }
4980 TopicCollect.remove({user_id:req.session.user._id,topic_id:topic._id},function(err){
4990 if(err) return next(err);
5000 res.json({status:'success'});
501 });
502
5030 user_ctrl.get_user_by_id(req.session.user._id,function(err,user){
5040 if(err) return next(err);
5050 user.collect_topic_count -= 1;
5060 user.save();
507 });
508
5090 topic.collect_count -= 1;
5100 topic.save();
511
5120 req.session.user.collect_topic_count -= 1;
513 });
514};
515
516// get topic without replies
5171function get_topic_by_id(id, cb) {
5180 var proxy = EventProxy.create();
5190 var done = function (topic, tags, author, last_reply) {
5200 return cb(null, topic, tags, author, last_reply);
521 };
5220 proxy.assign('topic', 'tags', 'author', 'last_reply', done);
523
5240 Topic.findOne({_id: id}, function (err, topic) {
5250 if (err) {
5260 return cb(err);
527 }
5280 if (!topic) {
5290 proxy.emit('topic', null);
5300 proxy.emit('tags', []);
5310 proxy.emit('author', null);
5320 proxy.emit('last_reply', null);
5330 return;
534 }
5350 proxy.emit('topic', topic);
536
5370 TopicTag.find({topic_id: topic._id}, function (err, topic_tags) {
5380 if(err) return cb(err);
5390 var tags_id = [];
5400 for(var i=0; i<topic_tags.length; i++){
5410 tags_id.push(topic_tags[i].tag_id);
542 }
5430 tag_ctrl.get_tags_by_ids(tags_id,function(err,tags){
5440 if(err) return cb(err);
5450 proxy.emit('tags',tags);
546 });
547 });
548
5490 user_ctrl.get_user_by_id(topic.author_id, function (err, author) {
5500 if (err) {
5510 return cb(err);
552 }
5530 proxy.emit('author', author);
554 });
555
5560 if (topic.last_reply) {
5570 reply_ctrl.get_reply_by_id(topic.last_reply, function (err, last_reply) {
5580 if (err) {
5590 return cb(err);
560 }
5610 if (!last_reply) {
5620 proxy.emit('last_reply', null);
5630 return;
564 }
5650 proxy.emit('last_reply', last_reply);
566 });
567 } else {
5680 proxy.emit('last_reply', null);
569 }
570 });
571}
572// get topic with replies
5731function get_full_topic(id, cb) {
5740 var proxy = EventProxy.create();
5750 var done = function(topic,tags,author,replies){
5760 return cb(null, '', topic,tags,author,replies);
577 };
5780 proxy.assign('topic','tags','author','replies',done);
579
5800 Topic.findOne({_id:id},function(err,topic){
5810 if(err) return cb(err);
5820 if(!topic){
5830 return cb(null, '此话题不存在或已被删除。');
584 }
5850 proxy.emit('topic',topic);
586
5870 TopicTag.find({topic_id: topic._id}, function(err,topic_tags){
5880 if(err) return cb(err);
5890 var tags_id = [];
5900 for(var i=0; i<topic_tags.length; i++){
5910 tags_id.push(topic_tags[i].tag_id);
592 }
5930 tag_ctrl.get_tags_by_ids(tags_id,function(err,tags){
5940 if(err) return cb(err);
5950 proxy.emit('tags',tags);
596 });
597 });
598
5990 user_ctrl.get_user_by_id(topic.author_id,function(err,author){
6000 if(err) return cb(err);
6010 if(!author){
6020 return cb(null, '话题的作者丢了。');
603 }
6040 proxy.emit('author',author);
605 });
606
6070 reply_ctrl.get_replies_by_topic_id(topic._id,function(err,replies){
6080 if(err) return cb(err);
6090 proxy.emit('replies',replies);
610 });
611
612 });
613
614}
6151function get_topics_by_query(query,opt, cb) {
6160 Topic.find(query, ['_id'], opt, function (err, docs) {
6170 if (err) {
6180 return cb(err);
619 }
6200 if (docs.length === 0) {
6210 return cb(null, []);
622 }
623
6240 var topics_id = [];
6250 for (var i = 0; i < docs.length; i++) {
6260 topics_id.push(docs[i]._id);
627 }
628
6290 var proxy = EventProxy.create();
6300 var done = function () {
6310 return cb(null, topics);
632 }
6330 var topics = [];
6340 proxy.after('topic_ready', topics_id.length, done);
6350 for (var i=0; i<topics_id.length; i++) {
6360 (function(i){
6370 get_topic_by_id(topics_id[i], function (err, topic, tags, author, last_reply) {
6380 if (err) {
6390 return cb(err);
640 }
6410 topic.tags = tags;
6420 topic.author = author;
6430 topic.reply = last_reply;
6440 topic.friendly_create_at = Util.format_date(topic.create_at,true);
6450 topics[i] = topic;
6460 proxy.emit('topic_ready');
647 });
648 })(i);
649 }
650 });
651}
6521function get_count_by_query(query,cb){
6530 Topic.count(query,function(err,count){
6540 if(err) return cb(err);
6550 return cb(err,count);
656 });
657}
658
6591exports.get_topic_by_id = get_topic_by_id;
6601exports.get_full_topic = get_full_topic;
6611exports.get_topics_by_query = get_topics_by_query;
6621exports.get_count_by_query = get_count_by_query;

controllers/at.js

22%
40
9
31
LineHitsSource
1/*!
2 * nodeclub - topic mention user controller.
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * Copyright(c) 2012 muyuan
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
121var models = require('../models');
131var User = models.User;
141var Message = require('./message');
151var EventProxy = require('eventproxy');
16
17
181function searchUsers(text, callback) {
190 var results = text.match(/@[a-zA-Z0-9]+/ig);
200 var names = [];
210 if (results) {
220 for (var i = 0, l = results.length; i < l; i++) {
230 var s = results[i];
24 //remove char @
250 s = s.slice(1);
260 names.push(s);
27 }
28 }
290 if (names.length === 0) {
300 return callback(null, names);
31 }
32
330 User.find({ name: { $in: names } }, callback);
34}
35
361function sendMessageToMentionUsers(text, topicId, authorId, callback) {
370 searchUsers(text, function (err, users) {
380 if (err || !users || users.length === 0) {
390 return callback && callback(err);
40 }
410 var ep = EventProxy.create();
420 ep.after('sent', users.length, function () {
430 callback && callback();
44 });
450 ep.once('error', function (err) {
460 ep.unbind();
470 callback && callback(err);
48 });
490 users.forEach(function (user) {
500 Message.send_at_message(user._id, authorId, topicId, function (err) {
510 if (err) {
520 return ep.emit('error', err);
53 }
540 ep.emit('sent');
55 });
56 });
57 });
58}
59
601function linkUsers(text, callback) {
610 searchUsers(text, function (err, users) {
620 if (err) {
630 return callback(err);
64 }
650 for (var i = 0, l = users.length; i < l; i++) {
660 var name = users[i].name;
670 text = text.replace(new RegExp('@' + name, 'gmi'), '@[' + name + '](/user/' + name + ')');
68 }
690 return callback(err, text);
70 });
71}
72
731exports.send_at_message = exports.sendMessageToMentionUsers = sendMessageToMentionUsers;
741exports.link_at_who = exports.linkUsers = linkUsers;

controllers/reply.js

10%
175
19
156
LineHitsSource
11var models = require('../models');
21var Reply = models.Reply;
31var Topic = models.Topic;
41var Message = models.Message;
5
61var check = require('validator').check;
71var sanitize = require('validator').sanitize;
8
91var at_ctrl = require('./at');
101var user_ctrl = require('./user');
111var message_ctrl = require('./message');
12
131var Util = require('../libs/util');
141var Showdown = require('../public/libs/showdown');
151var EventProxy = require('eventproxy');
16
171exports.add = function (req, res, next) {
180 if (!req.session || !req.session.user) {
190 res.send('forbidden!');
200 return;
21 }
22
230 var content = req.body.r_content;
240 var topic_id = req.params.topic_id;
25
260 var str = sanitize(content).trim();
270 if (str === '') {
280 res.render('notify/notify', {error: '回复内容不能为空!'});
290 return;
30 }
31
320 var render = function () {
330 res.redirect('/topic/' + topic_id);
34 };
350 var proxy = EventProxy.create();
360 proxy.assign('reply_saved', 'message_saved', 'score_saved', render);
37
380 var reply = new Reply();
390 reply.content = content;
400 reply.topic_id = topic_id;
410 reply.author_id = req.session.user._id;
420 reply.save(function (err) {
430 if (err) {
440 return next(err);
45 }
460 Topic.findOne({_id: topic_id}, function (err, topic) {
470 if (err) {
480 return next(err);
49 }
500 topic.last_reply = reply._id;
510 topic.last_reply_at = new Date();
520 topic.reply_count += 1;
530 topic.save();
540 proxy.emit('reply_saved');
55 //发送at消息
560 at_ctrl.send_at_message(content, topic_id, req.session.user._id);
57 });
58 });
59
600 Topic.findOne({_id: topic_id}, function (err, topic) {
610 if (err) {
620 return next(err);
63 }
640 if (topic.author_id.toString() === req.session.user._id.toString()) {
650 proxy.emit('message_saved');
66 } else {
670 message_ctrl.send_reply_message(topic.author_id, req.session.user._id, topic._id);
680 proxy.emit('message_saved');
69 }
70 });
71
720 user_ctrl.get_user_by_id(req.session.user._id, function (err, user) {
730 if (err) {
740 return next(err);
75 }
760 user.score += 5;
770 user.reply_count += 1;
780 user.save();
790 req.session.user.score += 5;
800 proxy.emit('score_saved');
81 });
82};
83
841exports.add_reply2 = function (req, res, next) {
850 if (!req.session || !req.session.user) {
860 res.send('forbidden!');
870 return;
88 }
89
900 var topic_id = req.params.topic_id;
910 var reply_id = req.body.reply_id;
920 var content = req.body.r2_content;
93
940 var str = sanitize(content).trim();
950 if (str === '') {
960 res.send('');
970 return;
98 }
99
1000 var done = function () {
1010 get_reply_by_id(reply._id, function (err, reply) {
1020 res.partial('reply/reply2', {object: reply, as: 'reply'});
103 });
104 };
1050 var proxy = EventProxy.create();
1060 proxy.assign('reply_saved', 'message_saved', done);
107
1080 var reply = new Reply();
1090 reply.content = content;
1100 reply.topic_id = topic_id;
111 //标识是二级回复
1120 reply.reply_id = reply_id;
1130 reply.author_id = req.session.user._id;
1140 reply.save(function (err) {
1150 if (err) {
1160 return next(err);
117 }
1180 Topic.findOne({_id: topic_id}, function (err, topic) {
1190 if (err) {
1200 return next(err);
121 }
1220 topic.last_reply = reply._id;
1230 topic.last_reply_at = new Date();
1240 topic.reply_count += 1;
1250 topic.save();
1260 proxy.emit('reply_saved');
127 //发送at消息
1280 at_ctrl.send_at_message(content, topic_id, req.session.user._id);
129 });
130 });
131
1320 Reply.findOne({_id: reply_id}, function (err, reply) {
1330 if (err) {
1340 return next(err);
135 }
1360 if (reply.author_id.toString() === req.session.user._id.toString()) {
1370 proxy.emit('message_saved');
138 } else {
1390 message_ctrl.send_reply2_message(reply.author_id, req.session.user._id, topic_id);
1400 proxy.emit('message_saved');
141 }
142 });
143};
144
1451exports.delete = function (req, res, next) {
1460 var reply_id = req.body.reply_id;
1470 get_reply_by_id(reply_id, function (err, reply) {
1480 if (!reply) {
1490 res.json({status: 'failed'});
1500 return;
151 }
1520 if (reply.author_id.toString() === req.session.user._id.toString()) {
1530 reply.remove();
1540 res.json({status: 'success'});
155
1560 if (!reply.reply_id) {
1570 reply.author.score -= 5;
1580 reply.author.reply_count -= 1;
1590 reply.author.save();
160 }
161 } else {
1620 res.json({status: 'failed'});
1630 return;
164 }
165
1660 Topic.findOne({_id: reply.topic_id}, function (err, topic) {
1670 if (topic) {
1680 topic.reply_count -= 1;
1690 topic.save();
170 }
171 });
172 });
173};
174
1751function get_reply_by_id(id, cb) {
1760 Reply.findOne({_id: id}, function (err, reply) {
1770 if (err) {
1780 return cb(err);
179 }
1800 if (!reply) {
1810 return cb(err, null);
182 }
183
1840 var author_id = reply.author_id;
1850 user_ctrl.get_user_by_id(author_id, function (err, author) {
1860 if (err) {
1870 return cb(err);
188 }
1890 reply.author = author;
1900 reply.friendly_create_at = Util.format_date(reply.create_at, true);
1910 if (reply.content_is_html) {
1920 return cb(null, reply);
193 }
1940 at_ctrl.link_at_who(reply.content, function (err, str) {
1950 if (err) {
1960 return cb(err);
197 }
1980 reply.content = Util.xss(Showdown.parse(str));
1990 return cb(err, reply);
200 });
201 });
202 });
203}
204
2051function get_replies_by_topic_id(id, cb) {
2060 Reply.find({topic_id: id}, [], {sort: [['create_at', 'asc']]}, function (err, replies) {
2070 if (err) {
2080 return cb(err);
209 }
2100 if (replies.length === 0) {
2110 return cb(err, []);
212 }
213
2140 var proxy = EventProxy.create();
2150 var done = function () {
2160 var replies2 = [];
2170 for (var i = replies.length - 1; i >= 0; i--) {
2180 if (replies[i].reply_id) {
2190 replies2.push(replies[i]);
2200 replies.splice(i, 1);
221 }
222 }
2230 for (var j = 0; j < replies.length; j++) {
2240 replies[j].replies = [];
2250 for (var k = 0; k < replies2.length; k++) {
2260 var id1 = replies[j]._id;
2270 var id2 = replies2[k].reply_id;
2280 if (id1.toString() === id2.toString()) {
2290 replies[j].replies.push(replies2[k]);
230 }
231 }
2320 replies[j].replies.reverse();
233 }
2340 return cb(err, replies);
235 };
2360 proxy.after('reply_find', replies.length, done);
2370 for (var j = 0; j < replies.length; j++) {
2380 (function (i) {
2390 var author_id = replies[i].author_id;
2400 user_ctrl.get_user_by_id(author_id, function (err, author) {
2410 if (err) {
2420 return cb(err);
243 }
2440 replies[i].author = author;
2450 replies[i].friendly_create_at = Util.format_date(replies[i].create_at, true);
2460 if (replies[i].content_is_html) {
2470 return proxy.emit('reply_find');
248 }
2490 at_ctrl.link_at_who(replies[i].content, function (err, str) {
2500 if (err) {
2510 return cb(err);
252 }
2530 replies[i].content = Util.xss(Showdown.parse(str));
2540 proxy.emit('reply_find');
255 });
256 });
257 })(j);
258 }
259 });
260}
2611exports.get_reply_by_id = get_reply_by_id;
2621exports.get_replies_by_topic_id = get_replies_by_topic_id;

libs/util.js

9%
43
4
39
LineHitsSource
11var xss = require('xss');
2
31exports.format_date = function (date, friendly) {
40 var year = date.getFullYear();
50 var month = date.getMonth() + 1;
60 var day = date.getDate();
70 var hour = date.getHours();
80 var minute = date.getMinutes();
90 var second = date.getSeconds();
10
110 if (friendly) {
120 var now = new Date();
130 var mseconds = -(date.getTime() - now.getTime());
140 var time_std = [ 1000, 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000 ];
150 if (mseconds < time_std[3]) {
160 if (mseconds > 0 && mseconds < time_std[1]) {
170 return Math.floor(mseconds / time_std[0]).toString() + ' 秒前';
18 }
190 if (mseconds > time_std[1] && mseconds < time_std[2]) {
200 return Math.floor(mseconds / time_std[1]).toString() + ' 分钟前';
21 }
220 if (mseconds > time_std[2]) {
230 return Math.floor(mseconds / time_std[2]).toString() + ' 小时前';
24 }
25 }
26 }
27
28 //month = ((month < 10) ? '0' : '') + month;
29 //day = ((day < 10) ? '0' : '') + day;
300 hour = ((hour < 10) ? '0' : '') + hour;
310 minute = ((minute < 10) ? '0' : '') + minute;
320 second = ((second < 10) ? '0': '') + second;
33
340 thisYear = new Date().getFullYear();
350 year = (thisYear === year) ? '' : (year + '-');
360 return year + month + '-' + day + ' ' + hour + ':' + minute;
37};
38
39/**
40 * Escape the given string of `html`.
41 *
42 * @param {String} html
43 * @return {String}
44 * @api private
45 */
46
471exports.escape = function(html){
480 var codeSpan = /(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm;
490 var codeBlock = /(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g;
500 var spans = [];
510 var blocks = [];
520 var text = String(html).replace(/\r\n/g, '\n')
53 .replace('/\r/g', '\n');
54
550 text = '\n\n' + text + '\n\n';
56
570 text = text.replace(codeSpan, function(code) {
580 spans.push(code);
590 return '`span`';
60 });
61
620 text += '~0';
63
640 return text.replace(codeBlock, function (whole, code, nextChar) {
650 blocks.push(code);
660 return '\n\tblock' + nextChar;
67 })
68 .replace(/&(?!\w+;)/g, '&amp;')
69 .replace(/</g, '&lt;')
70 .replace(/>/g, '&gt;')
71 .replace(/"/g, '&quot;')
72 .replace(/`span`/g, function() {
730 return spans.shift();
74 })
75 .replace(/\n\tblock/g, function() {
760 return blocks.shift();
77 })
78 .replace(/~0$/,'')
79 .replace(/^\n\n/, '')
80 .replace(/\n\n$/, '');
81};
82
83/**
84 * 过滤XSS攻击代码
85 *
86 * @param {string} html
87 * @return {string}
88 */
891exports.xss = function (html) {
900 return xss(html);
91};

controllers/mail.js

25%
80
20
60
LineHitsSource
11var mailer = require('nodemailer');
21var config = require('../conf');
31var eventproxy = require('eventproxy');
41var util = require('util');
51mailer.SMTP = {
6 host: config.mail_host,
7 port: config.mail_port,
8 use_authentication: config.mail_use_authentication,
9 user: config.mail_user,
10 pass: config.mail_pass
11};
12
131var SITE_ROOT_URL = 'http://' + config.hostname + (config.port !== 80 ? ':' + config.port : '');
14
15/**
16 * keep all the mails to send
17 * @type {Array}
18 */
191var mails = [];
201var timer;
21/**
22 * control mailer
23 * @type {EventProxy}
24 */
251var mailEvent = eventproxy.create();
26/**
27 * when need to send an email, start to check the mails array and send all of emails.
28 */
291mailEvent.on("getMail", function () {
300 if (mails.length === 0) {
310 return;
32 } else {
33 //遍历邮件数组,发送每一封邮件,如果有发送失败的,就再压入数组,同时触发mailEvent事件
340 var failed = false;
350 for (var i = 0, len = mails.length; i < len; ++i) {
360 var message = mails[i];
370 mails.splice(i, 1);
380 i--;
390 len--;
400 var mail;
410 try {
420 message.debug = false;
430 mail = mailer.send_mail(message, function (error, success) {
440 if (error) {
450 mails.push(message);
460 failed = true;
47 }
48 });
49 } catch(e) {
500 mails.push(message);
510 failed = true;
52 }
530 if (mail) {
540 var oldemit = mail.emit;
550 mail.emit = function () {
560 oldemit.apply(mail, arguments);
57 };
58 }
59 }
600 if (failed) {
610 clearTimeout(timer);
620 timer = setTimeout(trigger, 60000);
63 }
64 }
65});
66
67/**
68 * trigger email event
69 * @return {[type]}
70 */
711function trigger() {
720 mailEvent.trigger("getMail");
73}
74
75/**
76 * send an email
77 * @param {mail} data [info of an email]
78 */
791function send_mail(data) {
800 if (!data) {
810 return;
82 }
830 if (config.debug) {
840 console.log('******************** 在测试环境下,不会真的发送邮件*******************');
850 for (var k in data) {
860 console.log('%s: %s', k, data[k]);
87 }
880 return;
89 }
900 mails.push(data);
910 trigger();
92}
93
941function send_active_mail(who, token, name, email, cb) {
950 var sender = config.mail_sender;
960 var to = who;
970 var subject = config.name + '社区帐号激活';
980 var html = '<p>您好:<p/>' +
99 '<p>我们收到您在' + config.name + '社区的注册信息,请点击下面的链接来激活帐户:</p>' +
100 '<a href="' + SITE_ROOT_URL + '/active_account?key=' + token + '&name=' + name + '&email=' + email + '">激活链接</a>' +
101 '<p>若您没有在' + config.name + '社区填写过注册信息,说明有人滥用了您的电子邮箱,请删除此邮件,我们对给您造成的打扰感到抱歉。</p>' +
102 '<p>' +config.name +'社区 谨上。</p>';
1030 var data = {
104 sender: sender,
105 to: to,
106 subject: subject,
107 html: html
108 };
1090 cb (null, true);
1100 send_mail(data);
111}
1121function send_reset_pass_mail(who, token, name, cb) {
1130 var sender = config.mail_sender;
1140 var to = who;
1150 var subject = config.name + '社区密码重置';
1160 var html = '<p>您好:<p/>' +
117 '<p>我们收到您在' + config.name + '社区重置密码的请求,请在24小时内单击下面的链接来重置密码:</p>' +
118 '<a href="' + SITE_ROOT_URL + '/reset_pass?key=' + token + '&name=' + name + '">重置密码链接</a>' +
119 '<p>若您没有在' + config.name + '社区填写过注册信息,说明有人滥用了您的电子邮箱,请删除此邮件,我们对给您造成的打扰感到抱歉。</p>' +
120 '<p>' + config.name +'社区 谨上。</p>';
121
1220 var data = {
123 sender: sender,
124 to: to,
125 subject: subject,
126 html: html
127 };
128
1290 cb (null, true);
1300 send_mail(data);
131}
132
1331function send_reply_mail(who, msg) {
1340 var sender = config.mail_sender;
1350 var to = who;
1360 var subject = config.name + ' 新消息';
1370 var html = '<p>您好:<p/>' +
138 '<p>' +
139 '<a href="' + SITE_ROOT_URL + '/user/' + msg.author.name + '">' + msg.author.name + '</a>' +
140 ' 在话题 ' + '<a href="' + SITE_ROOT_URL + '/topic/' + msg.topic._id + '">' + msg.topic.title + '</a>' +
141 ' 中回复了你。</p>' +
142 '<p>若您没有在' + config.name + '社区填写过注册信息,说明有人滥用了您的电子邮箱,请删除此邮件,我们对给您造成的打扰感到抱歉。</p>' +
143 '<p>' + config.name +'社区 谨上。</p>';
144
1450 var data = {
146 sender: sender,
147 to: to,
148 subject: subject,
149 html: html
150 };
151
1520 send_mail(data);
153
154}
155
1561function send_at_mail(who, msg) {
1570 var sender = config.mail_sender;
1580 var to = who;
1590 var subject = config.name + ' 新消息';
1600 var html = '<p>您好:<p/>' +
161 '<p>' +
162 '<a href="' + SITE_ROOT_URL + '/user/' + msg.author.name + '">' + msg.author.name + '</a>' +
163 ' 在话题 ' + '<a href="' + SITE_ROOT_URL + '/topic/' + msg.topic._id + '">' + msg.topic.title + '</a>' +
164 ' 中@了你。</p>' +
165 '<p>若您没有在' + config.name + '社区填写过注册信息,说明有人滥用了您的电子邮箱,请删除此邮件,我们对给您造成的打扰感到抱歉。</p>' +
166 '<p>' +config.name +'社区 谨上。</p>';
167
1680 var data = {
169 sender: sender,
170 to: to,
171 subject: subject,
172 html: html
173 };
174
1750 send_mail(data);
176}
177
1781exports.send_active_mail = send_active_mail;
1791exports.send_reset_pass_mail = send_reset_pass_mail;
1801exports.send_reply_mail = send_reply_mail;
1811exports.send_at_mail = send_at_mail;

controllers/site.js

10%
59
6
53
LineHitsSource
1/*!
2 * nodeclub - site index controller.
3 * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
4 * Copyright(c) 2012 muyuan
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
121var tag_ctrl = require('./tag');
131var user_ctrl = require('./user');
141var topic_ctrl = require('./topic');
151var config = require('../conf');
161var EventProxy = require('eventproxy');
17
18
191exports.index = function (req, res, next) {
200 var page = parseInt(req.query.page, 10) || 1;
210 var keyword = req.query.q || ''; // in-site search
220 if (Array.isArray(keyword)) {
230 keyword = keyword.join(' ');
24 }
250 keyword = keyword.trim();
260 var limit = config.list_topic_count;
27
280 var render = function (tags, topics, hot_topics, stars, tops, no_reply_topics, pages) {
290 var all_tags = tags.slice(0);
30
31 // 计算最热标签
320 tags.sort(function (tag_a, tag_b) {
330 return tag_b.topic_count - tag_a.topic_count;
34 });
350 var hot_tags = tags.slice(0, 5);
36
37 // 计算最新标签
380 tags.sort(function (tag_a, tag_b) {
390 return tag_b.create_at - tag_a.create_at;
40 });
410 var recent_tags = tags.slice(0, 5);
420 res.render('index', {
43 tags: all_tags,
44 topics: topics,
45 current_page: page,
46 list_topic_count: limit,
47 recent_tags: recent_tags,
48 hot_topics: hot_topics,
49 stars: stars,
50 tops: tops,
51 no_reply_topics: no_reply_topics,
52 pages: pages,
53 keyword: keyword
54 });
55 };
56
570 var proxy = EventProxy.create('tags', 'topics', 'hot_topics', 'stars', 'tops', 'no_reply_topics', 'pages', render);
580 proxy.once('error', function (err) {
590 proxy.unbind();
600 next(err);
61 });
620 tag_ctrl.get_all_tags(function (err, tags) {
630 if (err) {
640 return proxy.emit('error', err);
65 }
660 proxy.emit('tags', tags);
67 });
68
690 var options = { skip: (page - 1) * limit, limit: limit, sort: [ ['top', 'desc' ], [ 'last_reply_at', 'desc' ] ] };
700 var query = {};
710 if (keyword) {
720 keyword = keyword.replace(/[\*\^\&\(\)\[\]\+\?\\]/g, '');
730 query.title = new RegExp(keyword, 'i');
74 }
750 topic_ctrl.get_topics_by_query(query, options, function (err, topics) {
760 if (err) {
770 return proxy.emit('error', err);
78 }
790 proxy.emit('topics', topics);
80 });
810 topic_ctrl.get_topics_by_query({}, { limit: 5, sort: [ [ 'visit_count', 'desc' ] ] }, function (err, hot_topics) {
820 if (err) {
830 return proxy.emit('error', err);
84 }
850 proxy.emit('hot_topics', hot_topics);
86 });
870 user_ctrl.get_users_by_query({ is_star: true }, { limit: 5 }, function (err, users) {
880 if (err) {
890 return proxy.emit('error', err);
90 }
910 proxy.emit('stars', users);
92 });
930 user_ctrl.get_users_by_query({}, { limit: 10, sort: [ [ 'score', 'desc' ] ] }, function (err, tops) {
940 if (err) {
950 return proxy.emit('error', err);
96 }
970 proxy.emit('tops', tops);
98 });
990 topic_ctrl.get_topics_by_query({ reply_count: 0 }, { limit: 5, sort: [ [ 'create_at', 'desc' ] ] },
100 function (err, no_reply_topics) {
1010 if (err) {
1020 return proxy.emit('error', err);
103 }
1040 proxy.emit('no_reply_topics', no_reply_topics);
105 });
1060 topic_ctrl.get_count_by_query(query, function (err, all_topics_count) {
1070 if (err) {
1080 return proxy.emit('error', err);
109 }
1100 var pages = Math.ceil(all_topics_count / limit);
1110 proxy.emit('pages', pages);
112 });
113};

controllers/rss.js

27%
18
5
13
LineHitsSource
11var topic_ctrl = require('./topic');
2
31var config = require('../conf');
41var data2xml = require('data2xml');
51var markdown = require('node-markdown').Markdown;
6
71exports.index = function (req, res, next) {
80 if (!config.rss) {
90 res.statusCode = 404;
100 return res.send('Please set `rss` in config.js');
11 }
120 var opt = { limit: config.rss.max_rss_items, sort: [ [ 'create_at','desc' ] ] };
13
140 topic_ctrl.get_topics_by_query({}, opt, function (err, topics) {
150 if (err) {
160 return next(err);
17 }
180 var rss_obj = {
19 _attr: { version: '2.0' },
20 channel: {
21 title: config.rss.title,
22 link: config.rss.link,
23 language: config.rss.language,
24 description: config.rss.description,
25 item: []
26 },
27 };
28
290 topics.forEach(function (topic) {
300 rss_obj.channel.item.push({
31 title: topic.title,
32 link: config.rss.link + '/topic/' + topic._id,
33 guid: config.rss.link + '/topic/' + topic._id,
34 description: markdown(topic.content, true),
35 author: topic.author.name,
36 pubDate: topic.create_at.toUTCString()
37 });
38 });
39
400 var rss_content = data2xml('rss', rss_obj);
41
420 res.contentType('application/xml');
430 res.send(rss_content);
44 });
45};

controllers/upload.js

19%
26
5
21
LineHitsSource
11var fs = require('fs');
21var path = require('path');
31var mkdirp = require('mkdirp');
41var config = require('../conf');
5
61exports.uploadImage = function (req, res, next) {
70 if (!req.session || !req.session.user) {
80 res.send({ status: 'forbidden' });
90 return;
10 }
110 var file = req.files && req.files.userfile;
120 if (!file) {
130 res.send({ status: 'failed', message: 'no file' });
140 return;
15 }
160 var uid = req.session.user._id.toString();
170 var userDir = path.join(config.upload_dir, uid);
180 mkdirp(userDir, function (err) {
190 if (err) {
200 return next(err);
21 }
220 var filename = Date.now() + '_' + file.name;
230 var savepath = path.resolve(path.join(userDir, filename));
240 if (savepath.indexOf(path.resolve(userDir)) !== 0) {
250 return res.send({status: 'forbidden'});
26 }
270 fs.rename(file.path, savepath, function (err) {
280 if (err) {
290 return next(err);
30 }
310 var url = '/upload/' + uid + '/' + encodeURIComponent(filename);
320 res.send({ status: 'success', url: url });
33 });
34 });
35};

controllers/static.js

50%
4
2
2
LineHitsSource
1// static page
21exports.about = function(req,res,next){
30 res.render('static/about');
4};
5
61exports.faq = function(req,res,next){
70 res.render('static/faq');
8};

controllers/tools.js

75%
4
3
1
LineHitsSource
11var models = require('../models'),
2 User = models.User,
3 Topic = models.Topic,
4 Reply = models.Reply,
5 Relation = models.Relation,
6 Message = models.Message;
7
81var EventProxy = require('eventproxy').EventProxy;
9
101exports.run_site_tools = function(req,res,next){
110 res.send('<h3>The White Castle</h3>');
12};
13
14// exports.reset_data = function(req,res,next){
15// Topic.find({},function(err,topics){
16// for(var i=0; i<topics.length; i++){
17// var topic = topics[i];
18// if(topic){
19// topic.reply_count = 0;
20// topic.save();
21// }
22// }
23// });
24// res.end('end');
25// };
26
27// exports.cal_data = function(req,res,next){
28// Reply.find({},function(err,replies){
29// for(var i=0; i<replies.length; i++){
30// var reply = replies[i];
31// if(reply.topic_id){
32// Topic.update({_id:reply.topic_id},{$inc:{reply_count:1}}).exec();
33// }
34// }
35// });
36// res.end('end');
37// };