Commit b5d83c74 by Java-聂换换

first commit

parents
node_modules
\ No newline at end of file
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
clean: {
release: {
src: ['dist/*']
}
},
copy: {
release: {
files: [{
expand: true,
cwd: 'src',
src: '**/*.*',
dest: 'dist'
}]
}
},
watch: {
options: {
spawn: false
},
test: {
files: ['src/**/*.json', 'src/**/*.js', 'spec/**/*.js', 'sync.js']
}
},
nodemon: {
dev: {
script: 'src',
options: {
nodeArgs: ['--harmony']
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-nodemon');
grunt.registerTask('release', ['clean', 'copy:release']);
};
\ No newline at end of file
The MIT License (MIT)
Copyright (c) 2015 融云 RongCloud
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# SealTalk Server
本项目是 SealTalk 系列应用的后端服务,提供了用户、好友、群组、黑名单相关接口和数据管理服务。
SealTalk 是使用[`融云 RongCloud`](http://www.rongcloud.cn)即时通讯云服务及其 SDK 打造的开源即时通讯应用,覆盖所有主流平台,包括:iOS、Android、Windows、Mac、Web。在融云官网注册开发者帐号后,您可以直接使用 SealTalk 打造您自己的 IM App。
## 配置安装与开发调试
请参见 [安装部署与开发调试](docs/install.md) 文档
## API 列表
### 用户相关 API
请参见 [用户相关 API](docs/api_user.md) 文档
### 好友相关 API
请参见 [好友相关 API](docs/api_friendship.md) 文档
### 群组相关 API
请参见 [群组相关 API](docs/api_group.md) 文档
### 其他相关 API
请参见 [其他相关 API](docs/api_misc.md) 文档
## 相关项目说明
融云 RongCloud 官网
[http://www.rongcloud.cn](http://www.rongcloud.cn)
SealTalk 全平台 App 下载
[http://www.rongcloud.cn/downloads](http://www.rongcloud.cn/downloads)
SealTalk iOS 开源项目
[https://github.com/sealtalk/sealtalk-ios](https://github.com/sealtalk/sealtalk-ios)
SealTalk Android 开源项目
[https://github.com/sealtalk/sealtalk-android](https://github.com/sealtalk/sealtalk-android)
SealTalk Desktop 开源项目
[https://github.com/sealtalk/sealtalk-desktop](https://github.com/sealtalk/sealtalk-desktop) 支持 Windows 和 Mac 平台
SealTalk Web 开源项目
[https://github.com/sealtalk/sealtalk-web](https://github.com/sealtalk/sealtalk-web)
融云 RongCloud 服务端 API SDK in Node.js [https://github.com/rongcloud/server-sdk-nodejs](https://github.com/rongcloud/server-sdk-nodejs)
{
"iOS": {
"version": "1.0.5",
"build": "201607181821",
"url": "https://dn-rongcloud.qbox.me/app.plist"
},
"Android": {
"version": "1.0.5",
"url": "http://downloads.rongcloud.cn/SealTalk_by_RongCloud_Android_v1_0_5.apk"
}
}
module.exports = {
AUTH_COOKIE_NAME: 'rong_im_auth',
NICKNAME_COOKIE_NAME: 'rong_im_nickname',
AUTH_COOKIE_KEY: 'ef98re9823434in',
AUTH_COOKIE_DOMAIN: 'devtalk.im',
AUTH_COOKIE_MAX_AGE: '2592000000',
RONGCLOUD_SMS_REGISTER_TEMPLATE_ID: '6iYv6rln4RIbgT3tIPJCS2',
RONGCLOUD_APP_KEY: 'e0x9wycfx7flq',
RONGCLOUD_APP_SECRET: 'SECRETTEST',
QINIU_ACCESS_KEY: 'livk5rb3__JZjCtEiMxXpQ8QscLxbNLehwhHySnX',
QINIU_SECRET_KEY: 'ysrYdcDrrF425QNz0pfY9RoafANC6Hni3TIVgjw5',
QINIU_BUCKET_NAME: 'sealtalk-image',
QINIU_BUCKET_DOMAIN: '7xogjk.com1.z0.glb.clouddn.com',
N3D_KEY: '11EdDIauqcim',
CORS_HOSTS: 'http://web.devtalk.im',
SERVER_PORT: '8585',
DB_NAME: 'sealtalk',
DB_USER: 'sealtalk',
DB_PASSWORD: 'sealtalk',
DB_HOST: '127.0.0.1',
DB_PORT: '3306'
};
[
{
"type": "group",
"id": 10
},
{
"type": "group",
"id": 15
},
{
"type": "group",
"id": 10000
},
{
"type": "chatroom",
"id": 1,
"name": "聊天室 I"
},
{
"type": "chatroom",
"id": 2,
"name": "聊天室 II"
},
{
"type": "chatroom",
"id": 3,
"name": "聊天室 III"
},
{
"type": "chatroom",
"id": 4,
"name": "聊天室 IV"
}
]
var Config, HTTPError, Session, Utility, app, authentication, bodyParser, cacheControl, compression, cookieParser, cors, departRouter, env, errorHandler, express, friendshipRouter, groupRouter, miscRouter, parameterPreprocessor, server, userRouter;
express = require('express');
cookieParser = require('cookie-parser');
bodyParser = require('body-parser');
compression = require('compression');
cors = require('cors');
Config = require('./conf');
Session = require('./util/session');
Utility = require('./util/util').Utility;
HTTPError = require('./util/util').HTTPError;
userRouter = require('./routes/user');
friendshipRouter = require('./routes/friendship');
groupRouter = require('./routes/group');
departRouter = require('./routes/departs');
miscRouter = require('./routes/misc');
if ((env = process.env.NODE_ENV) !== 'development' && env !== 'production') {
console.log("Error: NODE_ENV must be set to 'development' or 'production'.");
return process.exit();
}
app = express();
app.use(cors({
origin: Config.CORS_HOSTS,
credentials: true
}));
app.use(compression());
app.use(cookieParser());
app.use(bodyParser.json());
authentication = function(req, res, next) {
var body, currentUserId, i, len, ref, reqPath, userAgent;
userAgent = req.get('user-agent').substr(0, 50);
ref = ['/misc/demo_square', '/misc/latest_update', '/misc/client_version', '/user/login', '/user/register', '/user/reset_password', '/user/send_code', '/user/verify_code', '/user/get_sms_img_code', '/user/check_username_available', '/user/check_phone_available'];
for (i = 0, len = ref.length; i < len; i++) {
reqPath = ref[i];
if (req.path === reqPath) {
if (req.body.password) {
body = JSON.stringify(req.body).replace(/"password":".*?"/, '"password":"**********"');
} else {
body = JSON.stringify(req.body);
}
Utility.logPath('%s %s %s %s', userAgent, req.method, req.originalUrl, body);
return next();
}
}
currentUserId = Session.getCurrentUserId(req);
if (!currentUserId) {
return res.status(403).send('Not loged in.');
}
Utility.logPath('%s User(%s/%s) %s %s %s', userAgent, Utility.encodeId(currentUserId), currentUserId, req.method, req.originalUrl, JSON.stringify(req.body));
return next();
};
cacheControl = function(req, res, next) {
res.set('Cache-Control', 'private');
return next();
};
parameterPreprocessor = function(req, res, next) {
var prop;
for (prop in req.body) {
if (prop.endsWith('Id') || prop.endsWith('Ids')) {
req.body['encoded' + prop[0].toUpperCase() + prop.substr(1)] = req.body[prop];
req.body[prop] = Utility.decodeIds(req.body[prop]);
}
if (Utility.isEmpty(req.body[prop]) && prop !== 'displayName' && prop !== 'pushContent' && prop !== 'bulletin' && prop !== 'name') {
return res.status(400).send("Empty " + prop + ".");
}
}
return next();
};
errorHandler = function(err, req, res, next) {
if (err instanceof HTTPError) {
return res.status(err.statusCode).send(err.message);
}
Utility.logError(err);
return res.status(500).send(err.message || 'Unknown error.');
};
app.all('*', authentication);
app.use(parameterPreprocessor);
app.use(cacheControl);
app.use('/user', userRouter);
app.use('/friendship', friendshipRouter);
app.use('/group', groupRouter);
app.use('/departs', departRouter);
app.use('/misc', miscRouter);
app.use(errorHandler);
server = app.listen(Config.SERVER_PORT, function() {
return console.log('SealTalk Server already started, listening at http://%s:%s in %s mode.', server.address().address, server.address().port, app.get('env'));
});
module.exports = app;
var APIResult, Blacklist, Cache, Config, DataVersion, Department, DepartmentMember, Duty, Friendship, Group, GroupMember, GroupSync, HTTPError, LoginLog, Session, User, Utility, VerificationCode, _, co, express, ref, rongCloud, router, sequelize;
express = require('express');
co = require('co');
_ = require('underscore');
rongCloud = require('rongcloud-sdk');
Config = require('../conf');
Cache = require('../util/cache');
Session = require('../util/session');
Utility = require('../util/util').Utility;
APIResult = require('../util/util').APIResult;
HTTPError = require('../util/util').HTTPError;
ref = require('../db'), sequelize = ref[0], User = ref[1], Blacklist = ref[2], Friendship = ref[3], Duty = ref[4], Department = ref[5], DepartmentMember = ref[6], Group = ref[7], GroupMember = ref[8], GroupSync = ref[9], DataVersion = ref[10], VerificationCode = ref[11], LoginLog = ref[12];
router = express.Router();
router.get('/:id', function(req, res, next) {
var departId;
departId = req.params.id;
departId = Utility.decodeIds(departId);
return Cache.get("depart_" + departId).then(function(depart) {
if (depart) {
return res.send(new APIResult(200, depart));
} else {
return Department.findById(departId, {
attributes: ['id', 'deptName', 'sort', 'parentId', 'timestamp']
}).then(function(depart) {
var results;
if (!depart) {
return res.status(404).send('Unknow department.');
}
results = Utility.encodeResults(depart, ['id', 'parentId']);
Cache.set("depart_" + departId, results);
return res.send(new APIResult(200, results));
});
}
})["catch"](next);
});
router.get('/:id/members', function(req, res, next) {
var currentUserId, departId;
departId = req.params.id;
departId = Utility.decodeIds(departId);
currentUserId = Session.getCurrentUserId(req);
return DepartmentMember.findAll({
where: {
userId: currentUserId
},
attributes: ['deptId', 'userId', 'displayName', 'managerId', 'timestamp']
}).then(function(departMembers) {
if (departMembers.length === 0) {
return res.status(403).send('Have no members.');
}
return Cache.get("depart_members_" + departId).then(function(departMembers) {
if (departMembers) {
return res.send(new APIResult(200, departMembers));
} else {
return DepartmentMember.findAll({
where: {
deptId: departId
},
attributes: ['deptId', 'userId', 'displayName', 'managerId', 'timestamp'],
include: [
{
model: User,
attributes: ['id', 'nickname', 'portraitUri', 'region', 'phone', 'email']
}, {
model: Duty,
attributes: ['dutyName']
}
]
}).then(function(departMembers) {
var results;
if (departMembers.length === 0) {
return res.status(404).send('Have no members.');
}
results = Utility.encodeResults(departMembers, ['deptId', 'userId', 'managerId']);
results = Utility.encodeResults(results, [['user', 'id']]);
Cache.set("depart_members_" + departId, results);
return res.send(new APIResult(200, results));
});
}
})["catch"](next);
});
});
router.get('/', function(req, res, next) {
var currentUserId, parentid;
currentUserId = Session.getCurrentUserId(req);
parentid = req.query.parentid;
if (parentid) {
parentid = Utility.decodeIds(parentid);
} else {
parentid = 0;
}
return DepartmentMember.findAll({
where: {
userId: currentUserId
},
attributes: ['deptId', 'userId', 'displayName', 'managerId', 'timestamp']
}).then(function(departMembers) {
if (departMembers.length === 0) {
return res.status(403).send('Have no members.');
}
return Cache.get("depart_list_" + parentid).then(function(departs) {
if (departs) {
return res.send(new APIResult(200, departs));
} else {
return Department.findAll({
where: {
parentid: parentid
},
attributes: ['id', 'deptName', 'sort', 'timestamp', 'parentId']
}).then(function(departs) {
var results;
if (departs.length === 0) {
return res.status(404).send('Have no departments.');
}
results = Utility.encodeResults(departs, ['id', 'parentId']);
Cache.set("depart_list_" + parentid, results);
return res.send(new APIResult(200, results));
});
}
})["catch"](next);
});
});
module.exports = router;
var APIResult, Blacklist, Cache, Config, Department, DepartmentMember, Duty, FRIENDSHIP_AGREED, Friendship, Group, GroupMember, Session, User, Utility, _, express, jsonfile, path, ref, rongCloud, router, semver, sequelize;
express = require('express');
_ = require('underscore');
jsonfile = require('jsonfile');
path = require('path');
semver = require('semver');
rongCloud = require('rongcloud-sdk');
Config = require('../conf');
Cache = require('../util/cache');
Session = require('../util/session');
Utility = require('../util/util').Utility;
APIResult = require('../util/util').APIResult;
ref = require('../db'), sequelize = ref[0], User = ref[1], Blacklist = ref[2], Friendship = ref[3], Duty = ref[4], Department = ref[5], DepartmentMember = ref[6], Group = ref[7], GroupMember = ref[8];
FRIENDSHIP_AGREED = 20;
rongCloud.init(Config.RONGCLOUD_APP_KEY, Config.RONGCLOUD_APP_SECRET);
router = express.Router();
router.get('/latest_update', function(req, res, next) {
var clientVersion, err;
clientVersion = req.query.version;
try {
return Cache.get('latest_update').then(function(squirrelConfig) {
if (!squirrelConfig) {
squirrelConfig = jsonfile.readFileSync(path.join(__dirname, '../squirrel.json'));
Cache.set('latest_update', squirrelConfig);
}
if ((semver.valid(clientVersion) === null) || (semver.valid(squirrelConfig.version) === null)) {
return res.status(400).send('Invalid version.');
}
if (semver.gte(clientVersion, squirrelConfig.version)) {
return res.status(204).end();
} else {
return res.send(squirrelConfig);
}
});
} catch (_error) {
err = _error;
return next(err);
}
});
router.get('/client_version', function(req, res, next) {
var err;
try {
return Cache.get('client_version').then(function(clientVersionInfo) {
if (!clientVersionInfo) {
clientVersionInfo = jsonfile.readFileSync(path.join(__dirname, '../client_version.json'));
Cache.set('client_version', clientVersionInfo);
}
return res.send(clientVersionInfo);
});
} catch (_error) {
err = _error;
return next(err);
}
});
router.get('/demo_square', function(req, res, next) {
var demoSquareData, err, groupIds;
try {
demoSquareData = jsonfile.readFileSync(path.join(__dirname, '../demo_square.json'));
groupIds = _.chain(demoSquareData).where({
type: 'group'
}).pluck('id').value();
return Group.findAll({
where: {
id: {
$in: groupIds
}
},
attributes: ['id', 'name', 'portraitUri', 'memberCount']
}).then(function(groups) {
demoSquareData.forEach(function(item) {
var group;
if (item.type === 'group') {
group = _.findWhere(groups, {
id: item.id
});
if (!group) {
group = {
name: 'Unknown',
portraitUri: '',
memberCount: 0
};
}
item.name = group.name;
item.portraitUri = group.portraitUri;
item.memberCount = group.memberCount;
return item.maxMemberCount = group.maxMemberCount;
}
});
return res.send(new APIResult(200, Utility.encodeResults(demoSquareData)));
});
} catch (_error) {
err = _error;
return next(err);
}
});
router.post('/send_message', function(req, res, next) {
var content, conversationType, currentUserId, encodedCurrentUserId, encodedTargetId, objectName, pushContent, targetId;
conversationType = req.body.conversationType;
targetId = req.body.targetId;
objectName = req.body.objectName;
content = req.body.content;
pushContent = req.body.pushContent;
encodedTargetId = req.body.encodedTargetId;
currentUserId = Session.getCurrentUserId(req);
encodedCurrentUserId = Utility.encodeId(currentUserId);
switch (conversationType) {
case 'PRIVATE':
return Friendship.count({
where: {
userId: currentUserId,
friendId: targetId,
status: FRIENDSHIP_AGREED
}
}).then(function(count) {
if (count > 0) {
return rongCloud.message["private"].publish(encodedCurrentUserId, encodedTargetId, objectName, content, pushContent, function(err, resultText) {
if (err) {
Utility.logError('Error: send message failed: %j', err);
throw err;
}
return res.send(new APIResult(200));
});
} else {
return res.status(403).send("User " + encodedTargetId + " is not your friend.");
}
});
case 'GROUP':
return GroupMember.count({
where: {
groupId: targetId,
memberId: currentUserId
}
}).then(function(count) {
if (count > 0) {
return rongCloud.message.group.publish(encodedCurrentUserId, encodedTargetId, objectName, content, pushContent, function(err, resultText) {
if (err) {
Utility.logError('Error: send message failed: %j', err);
throw err;
}
return res.send(new APIResult(200));
});
} else {
return res.status(403).send("Your are not member of Group " + encodedTargetId + ".");
}
});
default:
return res.status(403).send('Unsupported conversation type.');
}
});
module.exports = router;
{
"version": "1.0.2",
"url": "http://downloads.rongcloud.cn/sealtalk/releases/mac/SealTalk_by_RongCloud_1.0.2.dmg",
"name": "SealTalk 1.0.2",
"notes": "修正",
"pub_date": "2016-05-05T16:00:00+08:00"
}
var Cache, Config, LRU, Utility;
LRU = require("lru-cache");
Config = require('../conf');
Utility = require('./util').Utility;
Cache = (function() {
function Cache() {}
Cache.cache = LRU({
max: 100000,
maxAge: 3600000
});
Cache.set = function(key, value) {
Utility.log("Cache: set '%s'.", key);
return Promise.resolve(this.cache.set(key, value));
};
Cache.get = function(key) {
Utility.log("Cache: get '%s'.", key);
return Promise.resolve(this.cache.get(key));
};
Cache.del = function(key) {
Utility.log("Cache: del '%s'.", key);
return Promise.resolve(this.cache.del(key));
};
return Cache;
})();
module.exports = Cache;
var N3D;
N3D = (function() {
var isUnsigned;
function N3D(key, lower, upper) {
var a, charMap, i, j, k, l, n, ref, s;
this.lower = lower;
this.upper = upper;
charMap = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
this.radix = charMap.length;
this.dict = [];
this.keyCode = 0;
if (!isUnsigned(this.lower) || !isUnsigned(this.upper)) {
throw new Error('Parameter is error.');
}
if (this.upper <= this.lower) {
throw new Error('The upper must be greater than the lower.');
}
if (typeof key !== 'string' || key.length === 0) {
throw new Error('The key is error.');
}
for (i = n = 0, ref = key.length; 0 <= ref ? n < ref : n > ref; i = 0 <= ref ? ++n : --n) {
a = key.charCodeAt(i);
if (a > 127) {
throw new Error('The key is error.');
}
this.keyCode += a * Math.pow(128, i % 7);
}
if (this.keyCode + this.radix < this.upper) {
throw new Error('The secret key is too short.');
}
i = this.keyCode - this.radix;
j = 0;
while (i < this.keyCode) {
this.dict[j] = [];
k = this.radix;
l = 0;
while (k > 0) {
s = i % k;
this.dict[j][l] = charMap[s];
charMap[s] = charMap[k - 1];
k--;
l++;
}
charMap = this.dict[j].slice(0);
i++;
j++;
}
}
isUnsigned = function(num) {
return Math.floor(num) === num && num > 0 && num < Number.MAX_VALUE;
};
N3D.prototype.encrypt = function(num) {
var m, map, result, s;
if (!isUnsigned(num) || num > this.upper || num < this.lower) {
throw new Error('Parameter is error.');
}
num = this.keyCode - num;
result = [];
m = num % this.radix;
map = this.dict[m];
s = 0;
result.push(this.dict[0][m]);
while (num > this.radix) {
num = (num - m) / this.radix;
m = num % this.radix;
if ((s = m + s) >= this.radix) {
s -= this.radix;
}
result.push(map[s]);
}
return result.join('');
};
N3D.prototype.decrypt = function(str) {
var chars, i, j, len, map, n, ref, result, s, t;
if (typeof str !== 'string' && str.length === 0) {
throw new Error('Parameter is error.');
}
chars = str.split('');
len = chars.length;
t = 0;
s = 0;
result = this.dict[0].join('').indexOf(chars[0]);
if (result < 0) {
throw new Error('Invalid string.');
}
map = this.dict[result].join('');
for (i = n = 1, ref = len; 1 <= ref ? n < ref : n > ref; i = 1 <= ref ? ++n : --n) {
j = map.indexOf(chars[i]);
if (j < 0) {
throw new Error('Invalid string.');
}
if ((s = j - t) < 0) {
s += this.radix;
}
result += s * Math.pow(this.radix, i);
t = j;
}
result = this.keyCode - result;
if (result > this.upper || result < this.lower) {
throw new Error('Invalid string.');
}
return result;
};
return N3D;
})();
module.exports = N3D;
var Cache, Config, LRU, Session, Utility, co, process;
process = require('process');
LRU = require("lru-cache");
co = require('co');
Config = require('../conf');
Cache = require('./cache');
Utility = require('./util').Utility;
Session = (function() {
function Session() {}
Session.getCurrentUserId = function(req) {
var cookie;
cookie = req.cookies[Config.AUTH_COOKIE_NAME];
if (!cookie) {
return null;
}
return parseInt(Utility.decryptText(cookie, Config.AUTH_COOKIE_KEY));
};
Session.getCurrentUserNickname = function(userId, UserModel) {
return new Promise(function(resolve, reject) {
return Cache.get('nickname_' + userId).then(function(cachedNickname) {
if (cachedNickname) {
return resolve(cachedNickname);
}
return UserModel.getNickname(userId).then(function(nickname) {
if (nickname) {
Cache.set('nickname_' + userId, nickname);
}
return resolve(nickname);
})["catch"](function(err) {
return reject(err);
});
});
});
};
Session.setNicknameToCache = function(userId, nickname) {
if (!Number.isInteger(userId) || Utility.isEmpty(nickname)) {
throw new Error('Invalid userId or nickname.');
}
return Cache.set('nickname_' + userId, nickname);
};
Session.setAuthCookie = function(res, userId) {
var value;
value = Utility.encryptText(userId, Config.AUTH_COOKIE_KEY);
return res.cookie(Config.AUTH_COOKIE_NAME, value, {
httpOnly: true,
domain: Config.AUTH_COOKIE_DOMAIN,
maxAge: Config.AUTH_COOKIE_MAX_AGE,
expires: new Date(Date.now() + Config.AUTH_COOKIE_MAX_AGE)
});
};
return Session;
})();
module.exports = Session;
var APIResult, Config, HTTPError, N3D, Utility, crypto, debug, process, xss,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
crypto = require('crypto');
process = require('process');
debug = require('debug');
xss = require('xss');
Config = require('../conf');
N3D = require('./n3d');
Utility = (function() {
function Utility() {}
Utility.n3d = new N3D(Config.N3D_KEY, 1, 4294967295);
Utility.log = debug('app:log');
Utility.logPath = debug('app:path');
Utility.logError = debug('app:error');
Utility.logResult = debug('app:result');
Utility.encryptText = function(text, password) {
var cipher, crypted, salt;
salt = this.random(1000, 9999);
text = salt + '|' + text + '|' + Date.now();
cipher = crypto.createCipher('aes-256-ctr', password);
crypted = cipher.update(text, 'utf8', 'hex');
return crypted += cipher.final('hex');
};
Utility.decryptText = function(text, password) {
var dec, decipher, strs;
decipher = crypto.createDecipher('aes-256-ctr', password);
dec = decipher.update(text, 'hex', 'utf8');
dec += decipher.final('utf8');
strs = dec.split('|');
if (strs.length !== 3) {
throw new Error('Invalid cookie value!');
}
return strs[1];
};
Utility.hash = function(text, salt) {
var sha1;
text = text + '|' + salt;
sha1 = crypto.createHash('sha1');
sha1.update(text, 'utf8');
return sha1.digest('hex');
};
Utility.random = function(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
};
Utility.isEmpty = function(obj) {
return obj === '' || obj === null || obj === void 0 || (Array.isArray(obj) && obj.length === 0);
};
Utility.decodeIds = function(obj) {
if (obj === null) {
return null;
}
if (Array.isArray(obj)) {
return obj.map(function(element) {
if (typeof element !== 'string') {
return null;
}
return Utility.stringToNumber(element);
});
} else if (typeof obj === 'string') {
return Utility.stringToNumber(obj);
} else {
return null;
}
};
Utility.encodeId = function(str) {
return Utility.numberToString(str);
};
Utility.encodeResults = function(results, keys) {
var isSubArrayKey, replaceKeys, retVal;
replaceKeys = function(obj) {
if (obj === null) {
return null;
}
if (isSubArrayKey) {
keys.forEach(function(key) {
var subObj;
subObj = obj[key[0]];
if (subObj) {
if (subObj[key[1]]) {
return subObj[key[1]] = Utility.numberToString(subObj[key[1]]);
}
}
});
} else {
keys.forEach(function(key) {
if (obj[key]) {
return obj[key] = Utility.numberToString(obj[key]);
}
});
}
return obj;
};
if (results === null) {
return null;
}
if (results.toJSON) {
results = results.toJSON();
}
if (!keys) {
keys = 'id';
}
if (typeof keys === 'string') {
keys = [keys];
}
isSubArrayKey = keys.length > 0 && Array.isArray(keys[0]);
if (Array.isArray(results)) {
retVal = results.map(function(item) {
if (item.toJSON) {
item = item.toJSON();
}
return replaceKeys(item);
});
} else {
retVal = replaceKeys(results);
}
return retVal;
};
Utility.stringToNumber = function(str) {
try {
return this.n3d.decrypt(str);
} catch (_error) {
return null;
}
};
Utility.numberToString = function(num) {
try {
return this.n3d.encrypt(num);
} catch (_error) {
return null;
}
};
Utility.xss = function(str, maxLength) {
var result;
result = xss(str, {
whiteList: []
});
if (str.length <= maxLength) {
if (result.length > maxLength) {
return result.substr(0, maxLength);
}
}
return result;
};
return Utility;
})();
APIResult = (function() {
function APIResult(code, result1, message) {
this.code = code;
this.result = result1;
this.message = message;
if (this.code === null || this.code === void 0) {
throw new Error('Code is null.');
}
Utility.logResult(JSON.stringify(this));
if (this.result === null) {
delete this.result;
}
if (this.message === null || process.env.NODE_ENV !== 'development') {
delete this.message;
}
}
return APIResult;
})();
HTTPError = (function(superClass) {
extend(HTTPError, superClass);
function HTTPError(message, statusCode) {
this.message = message;
this.statusCode = statusCode;
}
return HTTPError;
})(Error);
module.exports.Utility = Utility;
module.exports.APIResult = APIResult;
module.exports.HTTPError = HTTPError;
### 好友相关接口
| 接口地址 | 说明 |
|---------|-----|
| [/friendship/invite](#post-friendshipinvite) | 发起添加好友 |
| [/friendship/agree](#post-friendshipagree) | 同意加好友请求 |
| [/friendship/ignore](#post-friendshipignore) | 忽略好友请求 |
| [/friendship/delete](#post-friendshipdelete) | 删除好友请求 |
| [/friendship/set_display_name](#post-friendshipset_display_name) | 设置好友备注名 |
| [/friendship/all](#get-friendshipall) | 获取好友列表 |
| [/friendship/:friendId/profile](#get-friendshipfriendidprofile) | 获取好友信息 |
## API 说明
### POST /friendship/invite
添加好友
#### 请求参数
```
{
"friendId": "RfqHbcjes",
"message": "你好,我是 Martin"
}
```
* friendId: 好友 Id
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200,
"result": {
"action": "Added"
}
}
```
* action: 添加好友请求状态 `Added: 已添加` `None: 在对方黑名单中` `Sent: 请求已发送`
返回码说明:
* 200: 请求成功
* 400: 已经是好友
### POST /friendship/agree
同意好友请求
#### 请求参数
```
{
"friendId": "RfqHbcjes"
}
```
* friendId: 好友 Id
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200,
"result": {
"action": "Added"
}
}
```
* action: 添加好友请求状态 `Added: 已添加` `None: 在对方黑名单中` `Sent: 请求已发送`
返回码说明:
* 200: 请求成功
* 404: 无效的好友请求或未知好友
### POST /friendship/ignore
忽略好友请求
#### 请求参数
```
{
"friendId": "RfqHbcjes"
}
```
* friendId: 好友 Id
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 404: 无效的好友请求或未知好友
### POST /friendship/delete
删除好友
#### 请求参数
```
{
"friendId": "RfqHbcjes"
}
```
* friendId: 好友 Id
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 404: 无效的好友请求或未知好友
### POST /friendship/set_display_name
设置好友备注名称
#### 请求参数
```
{
"friendId": "RfqHbcjes",
"displayName": "备注"
}
```
* friendId: 好友 Id
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 404: 无效的好友请求或未知好友
* 400: 备注名称超限
### GET /friendship/all
获取好友列表
#### 请求参数
```
```
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200,
"result": [{
"displayName": "Martin",
"message": "你好,我是一杯水",
"status": 10,
"updatedAt": "2017-09-18",
"user": {
"id": "slEcpCI63",
"nickname": "一杯水",
"region": "86",
"phone": "13269772766",
"portraitUri": "http://7xogjk.com1.z0.glb.clouddn.com/Fo6wxS7zzvGpwyAFhlpTUVirpOGh"
}
}]
}
```
* displayName: 好友备注
* message: 请求加好友描述
* status: 好友关系状态 10: 请求, 11: 被请求, 20: 同意, 21: 忽略, 30: 被删除
* updatedAt: 最后一次好友状态修改时间
* user: 加好友请求发起方用户信息
* user.id: Id
* user.nickname: 昵称
* user.region: 手机号区域标识
* user.phone: 手机号
* user.portraiUri: 头像
返回码说明:
* 200: 请求成功
### GET /friendship/:friendId/profile
获取好友信息
#### 请求参数
```
friendId: 好友 Id
```
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{ "code":200,
"result": {
"displayName": "Wee",
"user": {
"id":"g891BoDvN",
"nickname":"Tina",
"region":"86",
"phone":"18221252163",
"portraitUri":"http://7xogjk.com1.z0.glb.clouddn.com/FjsNMjYoVKfGmA86SNwnggfKgE6_"
}
}
}
```
* displayName: 好友备注
* user: 好友信息
* user.id: Id
* user.nickname: 昵称
* user.region: 手机号区域标识
* user.phone: 手机号
* user.portraiUri: 头像
返回码说明:
* 200: 请求成功
* 403: friendId 非当前用户好友
### 群组相关接口
| 接口地址 | 说明 |
|---------|-----|
| [/group/create](#post-groupcreate) | 创建群组 |
| [/group/add](#post-groupadd) | 添加群成员 |
| [/group/join](#post-groupjoin) | 加入群组 |
| [/group/kick](#post-groupkick) | 踢人 |
| [/group/quit](#post-groupquit) | 退出群组 |
| [/group/dismiss](#post-groupdismiss) | 解散群组 |
| [/group/transfer](#post-grouptransfer) | 转让群主角色 |
| [/group/rename](#post-grouprename) | 群组重命名 |
| [/group/set_bulletin](#post-groupset_bulletin) | 发布群公告 |
| [/group/set_portrait_uri](#post-groupset_portrait_uri) | 设置群头像 |
| [/group/set_display_name](#post-groupset_display_name) | 设置群名片 |
| [/group/:id](#get-groupid) | 获取群信息 |
| [/group/:id/members](#get-groupidmembers) | 获取群成员 |
## API 说明
### POST /group/create
创建群组
#### 请求参数
```
{
"name": "RongCloud",
"memberIds": ["AUj8X32w1", "ODbpJIgrL"]
}
```
* name: 群名称
* memberIds: 群成员 Id 列表, 包含 `创建者 Id`
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code":200,
"result": {
"id": "RfqHbcjes"
}
}
```
* id: 群组 Id
返回码说明:
* 200: 请求成功
* 400: 错误的请求
* 1000: 群组个数超限
### POST /group/add
添加群成员
#### 请求参数
```
{
"groupId": "KC6kot3ID",
"memberIds": ["52dzNbLBZ"]
}
```
* groupId: 群组 Id
* memberIds: userId 列表
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 400: 错误的请求
### POST /group/join
用户加入群组
#### 请求参数
```
{
groupId: "KC6kot3ID"
}
```
*
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 400: 错误的请求
### POST /group/kick
群主或群管理将群成员移出群组
#### 请求参数
```
{
"groupId": "KC6kot3ID",
"memberIds": ["52dzNbLBZ"]
}
```
* groupId: 群组 Id
* memberIds: userId 列表
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 400: 错误的请求
* 404: 未知群组
* 403: 当前用户不创建者
* 500: 服务器内部错误,无法同步数据至 RongCloud IM Server
### POST /group/quit
用户退出群组
#### 请求参数
```
{
groupId: "KC6kot3ID"
}
```
* groupId: 群组 Id
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 400: 错误的请求
* 404: 未知群组
* 403: 当前用户不在群组中
* 500: 服务器内部错误,无法同步数据至 RongCloud IM Server
### POST /group/dismiss
解散群组
#### 请求参数
```
{
groupId: "KC6kot3ID"
}
```
* groupId: 群组 Id
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 400: 当前用户不是创建者
* 500: 服务器内部错误,无法同步数据至 RongCloud IM Server
### POST /group/transfer
转让群主
#### 请求参数
```
{
groupId: "KC6kot3ID",
userId: "52dzNbLBZ"
}
```
* groupId: 群组 Id
* userId: 用户 Id
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 400: 当前用户不是创建者
* 403: 不能把群主转让给自己
### POST /group/rename
群组重命名
#### 请求参数
```
{
groupId: "KC6kot3ID",
name: "RongCloud"
}
```
* groupId: 群组 Id
* name: 群名称, 长度不超过 32 个字符
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 400: 群名长度超限
### POST /group/set_bulletin
发布群公告
#### 请求参数
```
{
groupId: "KC6kot3ID",
bulletin: "@All 明天 4 点下班"
}
```
* groupId: 群组 Id
* bulletin: 群公告内容, 长度不超过 1024 个字符
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 400: 非法请求,未知群组或当前不用不是群组
### POST /group/set_portrait_uri
设置群头像
#### 请求参数
```
{
groupId: "KC6kot3ID",
portraitUri: "http://7xogjk.com1.z0.glb.clouddn.com/u0LUuhzHm1466557920584458984"
}
```
* groupId: 群组 Id
* portraitUri: 群头像地址, 长度不能超过 256 个字符
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 400: 非法请求
### POST /group/set_display_name
设置自己的群名片
#### 请求参数
```
{
groupId: "KC6kot3ID",
displayName: "Martin"
}
```
* groupId: 群组 Id
* displayName: 群名片
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200
}
```
返回码说明:
* 200: 请求成功
* 404: 未知群组
### GET /group/:id
获取群信息
#### 请求参数
```
{
groupId: "KC6kot3ID"
}
```
* groupId: 群组 Id
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200,
"result": {
"id": "KC6kot3ID",
"name": "RongCloud",
"portraitUri": "",
"memberCount": 13,
"maxMemberCount": 500,
"creatorId": "I8cpNlo7t",
"type": 1,
"bulletin": null,
"deletedAt": null
}
}
```
* id: 群组 Id
* name: 群名称
* portraitUri: 群头像
* memberCount: 群人数
* maxMemberCount: 群人数上限
* creatorId: 群主 Id
* type: 类型 1 普通群 2 企业群
* bulletin: 群公告
* deletedAt: 删除日期
返回码说明:
* 200: 请求成功
* 404: 未知群组
### GET /group/:id/members
获取群成员列表
#### 请求参数
```
{
groupId: "KC6kot3ID"
}
```
* groupId: 群组 Id
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
"code": 200,
"result": [
{
"displayName": "",
"role": 1,
"createdAt": "2016-11-22T03:06:13.000Z",
"updatedAt": "2016-11-22T03:06:13.000Z",
"user": {
"id": "xNlpDTUmw",
"nickname": "zl01",
"portraitUri": ""
}
},{
"displayName": "",
"role": 1,
"createdAt": "2016-11-22T03:14:09.000Z",
"updatedAt": "2016-11-22T03:14:09.000Z",
"user": {
"id": "h6nEgcPC7",
"nickname": "zl02",
"portraitUri": ""
}]
}
```
* displayName: 群名片
* role: 群角色
* createdAt: 创建时间
* updatedAt: 更改时间
* id: userId
* nickname: 用户名称
* portraitUri: 用户头像
返回码说明:
* 200: 请求成功
* 404: 未知群组
### 群组相关接口
| 接口地址 | 说明 |
|---------|-----|
| [/misc/latest_update](#get-misclatest_update) | 注册新用户 |
| [/misc/client_version](#get-miscclient_version) | 用户登录 |
| [/misc/send_message](#post-miscsend_message) | 通过手机验证码设置新密码 |
## API 说明
### GET /misc/latest_update?version=1.0.0
获取客户端最新版本( Desktop 使用 )
#### 请求参数
```
version
```
* version: 三位版本号,例如 1.0.0
#### 返回结果
```
{
"version": "1.0.2",
"url": "http://downloads.rongcloud.cn/sealtalk/releases/mac/SealTalk_by_RongCloud_1.0.2.dmg",
"name": "SealTalk 1.0.2",
"notes": "bug fix",
"pub_date": "2017-05-05"
}
```
* version: 最新版本
* url: 下载地址
* name: 包名
* notes: 更新日志
* pub_date: 最近一次更新日期
返回码说明:
* 204: 版本没有变化
* 400: 版本无效
### GET /misc/client_version
Android、iOS 获取更新版本
#### 请求参数
```
```
#### 返回结果
```
{
"iOS": {
"version": "1.0.5",
"build": "201607181821",
"url": "https://dn-rongcloud.qbox.me/app.plist"
},
"Android": {
"version": "1.0.5",
"url": "http://downloads.rongcloud.cn/SealTalk_by_RongCloud_Android_v1_0_5.apk"
}
}
```
### POST /misc/send_message
Server API 发送消息
#### 请求参数
```
{
conversationType: 'PRIVATE',
targetId: 'DS9ahdanm',
objectName: 'RC:TxtMsg',
content: '你好,Martin',
pushContent: 'push 内容'
}
```
* conversationType: 会话类型 `PRIVATE` `GROUP`
* targetId: 接收者 Id
* objectName: 消息类型 `RC:TxtMsg` `RC:ImgMsg` 更多请查看 [消息类型](http://rongcloud.cn/docs/server.html#message_type)
* content: 消息内容
* pushContent: push 内容
#### 返回结果
正常返回,返回的 HTTP Status Code 为 200,返回的内容如下:
```
{
code: "200"
}
```
返回码说明:
* 200: 发送成功
* 403: PRIVATE 与对方不是好友
* 403: GROUP 发送消息用户不在群组中
* 403: 未知会话类型,默认支持 PRIVATE、GROUP
\ No newline at end of file
## 客户端数据同步策略说明
通过以版本号为基础的数据同步策略,能够极大的降低客户端到服务器的请求次数和流量,提高业务性能和用户体验。注意:客户端数据同步策略并不需要强制使用。
### 本地缓存数据库设计
客户端本地建立如下一套表格作为本地数据缓存:用户表、黑名单表、好友关系表、加入的群组表、加入的群组的成员关系表,用来存储需要的数据。各表结构如下:
用户表(当前用户及其好友):
| 字段名 | 数据类型 | 说明 |
|-------------|:-------------:|----------|
| id | INT UNSIGNED | 用户 Id |
| nickname | VARCHAR(32) | 用户的昵称 |
| portraitUri | VARCHAR(256) | 用户的头像地址 |
| timestamp | BIGINT | 时间戳(版本号) |
黑名单表:
| 字段名 | 数据类型 | 说明 |
|-------------|:-------------:|----------|
| friendId | INT UNSIGNED | 好友 Id |
| status | TINYINT | 黑名单状态,参考 db.coffee 中的相关定义 |
| timestamp | BIGINT | 时间戳(版本号) |
好友关系表:
| 字段名 | 数据类型 | 说明 |
|-------------|:-------------:|----------|
| friendId | INT UNSIGNED | 好友 Id |
| displayName | VARCHAR(32) | 好友屏显名 |
| status | INT | 好友关系状态,参考 db.coffee 中的相关定义 |
| timestamp | BIGINT | 时间戳(版本号)|
加入的群组表:
| 字段名 | 数据类型 | 说明 |
|-------------|:-------------:|----------|
| id | INT UNSIGNED | 群组 Id |
| name | VARCHAR(32) | 群组名称 |
| portraitUri | VARCHAR(256) | 群组头像地址 |
| displayName | VARCHAR(32) | 当前用户在群组中的屏显名 |
| role | INT | 当前用户在群组中的权限,参考 db.coffee |
| timestamp | BIGINT | 时间戳(版本号)|
加入的群组的成员关系表:
| 字段名 | 数据类型 | 说明 |
|-------------|:-------------:|----------|
| groupId | INT UNSIGNED | 群组成员所属群组 Id |
| memberId | INT UNSIGNED | 群组成员 Id |
| displayName | VARCHAR(32) | 群组成员的屏显名 |
| role | INT | 群组成员的权限,参考 db.coffee |
| nickname | VARCHAR(32) | 群组成员的昵称 |
| portraitUri | VARCHAR(256) | 群组成员的头像 |
| timestamp | BIGINT | 时间戳(版本号)|
### 同步策略
客户端本地保存一个当前版本号数据,如 `version`,用户创建时,本地值为 `0`
1、登录同步:
每次登录后,调用服务器 `GET /user/sync/:version` 接口,将本地的版本号 `version` 传递给服务端,服务端会返回 `version` 之后所有的变化数据结果集。
根据情况,将结果集的数据更新到本地缓存表中,包括插入、更新、删除(结果集中返回群组、群成员 isDeleted == true 或者黑名单 status == 0 或者好友关系 status == 30)
最后,将本地的版本号 `version` 更新为刚刚接口返回的最新版本号 `version` 即可。
2、操作同步:
群成员变化时,会收到通知消息 `GroupNotificationMessage`,通知消息中也包含时间戳(版本号)`timestamp`,可以根据通知消息中的信息,更新到本地缓存数据库中。
当进行各种操作时,请注意更新本地缓存中的数据,并更新本地各个字段的时间戳(版本号),但不要更新本地的 `version`
### 本地缓存读取策略
采用客户端数据同步策略后,所有的用户信息、好友关系、黑名单列表、群组信息、群组成员信息,都可以直接从本地缓存中读取。
## 好友关系说明
在数据库 `friendships``status` 字段中包括如下值:
* FRIENDSHIP_REQUESTING = 10
* FRIENDSHIP_REQUESTED = 11
* FRIENDSHIP_AGREED = 20
* FRIENDSHIP_IGNORED = 21
* FRIENDSHIP_DELETED = 30
所有可能的状态组合如下:
| 对自己的状态 | 自己 | 好友 | 对好友的状态 |
|------------|:---:|:----:|------------|
| 发出了好友邀请 | 10 | 11 | 收到了好友邀请 |
| 发出了好友邀请 | 10 | 21 | 忽略了好友邀请 |
| 已是好友 | 20 | 20 | 已是好友 |
| 已是好友 | 20 | 30 | 删除了好友关系 |
| 删除了好友关系 | 30 | 30 | 删除了好友关系 |
# 配置安装与开发调试
## 安装 Git
[Git 官网](https://git-scm.com/downloads)
## 安装 Node
[Node 官网](https://nodejs.org), 支持的最低为 4.0。
## 安装 MySQL
[MySQL 官网](https://www.mysql.com/)
## 项目配置
请修改 [conf.js](../src/conf.js) 文件中的相关配置,详细请参看 [conf.js](../src/conf.js) 中的注释和示例。
> 如果您熟悉项目中使用的 Sequelize 数据库框架,也可以自行安装配置其他类型的数据库。但需要修改 db.js 中相应的 SQL 语句。
## 初始化
项目根目录下执行:
```
node install.js
```
## 设置环境量
Windows : `set NODE_ENV=development`
Mac/Linux : `export NODE_ENV=development`
## 启动服务
```
grunt nodemon
```
## 业务数据配置 (无需求略过)
client_version.json : 配置 SealTalk 移动端的最新 App 版本号、下载地址等信息。
squirrel.json : 配置 SealTalk Desktop 端的最新 App 版本号、下载地址等信息。
demo_square.json : 配置 SealTalk 移动端“发现”频道中的默认聊天室和群组数据。
## 生产环境部署
### 部署文件
项目根目录下执行:
```
grunt release
```
然后将 `dist` 目录拷贝到部署路径即可。
### 修改配置文件
修改 `dist` 目录下 `conf.js` 文件,请根据需要配置,配置项同上述开发环境说明。
### 修改环境变量
生产环境下请设置 `NODE_ENV=production`
### 启动服务
请在部署路径中用 `PM2` 等工具启动 `index.js` 文件。或直接使用 `node index.js` 启动(不推荐)。
var exec = require('child_process').exec;
var os = require('os');
var showResult = function(err, stdout) {
console.log(err || stdout);
};
var execShell = function(shell, callback) {
exec(shell, function(err, stdout, stderr) {
callback(err, stdout);
});
};
var addSudo = function() {
return os.platform() == 'linux' ? 'sudo ' : '';
};
var getMethod = function() {
return os.platform() == 'linux' ? 'export' : 'set';
};
var initGrunt = function() {
var shell = addSudo() + 'npm install -g grunt-cli';
execShell(shell, function(err, stdout) {
showResult(err, stdout);
next();
});
};
var initDeps = function() {
var shell = addSudo() + 'npm install';
execShell(shell, function(err, stdout) {
showResult(err, stdout);
next();
});
};
var initDB = function() {
var shell = 'node sync.js --harmony';
execShell(shell, function(err, stdout) {
showResult(err, stdout);
var logs = ['',
'初始化已完成,请在下方命令行执行: ',
'-----------------------------------------------',
'1、设置环境变量: |',
' |',
' Windows : set NODE_ENV=development |',
' |',
' Mac/Linux: export NODE_ENV=development |',
'-----------------------------------------------',
'2、启动服务: |',
' |',
' grunt nodemon |',
'-----------------------------------------------'];
!err && showResult(logs.join('\n'));
});
};
var methods = [initGrunt, initDeps, initDB],
index = 0;
var next = function() {
showResult('正在初始化,请耐心等待...');
methods[index]();
index++;
};
next();
// initDB();
\ No newline at end of file
{
"name": "sealtalk-server",
"version": "1.0.0",
"description": "SealTalk App Server powered by RongCloud.",
"scripts": {
"test": "node node_modules/istanbul/lib/cli.js cover node_modules/jasmine/bin/jasmine.js",
"initdb": "node sync.js --harmony"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rongcloud/sealtalk-server.git"
},
"keywords": [
"SealTalk",
"RongCloud",
"IM"
],
"author": "Ariel Yang <arielyang@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/rongcloud/sealtalk-server/issues"
},
"homepage": "https://github.com/rongcloud/sealtalk-server#readme",
"dependencies": {
"body-parser": "^1.14.1",
"co": "^4.6.0",
"compression": "^1.6.0",
"cookie-parser": "^1.4.0",
"cors": "^2.7.1",
"debug": "^2.2.0",
"express": "^4.13.3",
"jsonfile": "^2.2.3",
"lru-cache": "^4.0.1",
"moment": "^2.10.6",
"mysql": "^2.9.0",
"qiniu": "^6.1.8",
"rongcloud-sdk": "git+https://github.com/rongcloud/server-sdk-nodejs.git",
"semver": "^5.1.0",
"sequelize": "^3.8.0",
"underscore": "^1.8.3",
"xss": "^0.2.13"
},
"devDependencies": {
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-copy": "^0.8.1",
"grunt-contrib-watch": "^0.6.1",
"grunt-nodemon": "^0.4.0",
"istanbul": "^0.4.0",
"jasmine": "^2.3.2",
"supertest": "^1.1.0"
}
}
{
"iOS": {
"version": "1.0.5",
"build": "201607181821",
"url": "https://dn-rongcloud.qbox.me/app.plist"
},
"Android": {
"version": "1.0.5",
"url": "http://downloads.rongcloud.cn/SealTalk_by_RongCloud_Android_v1_0_5.apk"
}
}
module.exports = {
// 认证 Cookie 名称,请根据业务自行定义,如:rong_im_auth
AUTH_COOKIE_NAME: '<-- 此处设置 Cookie 名称 -->',
// 认证 Cookie 加密密钥,请自行定义,任意字母数字组合
AUTH_COOKIE_KEY: '<-- 此处设置 Cookie 加密密钥 -->',
// 认证 Cookie 过期时间,单位为毫秒,2592000000 毫秒 = 30 天
AUTH_COOKIE_MAX_AGE: 2592000000,
// 融云颁发的 App Key,请访问融云开发者后台:https://developer.rongcloud.cn
RONGCLOUD_APP_KEY: '<-- 此处填写融云颁发的 App Key -->',
// 融云颁发的 App Secret,请访问融云开发者后台:https://developer.rongcloud.cn
RONGCLOUD_APP_SECRET: '<-- 此处填写融云颁发的 App Secret -->',
// 融云短信服务提供的注册用户短信模板 Id
RONGCLOUD_SMS_REGISTER_TEMPLATE_ID: '<-- 此处填写融云颁发的短信模板 Id -->',
// 七牛颁发的 Access Key,请访问七牛开发者后台:https://portal.qiniu.com
QINIU_ACCESS_KEY: '<-- 此处填写七牛颁发的 Access Key -->',
// 七牛颁发的 Secret Key,请访问七牛开发者后台:https://portal.qiniu.com
QINIU_SECRET_KEY: '<-- 此处填写七牛颁发的 Secret Key -->',
// 七牛创建的空间名称,请访问七牛开发者后台:https://portal.qiniu.com
QINIU_BUCKET_NAME: '<-- 此处填写七牛创建的空间名称 -->',
// 七牛创建的空间域名,请访问七牛开发者后台:https://portal.qiniu.com
QINIU_BUCKET_DOMAIN: '<-- 此处填写七牛创建的空间域名 -->',
// N3D 密钥,用来加密所有的 Id 数字,不小于 5 位的字母数字组合
N3D_KEY: '<-- 此处设置加密 Id 的密钥 -->',
// 认证 Cookie 主域名 如果没有正式域名,请修改本地 hosts 文件配置域名
AUTH_COOKIE_DOMAIN: '<-- 此处设置 Cookie 主域名, 必须和 CORS_HOSTS 配置项在相同的顶级域下 例如: api.sealtalk.im -->',
// 跨域支持所需配置的域名信息,包括请求服务器的域名和端口号,如果是 80 端口可以省略端口号。如:http://web.sealtalk.im
CORS_HOSTS: '<-- 此处设置请求的域名信息 , 例如: web.sealtalk.im -->',
// 本服务部署的 HTTP 端口号
SERVER_PORT: 8585,
// MySQL 数据库名称
DB_NAME: '<-- 此处设置数据库名称 -->',
// MySQL 数据库用户名
DB_USER: '<-- 此处设置数据库用户名 -->',
// MySQL 数据库密码
DB_PASSWORD: '<-- 此处设置数据库密码 -->',
// MySQL 数据库服务器地址
DB_HOST: '<-- 此处设置数据库服务器的 IP 地址 -->',
// MySQL 数据库服务端口号
DB_PORT: 3306
};
// 示例:
/**
module.exports = {
AUTH_COOKIE_NAME: 'rong_auth_cookie',
NICKNAME_COOKIE_NAME: '',
AUTH_COOKIE_MAX_AGE: '2592000000',
RONGCLOUD_SMS_REGISTER_TEMPLATE_ID: '6iYv6rln4agT3tIPJCS2',
RONGCLOUD_APP_KEY: '8lupauivucail',
RONGCLOUD_APP_SECRET: 'y0i9asj14h1LWz',
QINIU_ACCESS_KEY: 'livk5rb3__JZjCtEiMxpQ8QscsLxbNLehwhHySnX',
QINIU_SECRET_KEY: 'ysrYdcDrrF425QNz0sfa9RoafANC6Hni3TIVgjw5',
QINIU_BUCKET_NAME: 'devtalk-image',
QINIU_BUCKET_DOMAIN: '7x2gjk.com1.z0.glb.clouddn.com',
N3D_KEY: '11EdDIaqpcim',
AUTH_COOKIE_DOMAIN: 'devtalk.im',
CORS_HOSTS: 'http://web.devtalk.im',
SERVER_PORT: '8585',
DB_NAME: 'sealtalk',
DB_USER: 'devtalk',
DB_PASSWORD: 'devtalk',
DB_HOST: '127.0.0.1',
DB_PORT: '3306'
};
*/
\ No newline at end of file
[
{
"type": "group",
"id": 10
},
{
"type": "group",
"id": 15
},
{
"type": "group",
"id": 10000
},
{
"type": "chatroom",
"id": 1,
"name": "聊天室 I"
},
{
"type": "chatroom",
"id": 2,
"name": "聊天室 II"
},
{
"type": "chatroom",
"id": 3,
"name": "聊天室 III"
},
{
"type": "chatroom",
"id": 4,
"name": "聊天室 IV"
}
]
var Config, HTTPError, Session, Utility, app, authentication, bodyParser, cacheControl, compression, cookieParser, cors, env, errorHandler, express, friendshipRouter, groupRouter, miscRouter, parameterPreprocessor, server, userRouter;
express = require('express');
cookieParser = require('cookie-parser');
bodyParser = require('body-parser');
compression = require('compression');
cors = require('cors');
Config = require('./conf');
Session = require('./util/session');
Utility = require('./util/util').Utility;
HTTPError = require('./util/util').HTTPError;
userRouter = require('./routes/user');
friendshipRouter = require('./routes/friendship');
groupRouter = require('./routes/group');
miscRouter = require('./routes/misc');
if ((env = process.env.NODE_ENV) !== 'development' && env !== 'production') {
console.log("Error: NODE_ENV must be set to 'development' or 'production'.");
return process.exit();
}
app = express();
app.use(cors({
origin: Config.CORS_HOSTS,
credentials: true
}));
app.use(compression());
app.use(cookieParser());
app.use(bodyParser.json());
authentication = function(req, res, next) {
var body, currentUserId, i, len, ref, reqPath, userAgent;
userAgent = req.get('user-agent').substr(0, 50);
ref = ['/misc/demo_square', '/misc/latest_update', '/misc/client_version', '/user/login', '/user/register', '/user/reset_password', '/user/send_code', '/user/verify_code', '/user/get_sms_img_code', '/user/check_username_available', '/user/check_phone_available'];
for (i = 0, len = ref.length; i < len; i++) {
reqPath = ref[i];
if (req.path === reqPath) {
if (req.body.password) {
body = JSON.stringify(req.body).replace(/"password":".*?"/, '"password":"**********"');
} else {
body = JSON.stringify(req.body);
}
Utility.logPath('%s %s %s %s', userAgent, req.method, req.originalUrl, body);
return next();
}
}
currentUserId = Session.getCurrentUserId(req);
if (!currentUserId) {
return res.status(403).send('Not loged in.');
}
Utility.logPath('%s User(%s/%s) %s %s %s', userAgent, Utility.encodeId(currentUserId), currentUserId, req.method, req.originalUrl, JSON.stringify(req.body));
return next();
};
cacheControl = function(req, res, next) {
res.set('Cache-Control', 'private');
return next();
};
parameterPreprocessor = function(req, res, next) {
var prop;
for (prop in req.body) {
if (prop.endsWith('Id') || prop.endsWith('Ids')) {
req.body['encoded' + prop[0].toUpperCase() + prop.substr(1)] = req.body[prop];
req.body[prop] = Utility.decodeIds(req.body[prop]);
}
if (Utility.isEmpty(req.body[prop]) && prop !== 'displayName' && prop !== 'pushContent' && prop !== 'bulletin') {
return res.status(400).send("Empty " + prop + ".");
}
}
return next();
};
errorHandler = function(err, req, res, next) {
if (err instanceof HTTPError) {
return res.status(err.statusCode).send(err.message);
}
Utility.logError(err);
return res.status(500).send(err.message || 'Unknown error.');
};
app.all('*', authentication);
app.use(parameterPreprocessor);
app.use(cacheControl);
app.use('/user', userRouter);
app.use('/friendship', friendshipRouter);
app.use('/group', groupRouter);
app.use('/misc', miscRouter);
app.use(errorHandler);
server = app.listen(Config.SERVER_PORT, function() {
var map = {
development: '开发环境',
production: '生产环境'
};
return console.log('%s服务已启动,地址: http://%s:%s', map[app.get('env')], server.address().address, server.address().port);
});
module.exports = app;
\ No newline at end of file
var APIResult, Blacklist, Cache, Config, FRIENDSHIP_AGREED, Friendship, Group, GroupMember, Session, User, Utility, _, express, jsonfile, path, ref, rongCloud, router, semver, sequelize;
express = require('express');
_ = require('underscore');
jsonfile = require('jsonfile');
path = require('path');
semver = require('semver');
rongCloud = require('rongcloud-sdk');
Config = require('../conf');
Cache = require('../util/cache');
Session = require('../util/session');
Utility = require('../util/util').Utility;
APIResult = require('../util/util').APIResult;
ref = require('../db'), sequelize = ref[0], User = ref[1], Blacklist = ref[2], Friendship = ref[3], Group = ref[4], GroupMember = ref[5];
FRIENDSHIP_AGREED = 20;
rongCloud.init(Config.RONGCLOUD_APP_KEY, Config.RONGCLOUD_APP_SECRET);
router = express.Router();
router.get('/latest_update', function(req, res, next) {
var clientVersion, err;
clientVersion = req.query.version;
try {
return Cache.get('latest_update').then(function(squirrelConfig) {
if (!squirrelConfig) {
squirrelConfig = jsonfile.readFileSync(path.join(__dirname, '../squirrel.json'));
Cache.set('latest_update', squirrelConfig);
}
if ((semver.valid(clientVersion) === null) || (semver.valid(squirrelConfig.version) === null)) {
return res.status(400).send('Invalid version.');
}
if (semver.gte(clientVersion, squirrelConfig.version)) {
return res.status(204).end();
} else {
return res.send(squirrelConfig);
}
});
} catch (_error) {
err = _error;
return next(err);
}
});
router.get('/client_version', function(req, res, next) {
var err;
try {
return Cache.get('client_version').then(function(clientVersionInfo) {
if (!clientVersionInfo) {
clientVersionInfo = jsonfile.readFileSync(path.join(__dirname, '../client_version.json'));
Cache.set('client_version', clientVersionInfo);
}
return res.send(clientVersionInfo);
});
} catch (_error) {
err = _error;
return next(err);
}
});
router.get('/demo_square', function(req, res, next) {
var demoSquareData, err, groupIds;
try {
demoSquareData = jsonfile.readFileSync(path.join(__dirname, '../demo_square.json'));
groupIds = _.chain(demoSquareData).where({
type: 'group'
}).pluck('id').value();
return Group.findAll({
where: {
id: {
$in: groupIds
}
},
attributes: ['id', 'name', 'portraitUri', 'memberCount']
}).then(function(groups) {
demoSquareData.forEach(function(item) {
var group;
if (item.type === 'group') {
group = _.findWhere(groups, {
id: item.id
});
if (!group) {
group = {
name: 'Unknown',
portraitUri: '',
memberCount: 0
};
}
item.name = group.name;
item.portraitUri = group.portraitUri;
item.memberCount = group.memberCount;
return item.maxMemberCount = group.maxMemberCount;
}
});
return res.send(new APIResult(200, Utility.encodeResults(demoSquareData)));
});
} catch (_error) {
err = _error;
return next(err);
}
});
router.post('/send_message', function(req, res, next) {
var content, conversationType, currentUserId, encodedCurrentUserId, encodedTargetId, objectName, pushContent, targetId;
conversationType = req.body.conversationType;
targetId = req.body.targetId;
objectName = req.body.objectName;
content = req.body.content;
pushContent = req.body.pushContent;
encodedTargetId = req.body.encodedTargetId;
currentUserId = Session.getCurrentUserId(req);
encodedCurrentUserId = Utility.encodeId(currentUserId);
switch (conversationType) {
case 'PRIVATE':
return Friendship.count({
where: {
userId: currentUserId,
friendId: targetId,
status: FRIENDSHIP_AGREED
}
}).then(function(count) {
if (count > 0) {
return rongCloud.message["private"].publish(encodedCurrentUserId, encodedTargetId, objectName, content, pushContent, function(err, resultText) {
if (err) {
Utility.logError('Error: send message failed: %j', err);
throw err;
}
return res.send(new APIResult(200));
});
} else {
return res.status(403).send("User " + encodedTargetId + " is not your friend.");
}
});
case 'GROUP':
return GroupMember.count({
where: {
groupId: targetId,
memberId: currentUserId
}
}).then(function(count) {
if (count > 0) {
return rongCloud.message.group.publish(encodedCurrentUserId, encodedTargetId, objectName, content, pushContent, function(err, resultText) {
if (err) {
Utility.logError('Error: send message failed: %j', err);
throw err;
}
return res.send(new APIResult(200));
});
} else {
return res.status(403).send("Your are not member of Group " + encodedTargetId + ".");
}
});
default:
return res.status(403).send('Unsupported conversation type.');
}
});
module.exports = router;
{
"version": "1.0.2",
"url": "http://downloads.rongcloud.cn/sealtalk/releases/mac/SealTalk_by_RongCloud_1.0.2.dmg",
"name": "SealTalk 1.0.2",
"notes": "修正",
"pub_date": "2016-05-05T16:00:00+08:00"
}
var Cache, Config, LRU, Utility;
LRU = require("lru-cache");
Config = require('../conf');
Utility = require('./util').Utility;
Cache = (function() {
function Cache() {}
Cache.cache = LRU({
max: 100000,
maxAge: 3600000
});
Cache.set = function(key, value) {
Utility.log("Cache: set '%s'.", key);
return Promise.resolve(this.cache.set(key, value));
};
Cache.get = function(key) {
Utility.log("Cache: get '%s'.", key);
return Promise.resolve(this.cache.get(key));
};
Cache.del = function(key) {
Utility.log("Cache: del '%s'.", key);
return Promise.resolve(this.cache.del(key));
};
return Cache;
})();
module.exports = Cache;
var N3D;
N3D = (function() {
var isUnsigned;
function N3D(key, lower, upper) {
var a, charMap, i, j, k, l, n, ref, s;
this.lower = lower;
this.upper = upper;
charMap = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
this.radix = charMap.length;
this.dict = [];
this.keyCode = 0;
if (!isUnsigned(this.lower) || !isUnsigned(this.upper)) {
throw new Error('Parameter is error.');
}
if (this.upper <= this.lower) {
throw new Error('The upper must be greater than the lower.');
}
if (typeof key !== 'string' || key.length === 0) {
throw new Error('The key is error.');
}
for (i = n = 0, ref = key.length; 0 <= ref ? n < ref : n > ref; i = 0 <= ref ? ++n : --n) {
a = key.charCodeAt(i);
if (a > 127) {
throw new Error('The key is error.');
}
this.keyCode += a * Math.pow(128, i % 7);
}
if (this.keyCode + this.radix < this.upper) {
throw new Error('The secret key is too short.');
}
i = this.keyCode - this.radix;
j = 0;
while (i < this.keyCode) {
this.dict[j] = [];
k = this.radix;
l = 0;
while (k > 0) {
s = i % k;
this.dict[j][l] = charMap[s];
charMap[s] = charMap[k - 1];
k--;
l++;
}
charMap = this.dict[j].slice(0);
i++;
j++;
}
}
isUnsigned = function(num) {
return Math.floor(num) === num && num > 0 && num < Number.MAX_VALUE;
};
N3D.prototype.encrypt = function(num) {
var m, map, result, s;
if (!isUnsigned(num) || num > this.upper || num < this.lower) {
throw new Error('Parameter is error.');
}
num = this.keyCode - num;
result = [];
m = num % this.radix;
map = this.dict[m];
s = 0;
result.push(this.dict[0][m]);
while (num > this.radix) {
num = (num - m) / this.radix;
m = num % this.radix;
if ((s = m + s) >= this.radix) {
s -= this.radix;
}
result.push(map[s]);
}
return result.join('');
};
N3D.prototype.decrypt = function(str) {
var chars, i, j, len, map, n, ref, result, s, t;
if (typeof str !== 'string' && str.length === 0) {
throw new Error('Parameter is error.');
}
chars = str.split('');
len = chars.length;
t = 0;
s = 0;
result = this.dict[0].join('').indexOf(chars[0]);
if (result < 0) {
throw new Error('Invalid string.');
}
map = this.dict[result].join('');
for (i = n = 1, ref = len; 1 <= ref ? n < ref : n > ref; i = 1 <= ref ? ++n : --n) {
j = map.indexOf(chars[i]);
if (j < 0) {
throw new Error('Invalid string.');
}
if ((s = j - t) < 0) {
s += this.radix;
}
result += s * Math.pow(this.radix, i);
t = j;
}
result = this.keyCode - result;
if (result > this.upper || result < this.lower) {
throw new Error('Invalid string.');
}
return result;
};
return N3D;
})();
module.exports = N3D;
var Cache, Config, LRU, Session, Utility, co, process;
process = require('process');
LRU = require("lru-cache");
co = require('co');
Config = require('../conf');
Cache = require('./cache');
Utility = require('./util').Utility;
Session = (function() {
function Session() {}
Session.getCurrentUserId = function(req) {
var cookie;
cookie = req.cookies[Config.AUTH_COOKIE_NAME];
if (!cookie) {
return null;
}
return parseInt(Utility.decryptText(cookie, Config.AUTH_COOKIE_KEY));
};
Session.getCurrentUserNickname = function(userId, UserModel) {
return new Promise(function(resolve, reject) {
return Cache.get('nickname_' + userId).then(function(cachedNickname) {
if (cachedNickname) {
return resolve(cachedNickname);
}
return UserModel.getNickname(userId).then(function(nickname) {
if (nickname) {
Cache.set('nickname_' + userId, nickname);
}
return resolve(nickname);
})["catch"](function(err) {
return reject(err);
});
});
});
};
Session.setNicknameToCache = function(userId, nickname) {
if (!Number.isInteger(userId) || Utility.isEmpty(nickname)) {
throw new Error('Invalid userId or nickname.');
}
return Cache.set('nickname_' + userId, nickname);
};
Session.setAuthCookie = function(res, userId) {
var value;
value = Utility.encryptText(userId, Config.AUTH_COOKIE_KEY);
return res.cookie(Config.AUTH_COOKIE_NAME, value, {
httpOnly: true,
domain: Config.AUTH_COOKIE_DOMAIN,
maxAge: Config.AUTH_COOKIE_MAX_AGE,
expires: new Date(Date.now() + Config.AUTH_COOKIE_MAX_AGE)
});
};
return Session;
})();
module.exports = Session;
var APIResult, Config, HTTPError, N3D, Utility, crypto, debug, process, xss,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
crypto = require('crypto');
process = require('process');
debug = require('debug');
xss = require('xss');
Config = require('../conf');
N3D = require('./n3d');
Utility = (function() {
function Utility() {}
Utility.n3d = new N3D(Config.N3D_KEY, 1, 4294967295);
Utility.log = debug('app:log');
Utility.logPath = debug('app:path');
Utility.logError = debug('app:error');
Utility.logResult = debug('app:result');
Utility.encryptText = function(text, password) {
var cipher, crypted, salt;
salt = this.random(1000, 9999);
text = salt + '|' + text + '|' + Date.now();
cipher = crypto.createCipher('aes-256-ctr', password);
crypted = cipher.update(text, 'utf8', 'hex');
return crypted += cipher.final('hex');
};
Utility.decryptText = function(text, password) {
var dec, decipher, strs;
decipher = crypto.createDecipher('aes-256-ctr', password);
dec = decipher.update(text, 'hex', 'utf8');
dec += decipher.final('utf8');
strs = dec.split('|');
if (strs.length !== 3) {
throw new Error('Invalid cookie value!');
}
return strs[1];
};
Utility.hash = function(text, salt) {
var sha1;
text = text + '|' + salt;
sha1 = crypto.createHash('sha1');
sha1.update(text, 'utf8');
return sha1.digest('hex');
};
Utility.random = function(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
};
Utility.isEmpty = function(obj) {
return obj === '' || obj === null || obj === void 0 || (Array.isArray(obj) && obj.length === 0);
};
Utility.decodeIds = function(obj) {
if (obj === null) {
return null;
}
if (Array.isArray(obj)) {
return obj.map(function(element) {
if (typeof element !== 'string') {
return null;
}
return Utility.stringToNumber(element);
});
} else if (typeof obj === 'string') {
return Utility.stringToNumber(obj);
} else {
return null;
}
};
Utility.encodeId = function(str) {
return Utility.numberToString(str);
};
Utility.encodeResults = function(results, keys) {
var isSubArrayKey, replaceKeys, retVal;
replaceKeys = function(obj) {
if (obj === null) {
return null;
}
if (isSubArrayKey) {
keys.forEach(function(key) {
var subObj;
subObj = obj[key[0]];
if (subObj) {
if (subObj[key[1]]) {
return subObj[key[1]] = Utility.numberToString(subObj[key[1]]);
}
}
});
} else {
keys.forEach(function(key) {
if (obj[key]) {
return obj[key] = Utility.numberToString(obj[key]);
}
});
}
return obj;
};
if (results === null) {
return null;
}
if (results.toJSON) {
results = results.toJSON();
}
if (!keys) {
keys = 'id';
}
if (typeof keys === 'string') {
keys = [keys];
}
isSubArrayKey = keys.length > 0 && Array.isArray(keys[0]);
if (Array.isArray(results)) {
retVal = results.map(function(item) {
if (item.toJSON) {
item = item.toJSON();
}
return replaceKeys(item);
});
} else {
retVal = replaceKeys(results);
}
return retVal;
};
Utility.stringToNumber = function(str) {
try {
return this.n3d.decrypt(str);
} catch (_error) {
return null;
}
};
Utility.numberToString = function(num) {
try {
return this.n3d.encrypt(num);
} catch (_error) {
return null;
}
};
Utility.xss = function(str, maxLength) {
var result;
result = xss(str, {
whiteList: []
});
if (str.length <= maxLength) {
if (result.length > maxLength) {
return result.substr(0, maxLength);
}
}
return result;
};
return Utility;
})();
APIResult = (function() {
function APIResult(code, result1, message) {
this.code = code;
this.result = result1;
this.message = message;
if (this.code === null || this.code === void 0) {
throw new Error('Code is null.');
}
Utility.logResult(JSON.stringify(this));
if (this.result === null) {
delete this.result;
}
if (this.message === null || process.env.NODE_ENV !== 'development') {
delete this.message;
}
}
return APIResult;
})();
HTTPError = (function(superClass) {
extend(HTTPError, superClass);
function HTTPError(message, statusCode) {
this.message = message;
this.statusCode = statusCode;
}
return HTTPError;
})(Error);
module.exports.Utility = Utility;
module.exports.APIResult = APIResult;
module.exports.HTTPError = HTTPError;
var sequelize;
sequelize = require('./src/db')[0];
console.log('Drop all schemas.');
sequelize.drop();
console.log('Sync all schemas.');
sequelize.sync({
force: true
}).then(function() {
return console.log('All done!');
})["catch"](function(err) {
return console.log(err);
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment