浏览代码

修改工程结构,为患者端IM做准备

Sand 8 年之前
父节点
当前提交
051ea0758c
共有 42 个文件被更改,包括 3653 次插入919 次删除
  1. 2 1
      .gitignore
  2. 7 11
      readme.md
  3. 0 48
      routes/index.js
  4. 0 240
      routes/msg_group.js
  5. 0 199
      routes/msg_p2p.js
  6. 0 220
      routes/msg_statistic.js
  7. 0 111
      routes/msg_system.js
  8. 22 29
      app.js
  9. 0 0
      src/doctor/appArmour.js
  10. 1 1
      bin/spawn.script.js
  11. 10 0
      src/doctor/controllers/index.js
  12. 44 0
      src/doctor/controllers/management.js
  13. 793 0
      src/doctor/controllers/messages.js
  14. 3 3
      routes/user.js
  15. 0 0
      src/doctor/include/commons.js
  16. 32 0
      src/doctor/include/endpoints.js
  17. 3 3
      models/msg_group.js
  18. 3 3
      models/msg_p2p.js
  19. 16 16
      models/msg_statistic.js
  20. 2 2
      models/msg_system.js
  21. 1 1
      models/push_notify.js
  22. 11 11
      models/user.js
  23. 2 1
      package.json
  24. 二进制
      src/doctor/public/favicon.ico
  25. 3 3
      public/javascripts/sdk/im.client.js
  26. 0 0
      src/doctor/public/stylesheets/style.css
  27. 2 2
      repository/imRepo.js
  28. 2 2
      repository/wlyyRepo.js
  29. 0 0
      src/doctor/resources/config/config.dev.js
  30. 0 0
      src/doctor/resources/config/config.pro.js
  31. 0 0
      src/doctor/resources/config/config.test.js
  32. 0 0
      src/doctor/resources/schema/im_schema.sql
  33. 0 0
      src/doctor/resources/schema/talk_group_schema.sql
  34. 0 0
      src/doctor/util/dbUtil.js
  35. 0 0
      src/doctor/util/log.js
  36. 108 0
      src/doctor/util/mime.js
  37. 2586 0
      src/doctor/util/mime.types.json
  38. 0 0
      src/doctor/views/error.jade
  39. 0 0
      src/doctor/views/index.jade
  40. 0 0
      src/doctor/views/layout.jade
  41. 0 1
      views/error.html
  42. 0 11
      views/index.html

+ 2 - 1
.gitignore

@ -1,2 +1,3 @@
.idea/*
node_modules/*
src/doctor/node_modules/*
npm-debug.log

+ 7 - 11
readme.md

@ -12,11 +12,6 @@
- 数据库准备: IM服务器使用MySQL数据库,因此需要先安装MySQL数据库。然后执行resources/schema/im_schema.sql与resources/schema/talk_group_schema.sql脚本文件,创建数据模式。
  第一个创建消息存储所需要的模式,第二个创建业务讨论组数据模式,并包含演示数据。
## 部署
为保证服务正常运行,当服务异常退出时自动重启服务,IM服务器使用appArmour脚本,对进程添加保护:进程异常退出时,自动重启。
为实现此目标,部署时通过appArmour启动服务即可实现进程的守护功能。
## 工程结构
- 消息流: 首先要明白即时消息的流程,有助于理解整个过程。医生端的IM仅关注医生部分,即下图中蓝色标注的内容。
@ -27,13 +22,14 @@
https://my.oschina.net/antianlu/blog/190191
## 开发SDK
- 环境要求: IM提供了开发SDK,一个JS脚本。客户端可以通过引用此脚本或将此脚本打包到资源中。此外,脚本使用jQuery作为基础环境,因此客户端需要预先引用jQuery脚本。
- 开发流程: 通过以下链接引用SDK文件
## 部署
为保证服务正常运行,当服务异常退出时自动重启服务,IM服务器使用appArmour脚本,对进程添加保护:进程异常退出时,自动重启。
为实现此目标,部署时通过appArmour启动服务即可实现进程的守护功能。
    <script src='http://host:port/sdk/im.client.js'></script>
## 开发SDK
IM提供了开发SDK,一个JS脚本。客户端可以通过引用此脚本或将此脚本打包到资源中。此外,脚本使用jQuery作为基础环境,因此客户端需要预先引用jQuery脚本。
客户端通过以下链接引用SDK文件
SDK中封装了常用接口。
    <script src='http://host:port/sdk/im.client.js'></script>

+ 0 - 48
routes/index.js

@ -1,48 +0,0 @@
var express = require('express');
var router = express.Router();
var log = require('../util/log');
var mysql_wlyy = require("../repository/mysql_wlyy");
var mysql_im = require("../repository/mysql_im");
function getWlyyTables(handler) {
  mysql_wlyy.execQuery({
    "sql": "SELECT table_name FROM information_schema.TABLES",
    "args": [],
    "handler": handler
  });
}
function getImTables(handler) {
  mysql_im.execQuery({
    "sql": "SELECT table_name FROM information_schema.TABLES",
    "args": [],
    "handler": handler
  });
}
/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});
router.get('/wlyy_db.im', function(req, res, next) {
  getWlyyTables(function(err, result) {
    if (err) {
      res.send({errno: -1, errmsg: 'open wlyy db error'});
    } else {
      res.send({errno: 0, errmsg: 'open wlyy db success'});
    }
  });
});
router.get('/im_db.im', function(req, res, next) {
  getImTables(function(err, result) {
    if (err) {
      res.send({errno: -1, errmsg: 'open im db error'});
    } else {
      res.send({errno: 0, errmsg: 'open im db success'});
    }
  });
});
module.exports = router;

+ 0 - 240
routes/msg_group.js

@ -1,240 +0,0 @@
var express = require('express');
var router = express.Router();
var msg_group = require("../models/msg_group");
var msg_statistic = require("../models/msg_statistic");
var user = require("../models/user");
var push = require("../models/push_notify");
var http = require('http');
var qs = require('querystring');
var getui = require('getui');
/**
 * 群组消息:group/sendmsg.im?from_uid=x&to_gid=xx&content=xxx&type=1&at_uid=xxxx&group_type
 * 参数:
 * from_uid:发送者id
 * to_gid:群组id
 * content:消息内容
 * type:消息类型:1文本,2图片,3语音
 * at_uid:@用户id:1为行政组,null或者其他值为自定义组
 * group_type:区分是行政组还是自定义组,从不同表中查找组成员
 */
router.get('/sendmsg.im', function (req, res, next) {
    if (req.query.from_uid == null
        || req.query.to_gid == null
        || req.query.content == null
        || req.query.type == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_group.isGroupUser(req.query.from_uid, req.query.to_gid, req.query.group_type, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get users from db error'});
            return;
        }
        if (result.length == 0) {
            res.send({errno: 2, errmsg: 'no receive users'});
            return;
        }
        var at_uid = '';
        if (req.query.at_uid != null) {
            at_uid = req.query.at_uid;
        }
        // 保存群组消息
        msg_group.saveGroupMsg(req.query.from_uid, req.query.to_gid, at_uid, req.query.type, req.query.content, function (err, result) {
            if (err) {
                res.send({errno: 3, errmsg: 'save msg to db error'});
                return;
            }
            res.send({errno: 0, errmsg: 'send group msg success'});
            // 统计信息
            msg_statistic.updateGroupChatInfo(req.query.from_uid,
                req.query.to_gid,
                req.query.from_uid,
                0,
                req.query.type,
                req.query.content,
                false,
                function (err, result) {
                    if (err) {
                        console.log(err);
                    }
                });
            // 推送通知消息给群组各成员
            msg_group.getGroupUsers(req.query.to_gid, req.query.group_type, function (err, result) {
                if (err) {
                    console.log('get users from db error');
                    return;
                }
                if (result.length == 0) {
                    console.log('no receive users');
                    return;
                }
                // 推送通知
                for (var nIndex = 0; nIndex < result.length; nIndex++) {
                    if (result[nIndex].member_code == req.query.from_uid) {
                        continue;
                    }
                    (function () {
                        var toUserID = result[nIndex].member_code;
                        user.getUserbyID(toUserID, function (err, result) {
                            var tmp = toUserID;
                            if (err) {
                                console.log('group msg:get user by id from db failed');
                                return;
                            }
                            var title = '';
                            var content = '';
                            if (req.query.type == 1) {
                                title = '群组消息';
                                content = req.query.content;
                            } else if (req.query.type == 2) {
                                title = '群组消息';
                                content = '接收到 [图片]';
                            } else if (req.query.type == 3) {
                                title = '群组消息';
                                content = '接收到 [语音]';
                            } else {
                                title = '群组消息';
                                content = '接收到一条新消息';
                            }
                            var bMustPush = 0;
                            var data;
                            if (result.length > 0) {
                                data = result[0];
                                if (data.is_online) {
                                    bMustPush = 1;
                                }
                            }
                            var push_data = JSON.stringify({
                                type:'group_msg',
                                gid:req.query.to_gid
                            });
                            // 保存通知到数据库中
                            push.savePushNotify(toUserID,
                                req.query.type,
                                title,
                                content,
                                push_data,
                                bMustPush,
                                function (err, result) {
                                    if (err) {
                                        // 保存失败
                                        console.log('save msg to db failed');
                                    } else {
                                        console.log('save msg to db success');
                                        if (bMustPush == true) {
                                            if (data.platform == 0) {// iOS
                                                getui.pushAPN(toUserID,
                                                    data.token,
                                                    req.query.type,
                                                    title,
                                                    content,
                                                    push_data,
                                                    function (err, result) {
                                                        if (err != null) {
                                                            console.log(err);
                                                        } else {
                                                            console.log(result);
                                                        }
                                                    });
                                            } else {// Android
                                                getui.pushAndroid(data.client_id,
                                                    req.query.type,
                                                    title,
                                                    content,
                                                    push_data,
                                                    data.status,
                                                    function (err, result) {
                                                        if (err != null) {
                                                            console.log(err);
                                                        } else {
                                                            console.log(result);
                                                        }
                                                    });
                                            }
                                        }
                                    }
                                });
                        });
                        // 统计信息
                        var at_me = 0;
                        if (at_uid == toUserID) {
                            at_me = 1;
                        }
                        msg_statistic.updateGroupChatInfo(toUserID,
                            req.query.to_gid,
                            req.query.from_uid,
                            at_me,
                            req.query.type,
                            req.query.content,
                            true,
                            function (err, result) {
                                if (err) {
                                    console.log(err);
                                }
                            });
                    })();
                }
            });
        });
    });
});
/**
 * 群组消息:group/getmsg.im?uid=x&gid=xx&start=0&count=20
 * 参数:
 * uid:用户id
 * gid:群组id
 * start;分页查询起始条目
 * count:查询条数
 * 备注:按时间倒序
 */
router.get('/getmsg.im', function (req, res, next) {
    if (req.query.uid == null
        || req.query.gid == null
        || req.query.start == null
        || req.query.count == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_group.getGroupMsg(req.query.gid, req.query.start, req.query.count, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get group msg from db error'});
            return;
        }
        var data = {};
        data.start = parseInt(req.query.start);
        data.count = result.length;
        data.list = new Array();
        for (var nIndex = 0; nIndex < result.length; nIndex++) {
            result[nIndex].timestamp = Date.parse(new Date(result[nIndex].timestamp));
            data.list.push(result[nIndex]);
        }
        res.send(data);
        // 清空统计信息
        msg_statistic.clearGroupChatInfo(req.query.uid,
            req.query.gid,
            function (err, result) {
                if (err) {
                    console.log(err);
                }
            });
    });
});
module.exports = router;

+ 0 - 199
routes/msg_p2p.js

@ -1,199 +0,0 @@
var express = require('express');
var router = express.Router();
var msg_p2p = require("../models/msg_p2p");
var msg_statistic = require("../models/msg_statistic");
var user = require("../models/user");
var push = require("../models/push_notify");
var getui = require('getui');
/**
 * 一对一消息:p2p/sendmsg.im?from_uid=x&to_uid=xx&content=xxx&type=1
 * 参数:
 * from_uid:发送者id
 * to_uid:接收者id
 * content:消息内容
 * type:消息类型:1文本,2图片,3语音
 */
router.get('/sendmsg.im', function (req, res, next) {
    if (req.query.from_uid == null
        || req.query.to_uid == null
        || req.query.content == null
        || req.query.type == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_p2p.isUserExist(req.query.to_uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get users from db error'});
            return;
        }
        if (result.length == 0) {
            res.send({errno: 2, errmsg: 'no receive users'});
            return;
        }
        // 保存一对一消息
        msg_p2p.saveP2PMsg(req.query.to_uid, req.query.from_uid, req.query.type, req.query.content, function (err, result) {
            if (err) {
                res.send({errno: 3, errmsg: 'save msg to db error'});
                return;
            }
            res.send({errno: 0, errmsg: 'send p2p msg success'});
            // 更新自身的聊天统计信息
            msg_statistic.updateP2PChatInfo(req.query.from_uid,
                req.query.to_uid,
                req.query.from_uid,
                req.query.type,
                req.query.content,
                function (err, result) {
                    if (err) {
                        console.log(err);
                    }
                });
            // 更新对端的聊天统计信息
            msg_statistic.updateP2PChatInfo(req.query.to_uid,
                req.query.from_uid,
                req.query.from_uid,
                req.query.type,
                req.query.content,
                function (err, result) {
                    if (err) {
                        console.log(err);
                    }
                });
            // 推送通知消息给对端
            user.getUserbyID(req.query.to_uid, function (err, result) {
                if (err) {
                    console.log('group msg:get user by id from db failed');
                    return;
                }
                var title = '';
                var content = '';
                if (req.query.type == 1) {
                    title = '新消息';
                    content = req.query.content;
                } else if (req.query.type == 2) {
                    title = '新消息';
                    content = '接收到 [图片]';
                } else if (req.query.type == 3) {
                    title = '新消息';
                    content = '接收到 [语音]';
                } else {
                    title = '新消息';
                    content = '接收到一条新消息';
                }
                var bMustPush = 0;
                var data;
                if (result.length > 0) {
                    data = result[0];
                    if (data.is_online) {
                        bMustPush = 1;
                    }
                }
                var push_data = JSON.stringify({
                    type:'p2p_msg',
                    from_uid:req.query.from_uid
                });
                // 保存通知到数据库中
                push.savePushNotify(req.query.to_uid,
                    req.query.type,
                    title,
                    content,
                    push_data,
                    bMustPush,
                    function (err, result) {
                        if (err) {
                            // 保存失败
                            console.log('save msg to db failed');
                        } else {
                            console.log('save msg to db success');
                            if (bMustPush == true) {
                                if (data.platform == 0) {// iOS
                                    getui.pushAPN(req.query.to_uid,
                                        data.token,
                                        req.query.type,
                                        title,
                                        content,
                                        push_data,
                                        function (err, result) {
                                            if (err != null) {
                                                console.log(err);
                                            } else {
                                                console.log(result);
                                            }
                                        });
                                } else {// Android
                                    getui.pushAndroid(data.client_id,
                                        req.query.type,
                                        title,
                                        content,
                                        push_data,
                                        data.status,
                                        function (err, result) {
                                            if (err != null) {
                                                console.log(err);
                                            } else {
                                                console.log(result);
                                            }
                                        });
                                }
                            }
                        }
                    });
            });
        });
    });
});
/**
 * 一对一消息:p2p/getmsg.im?uid=x&peer_uid=xx&start=0&count=20
 * 参数:
 * uid:用户id
 * peer_uid:对端id
 * start;分页查询起始条目
 * count:查询条数
 * 备注:按时间倒序
 */
router.get('/getmsg.im', function (req, res, next) {
    if (req.query.uid == null
        || req.query.peer_uid == null
        || req.query.start == null
        || req.query.count == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_p2p.getP2PMsg(req.query.uid, req.query.peer_uid, req.query.start, req.query.count, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get group msg from db error'});
            return;
        }
        var data = {};
        data.start = parseInt(req.query.start);
        data.count = result.length;
        data.list = new Array();
        for (var nIndex = 0; nIndex < result.length; nIndex++) {
            result[nIndex].timestamp = Date.parse(new Date(result[nIndex].timestamp));
            data.list.push(result[nIndex]);
        }
        res.send(data);
        // 清空统计信息
        msg_statistic.clearP2PChatInfo(req.query.uid,
            req.query.peer_uid,
            function (err, result) {
                if (err) {
                    console.log(err);
                }
            });
    });
});
module.exports = router;

+ 0 - 220
routes/msg_statistic.js

@ -1,220 +0,0 @@
var express = require('express');
var router = express.Router();
var msg_statistic = require("../models/msg_statistic");
/**
 * 群组消息统计:statistic/getgroupchatinfo.im?uid=x&gid=xx
 * 参数:
 * uid:信息所有者id
 * gid:群组id
 */
router.get('/getgroupchatinfo.im', function (req, res, next) {
    if (req.query.uid == null
        || req.query.gid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_statistic.getGroupChatInfo(req.query.uid, req.query.gid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        if (result.length == 0) {
            var data = {"uid":req.query.uid,"from_uid":"","from_gid":req.query.gid,"at_me":0,"last_content_type":1,"last_content":"","new_msg_count":0,"timestamp":0};
            res.send(data);
            return;
        }
        result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        res.send(result[0]);
    });
});
/**
 * 一对一聊天消息统计:statistic/getp2pchatinfo.im?uid=x&peer_uid=xx
 * 参数:
 * uid:信息所有者id
 * peer_uid:聊天对端id
 */
router.get('/getp2pchatinfo.im', function (req, res, next) {
    if (req.query.uid == null
        || req.query.peer_uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_statistic.getP2PChatInfo(req.query.uid, req.query.peer_uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        if (result.length == 0) {
            var data = {"uid":req.query.uid,"from_uid":req.query.peer_uid,"last_content_type":1,"last_content":"","new_msg_count":0,"timestamp":0};
            res.send(data);
            return;
        }
        result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        res.send(result[0]);
    });
});
/**
 * 获取聊天列表:statistic/getchatlist.im?uid=x
 * 参数:
 * uid:信息所有者id
 */
router.get('/getchatlist.im', function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_statistic.getChatList(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        if (result.length == 0) {
            var data = {"uid":req.query.uid,"from_uid":req.query.peer_uid,"last_content_type":1,"last_content":"","new_msg_count":0,"timestamp":0};
            res.send(data);
            return;
        }
        //result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        for (var index = 0; index < result.length; index++) {
            result[index].timestamp = Date.parse(new Date(result[index].timestamp));
        }
        res.send(result);
    });
});
/**
 * 群组聊天消息所有未读数:statistic/getgroupunreadcount.im?uid=x
 * 参数:
 * uid:信息所有者id
 */
router.get('/getgroupunreadcount.im', function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_statistic.getGroupChatAllUnRead(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        var data = {"uid":req.query.uid,"msg_type":2,"new_msg_count":0};
        if (result.length == 0) {
            res.send(data);
            return;
        }
        var count = 0;
        var index = 0;
        var length = result.length;
        for (; index < length; index++) {
            count += result[index].new_msg_count;
        }
        data.new_msg_count = count;
        res.send(data);
    });
});
/**
 * 一对一聊天消息所有未读数:statistic/getp2punreadcount.im?uid=x
 * 参数:
 * uid:信息所有者id
 */
router.get('/getp2punreadcount.im', function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_statistic.getP2PChatAllUnRead(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        var data = {"uid":req.query.uid,"msg_type":1,"new_msg_count":0};
        if (result.length == 0) {
            res.send(data);
            return;
        }
        var count = 0;
        var index = 0;
        var length = result.length;
        for (; index < length; index++) {
            count += result[index].new_msg_count;
        }
        data.new_msg_count = count;
        res.send(data);
    });
});
/**
 * 所有聊天消息未读数:statistic/getallunreadmsgcount.im?uid=x
 * 参数:
 * uid:信息所有者id
 */
router.get('/getallunreadmsgcount.im', function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_statistic.getChatAllUnRead(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        var data = {"uid":req.query.uid,"msg_type":0,"new_msg_count":0};
        if (result.length == 0) {
            res.send(data);
            return;
        }
        var count = 0;
        var index = 0;
        var length = result.length;
        for (; index < length; index++) {
            count += result[index].new_msg_count;
        }
        data.new_msg_count = count;
        res.send(data);
    });
});
/**
 * 获取角标数:statistic/getbadgenum.im?uid=x
 * 参数:
 * uid:用户id
 */
router.get('/getbadgenum.im', function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    msg_statistic.getBadgeNumber(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get badge error'});
            return;
        }
        var data = {"uid":req.query.uid,"badge":result};
        res.send(data);
    });
});
module.exports = router;

+ 0 - 111
routes/msg_system.js

@ -1,111 +0,0 @@
var express = require('express');
var router = express.Router();
var user = require("../models/user");
var msg_system = require("../models/msg_system");
var push = require("../models/push_notify");
var http = require('http');
var qs = require('querystring');
var getui = require('getui');
/**
 * 推送消息:system/sendmsg.im?to_uid=x&type=xx&title=xxx&content=xxxx&data=xxxxx
 * 参数:
 * to_uid:消息接收者ID
 * type:消息类型
 * title:消息标题
 * content:推送消息提示内容
 * data:推送消息内容
 */
router.get('/sendmsg.im', function (req, res, next) {
    if (req.query.to_uid == null
        || req.query.title == null
        || req.query.type == null
        || req.query.content == null
        || req.query.data == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    user.getUserbyID(req.query.to_uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get user by id from db failed'});
            return;
        }
        var bMustPush = 0;
        var data;
        if (result.length > 0) {
            data = result[0];
            if (data.is_online) {
                bMustPush = 1;
            }
        }
        var push_data = JSON.stringify({
            type:req.query.type,
            data:req.query.data
        });
        // 保存该条推送信息
        msg_system.saveSystemMsg(req.query.to_uid,
            req.query.type,
            req.query.title,
            req.query.content,
            req.query.data,
            function (err, result) {
                if (err) {
                    // 保存失败
                    res.send({errno: 2, errmsg: 'save msg to db failed'});
                } else {
                    // 保存通知到数据库中
                    push.savePushNotify(req.query.to_uid,
                        req.query.type,
                        req.query.title,
                        req.query.content,
                        push_data,
                        bMustPush,
                        function (err, result) {
                            if (err) {
                                // 保存失败
                                res.send({errno: 3, errmsg: 'save msg to db failed'});
                            } else {
                                res.send({errno: 0, errmsg: 'save msg to db success'});
                                if (bMustPush == true) {
                                    if (data.platform == 0) {// iOS
                                        getui.pushAPN(req.query.to_uid,
                                            data.token,
                                            req.query.type,
                                            req.query.title,
                                            req.query.content,
                                            push_data,
                                            function (err, result) {
                                                if (err != null) {
                                                    console.log(err);
                                                } else {
                                                    console.log(result);
                                                }
                                            });
                                    } else {// Android
                                        getui.pushAndroid(data.client_id,
                                            req.query.type,
                                            req.query.title,
                                            req.query.content,
                                            push_data,
                                            data.status,
                                            function (err, result) {
                                                if (err != null) {
                                                    console.log(err);
                                                } else {
                                                    console.log(result);
                                                }
                                            });
                                    }
                                }
                            }
                        });
                }
            });
    });
});
module.exports = router;

+ 22 - 29
app.js

@ -1,3 +1,4 @@
// general dependencies
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
@ -5,56 +6,49 @@ var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var user = require('./routes/user');
var msg_group = require('./routes/msg_group');
var msg_p2p = require('./routes/msg_p2p');
var msg_system = require('./routes/msg_system');
var msg_statistic = require('./routes/msg_statistic');
// server config dependencies
var endpoints = require('./include/endpoints');
var config = require('./include/commons');
var commons = require('./include/commons');
var config = require(commons.CONFIG_FILE);
// routers dependencies
var routes = require('./controllers/index');
var users = require('./controllers/users');
var messages = require('./controllers/messages');
var management = require('./controllers/management');
var app = express();
var startDate = new Date();
console.log('==============================================');
console.log('');
console.log('Push Server started on ' + startDate.toLocaleString());
console.log('Version: ' + config.version);
console.log(new Date().toLocaleString() + ': Starting IM server, version ' + config.version);
console.log('');
console.log('==============================================');
// view engine setup
// Application entry point
var app = express();
// Setup view engine as Jade
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(favicon(__dirname + '/public/favicon.ico', null));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes);
app.use('/user', user);
app.use('/group', msg_group);
app.use('/p2p', msg_p2p);
app.use('/system', msg_system);
app.use('/statistic', msg_statistic);
app.use(endpoints.Home.Base, routes);
app.use(endpoints.Msg.Base, messages);
app.use(endpoints.Users.Base, users);
app.use(endpoints.Management.Base, management);
// catch 404 and forward to error handler
// 捕捉404错误,并转发至错误处理器
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});
// error handlers
// development error handler
// will print stacktrace
// In development mode, error handler will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
@ -65,8 +59,7 @@ if (app.get('env') === 'development') {
  });
}
// production error handler
// no stacktraces leaked to user
// In production mode, error handler no stack traces leaked to users
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {

appArmour.js → src/doctor/appArmour.js


+ 1 - 1
bin/spawn.script.js

@ -3,7 +3,7 @@ var app = require('../app');
var debug = require('debug')('im.doctor:server');
var http = require('http');
var commons = require('./include/commons');
var commons = require('../include/commons');
var config = require(commons.CONFIG_FILE);
/**

+ 10 - 0
src/doctor/controllers/index.js

@ -0,0 +1,10 @@
var express = require('express');
var router = express.Router();
var endpoints = require('../include/endpoints');
router.get(endpoints.Home.Base, function(req, res) {
  res.render('index', { title: 'IM.Doctor' });
});
module.exports = router;

+ 44 - 0
src/doctor/controllers/management.js

@ -0,0 +1,44 @@
var express = require('express');
var router = express.Router();
var commons = require('../include/commons');
var wlyyRepo = require("../repository/wlyy.repo");
var imRepo = require("../repository/im.repo");
function getWlyyTables(handler) {
    wlyyRepo.execQuery({
        "sql": "SELECT table_name FROM information_schema.TABLES",
        "args": [],
        "handler": handler
    });
}
function getImTables(handler) {
    imRepo.execQuery({
        "sql": "SELECT table_name FROM information_schema.TABLES",
        "args": [],
        "handler": handler
    });
}
/**
 * 数据库检查,包括所有表,连接状态。
 */
router.get(commons.END_POINT.Management.DbStatus, function(req, res, next) {
    getWlyyTables(function(err, result) {
        if (err) {
            res.send({errno: -1, errmsg: 'open wlyy db error'});
            getImTables(function(err, result) {
                if (err) {
                    res.send({errno: -1, errmsg: 'open im db error'});
                } else {
                    res.send({errno: 0, errmsg: 'open im db success'});
                }
            });
        } else {
            res.send({errno: 0, errmsg: 'open wlyy db success'});
        }
    });
});
module.exports = router;

+ 793 - 0
src/doctor/controllers/messages.js

@ -0,0 +1,793 @@
/**
 * 消息控制器。
 *
 * 此控制器处理点对点,组及消息消息。为三类消息提供发送及查询功能。
 */
var http = require('http');
var qs = require('querystring');
var getui = require('getui');
var endpoints = require('../include/endpoints');
var express = require('express');
var router = express.Router();
var groupMsg = require("../models/msg.group");
var privateMsg = require('../models/msg.p2p');
var StatMsg = require("../models/msg.stat");
var systemMsg = require("../models/msg.system");
var user = require("../models/user");
var push = require("../models/push_notify");
/**
 * 发送P2P消息。
 *
 * from_uid=x&to_uid=xx&content=xxx&type=1
 *
 * 参数:
 * from_uid:发送者id
 * to_uid:接收者id
 * content:消息内容
 * type:消息类型:1文本,2图片,3语音
 */
router.post(endpoints.Msg.Privates, function (req, res, next) {
    if (req.query.from_uid == null
        || req.query.to_uid == null
        || req.query.content == null
        || req.query.type == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    privateMsg.isUserExist(req.query.to_uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get users from db error'});
            return;
        }
        if (result.length == 0) {
            res.send({errno: 2, errmsg: 'no receive users'});
            return;
        }
        // 保存一对一消息
        privateMsg.saveP2PMsg(req.query.to_uid, req.query.from_uid, req.query.type, req.query.content, function (err, result) {
            if (err) {
                res.send({errno: 3, errmsg: 'save msg to db error'});
                return;
            }
            res.send({errno: 0, errmsg: 'send p2p msg success'});
            // 更新自身的聊天统计信息
            StatMsg.updateP2PChatInfo(req.query.from_uid,
                req.query.to_uid,
                req.query.from_uid,
                req.query.type,
                req.query.content,
                function (err, result) {
                    if (err) {
                        console.log(err);
                    }
                });
            // 更新对端的聊天统计信息
            StatMsg.updateP2PChatInfo(req.query.to_uid,
                req.query.from_uid,
                req.query.from_uid,
                req.query.type,
                req.query.content,
                function (err, result) {
                    if (err) {
                        console.log(err);
                    }
                });
            // 推送通知消息给对端
            user.getUserbyID(req.query.to_uid, function (err, result) {
                if (err) {
                    console.log('group msg:get users by id from db failed');
                    return;
                }
                var title = '';
                var content = '';
                if (req.query.type == 1) {
                    title = '新消息';
                    content = req.query.content;
                } else if (req.query.type == 2) {
                    title = '新消息';
                    content = '接收到 [图片]';
                } else if (req.query.type == 3) {
                    title = '新消息';
                    content = '接收到 [语音]';
                } else {
                    title = '新消息';
                    content = '接收到一条新消息';
                }
                var bMustPush = 0;
                var data;
                if (result.length > 0) {
                    data = result[0];
                    if (data.is_online) {
                        bMustPush = 1;
                    }
                }
                var push_data = JSON.stringify({
                    type: 'p2p_msg',
                    from_uid: req.query.from_uid
                });
                // 保存通知到数据库中
                push.savePushNotify(req.query.to_uid,
                    req.query.type,
                    title,
                    content,
                    push_data,
                    bMustPush,
                    function (err, result) {
                        if (err) {
                            // 保存失败
                            console.log('save msg to db failed');
                        } else {
                            console.log('save msg to db success');
                            if (bMustPush == true) {
                                if (data.platform == 0) {// iOS
                                    getui.pushAPN(req.query.to_uid,
                                        data.token,
                                        req.query.type,
                                        title,
                                        content,
                                        push_data,
                                        function (err, result) {
                                            if (err != null) {
                                                console.log(err);
                                            } else {
                                                console.log(result);
                                            }
                                        });
                                } else {// Android
                                    getui.pushAndroid(data.client_id,
                                        req.query.type,
                                        title,
                                        content,
                                        push_data,
                                        data.status,
                                        function (err, result) {
                                            if (err != null) {
                                                console.log(err);
                                            } else {
                                                console.log(result);
                                            }
                                        });
                                }
                            }
                        }
                    });
            });
        });
    });
});
/**
 * 获取P2P消息。
 *
 * p2p/getmsg.im?uid=x&peer_uid=xx&start=0&count=20
 *
 * 参数:
 * uid:用户id
 * peer_uid:对端id
 * start;分页查询起始条目
 * count:查询条数
 * 备注:按时间倒序
 */
router.get(endpoints.Msg.Privates, function (req, res, next) {
    if (req.query.uid == null
        || req.query.peer_uid == null
        || req.query.start == null
        || req.query.count == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    privateMsg.getP2PMsg(req.query.uid, req.query.peer_uid, req.query.start, req.query.count, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get group msg from db error'});
            return;
        }
        var data = {};
        data.start = parseInt(req.query.start);
        data.count = result.length;
        data.list = new Array();
        for (var nIndex = 0; nIndex < result.length; nIndex++) {
            result[nIndex].timestamp = Date.parse(new Date(result[nIndex].timestamp));
            data.list.push(result[nIndex]);
        }
        res.send(data);
        // 清空统计信息
        StatMsg.clearP2PChatInfo(req.query.uid,
            req.query.peer_uid,
            function (err, result) {
                if (err) {
                    console.log(err);
                }
            });
    });
});
/**
 * 发送群组消息。
 *
 * group/sendmsg.im?from_uid=x&to_gid=xx&content=xxx&type=1&at_uid=xxxx&group_type
 *
 * 参数:
 * from_uid:发送者id
 * to_gid:群组id
 * content:消息内容
 * type:消息类型:1文本,2图片,3语音
 * at_uid:@用户id:1为行政组,null或者其他值为自定义组
 * group_type:区分是行政组还是自定义组,从不同表中查找组成员
 */
router.post(endpoints.Msg.Groups, function (req, res, next) {
    if (req.query.from_uid == null
        || req.query.to_gid == null
        || req.query.content == null
        || req.query.type == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    groupMsg.isGroupUser(req.query.from_uid, req.query.to_gid, req.query.group_type, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get users from db error'});
            return;
        }
        if (result.length == 0) {
            res.send({errno: 2, errmsg: 'no receive users'});
            return;
        }
        var at_uid = '';
        if (req.query.at_uid != null) {
            at_uid = req.query.at_uid;
        }
        // 保存群组消息
        groupMsg.saveGroupMsg(req.query.from_uid, req.query.to_gid, at_uid, req.query.type, req.query.content, function (err, result) {
            if (err) {
                res.send({errno: 3, errmsg: 'save msg to db error'});
                return;
            }
            res.send({errno: 0, errmsg: 'send group msg success'});
            // 统计信息
            StatMsg.updateGroupChatInfo(req.query.from_uid,
                req.query.to_gid,
                req.query.from_uid,
                0,
                req.query.type,
                req.query.content,
                false,
                function (err, result) {
                    if (err) {
                        console.log(err);
                    }
                });
            // 推送通知消息给群组各成员
            groupMsg.getGroupUsers(req.query.to_gid, req.query.group_type, function (err, result) {
                if (err) {
                    console.log('get users from db error');
                    return;
                }
                if (result.length == 0) {
                    console.log('no receive users');
                    return;
                }
                // 推送通知
                for (var nIndex = 0; nIndex < result.length; nIndex++) {
                    if (result[nIndex].member_code == req.query.from_uid) {
                        continue;
                    }
                    (function () {
                        var toUserID = result[nIndex].member_code;
                        user.getUserbyID(toUserID, function (err, result) {
                            var tmp = toUserID;
                            if (err) {
                                console.log('group msg:get users by id from db failed');
                                return;
                            }
                            var title = '';
                            var content = '';
                            if (req.query.type == 1) {
                                title = '群组消息';
                                content = req.query.content;
                            } else if (req.query.type == 2) {
                                title = '群组消息';
                                content = '接收到 [图片]';
                            } else if (req.query.type == 3) {
                                title = '群组消息';
                                content = '接收到 [语音]';
                            } else {
                                title = '群组消息';
                                content = '接收到一条新消息';
                            }
                            var bMustPush = 0;
                            var data;
                            if (result.length > 0) {
                                data = result[0];
                                if (data.is_online) {
                                    bMustPush = 1;
                                }
                            }
                            var push_data = JSON.stringify({
                                type: 'group_msg',
                                gid: req.query.to_gid
                            });
                            // 保存通知到数据库中
                            push.savePushNotify(toUserID,
                                req.query.type,
                                title,
                                content,
                                push_data,
                                bMustPush,
                                function (err, result) {
                                    if (err) {
                                        // 保存失败
                                        console.log('save msg to db failed');
                                    } else {
                                        console.log('save msg to db success');
                                        if (bMustPush == true) {
                                            if (data.platform == 0) {// iOS
                                                getui.pushAPN(toUserID,
                                                    data.token,
                                                    req.query.type,
                                                    title,
                                                    content,
                                                    push_data,
                                                    function (err, result) {
                                                        if (err != null) {
                                                            console.log(err);
                                                        } else {
                                                            console.log(result);
                                                        }
                                                    });
                                            } else {// Android
                                                getui.pushAndroid(data.client_id,
                                                    req.query.type,
                                                    title,
                                                    content,
                                                    push_data,
                                                    data.status,
                                                    function (err, result) {
                                                        if (err != null) {
                                                            console.log(err);
                                                        } else {
                                                            console.log(result);
                                                        }
                                                    });
                                            }
                                        }
                                    }
                                });
                        });
                        // 统计信息
                        var at_me = 0;
                        if (at_uid == toUserID) {
                            at_me = 1;
                        }
                        StatMsg.updateGroupChatInfo(toUserID,
                            req.query.to_gid,
                            req.query.from_uid,
                            at_me,
                            req.query.type,
                            req.query.content,
                            true,
                            function (err, result) {
                                if (err) {
                                    console.log(err);
                                }
                            });
                    })();
                }
            });
        });
    });
});
/**
 * 读取群组消息。
 *
 * :group/getmsg.im?uid=x&gid=xx&start=0&count=20
 *
 * 参数:
 * uid:用户id
 * gid:群组id
 * start;分页查询起始条目
 * count:查询条数
 * 备注:按时间倒序
 */
router.get(endpoints.Msg.Groups, function (req, res, next) {
    if (req.query.uid == null
        || req.query.gid == null
        || req.query.start == null
        || req.query.count == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    groupMsg.getGroupMsg(req.query.gid, req.query.start, req.query.count, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get group msg from db error'});
            return;
        }
        var data = {};
        data.start = parseInt(req.query.start);
        data.count = result.length;
        data.list = new Array();
        for (var nIndex = 0; nIndex < result.length; nIndex++) {
            result[nIndex].timestamp = Date.parse(new Date(result[nIndex].timestamp));
            data.list.push(result[nIndex]);
        }
        res.send(data);
        // 清空统计信息
        StatMsg.clearGroupChatInfo(req.query.uid,
            req.query.gid,
            function (err, result) {
                if (err) {
                    console.log(err);
                }
            });
    });
});
/**
 * 发送系统消息。
 *
 * system/sendmsg.im?to_uid=x&type=xx&title=xxx&content=xxxx&data=xxxxx
 *
 * 参数:
 * to_uid:消息接收者ID
 * type:消息类型
 * title:消息标题
 * content:推送消息提示内容
 * data:推送消息内容
 */
router.get(endpoints.Msg.System, function (req, res, next) {
    if (req.query.to_uid == null
        || req.query.title == null
        || req.query.type == null
        || req.query.content == null
        || req.query.data == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    user.getUserbyID(req.query.to_uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get users by id from db failed'});
            return;
        }
        var bMustPush = 0;
        var data;
        if (result.length > 0) {
            data = result[0];
            if (data.is_online) {
                bMustPush = 1;
            }
        }
        var push_data = JSON.stringify({
            type: req.query.type,
            data: req.query.data
        });
        // 保存该条推送信息
        systemMsg.saveSystemMsg(req.query.to_uid,
            req.query.type,
            req.query.title,
            req.query.content,
            req.query.data,
            function (err, result) {
                if (err) {
                    // 保存失败
                    res.send({errno: 2, errmsg: 'save msg to db failed'});
                } else {
                    // 保存通知到数据库中
                    push.savePushNotify(req.query.to_uid,
                        req.query.type,
                        req.query.title,
                        req.query.content,
                        push_data,
                        bMustPush,
                        function (err, result) {
                            if (err) {
                                // 保存失败
                                res.send({errno: 3, errmsg: 'save msg to db failed'});
                            } else {
                                res.send({errno: 0, errmsg: 'save msg to db success'});
                                if (bMustPush == true) {
                                    if (data.platform == 0) {// iOS
                                        getui.pushAPN(req.query.to_uid,
                                            data.token,
                                            req.query.type,
                                            req.query.title,
                                            req.query.content,
                                            push_data,
                                            function (err, result) {
                                                if (err != null) {
                                                    console.log(err);
                                                } else {
                                                    console.log(result);
                                                }
                                            });
                                    } else {// Android
                                        getui.pushAndroid(data.client_id,
                                            req.query.type,
                                            req.query.title,
                                            req.query.content,
                                            push_data,
                                            data.status,
                                            function (err, result) {
                                                if (err != null) {
                                                    console.log(err);
                                                } else {
                                                    console.log(result);
                                                }
                                            });
                                    }
                                }
                            }
                        });
                }
            });
    });
});
/**
 * 获取群组消息统计。
 *
 * statistic/getgroupchatinfo.im?uid=x&gid=xx
 *
 * 参数:
 * uid:信息所有者id
 * gid:群组id
 */
router.get(endpoints.Msg.GroupStat, function (req, res, next) {
    if (req.query.uid == null
        || req.query.gid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getGroupChatInfo(req.query.uid, req.query.gid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        if (result.length == 0) {
            var data = {"uid":req.query.uid,"from_uid":"","from_gid":req.query.gid,"at_me":0,"last_content_type":1,"last_content":"","new_msg_count":0,"timestamp":0};
            res.send(data);
            return;
        }
        result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        res.send(result[0]);
    });
});
/**
 * 获取点对点消息统计。
 *
 * statistic/getp2pchatinfo.im?uid=x&peer_uid=xx
 *
 * 参数:
 * uid:信息所有者id
 * peer_uid:聊天对端id
 */
router.get(endpoints.Msg.PrivateStat, function (req, res, next) {
    if (req.query.uid == null
        || req.query.peer_uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getP2PChatInfo(req.query.uid, req.query.peer_uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        if (result.length == 0) {
            var data = {"uid":req.query.uid,"from_uid":req.query.peer_uid,"last_content_type":1,"last_content":"","new_msg_count":0,"timestamp":0};
            res.send(data);
            return;
        }
        result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        res.send(result[0]);
    });
});
/**
 * 获取参与的聊天列表,包括:点对点,@我,参与的讨论组等。
 *
 * statistic/getchatlist.im?uid=x
 *
 * 参数:
 * uid:信息所有者id
 */
router.get(endpoints.Users.ChatList, function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getChatList(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        if (result.length == 0) {
            var data = {"uid":req.query.uid,"from_uid":req.query.peer_uid,"last_content_type":1,"last_content":"","new_msg_count":0,"timestamp":0};
            res.send(data);
            return;
        }
        //result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        for (var index = 0; index < result.length; index++) {
            result[index].timestamp = Date.parse(new Date(result[index].timestamp));
        }
        res.send(result);
    });
});
/**
 * 群组聊天消息所有未读数。
 *
 * statistic/getgroupunreadcount.im?uid=x
 *
 * 参数:
 * uid:信息所有者id
 */
router.get(endpoints.Msg.Users.GroupUnreadMsgCount, function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getGroupChatAllUnRead(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        var data = {"uid":req.query.uid,"msg_type":2,"new_msg_count":0};
        if (result.length == 0) {
            res.send(data);
            return;
        }
        var count = 0;
        var index = 0;
        var length = result.length;
        for (; index < length; index++) {
            count += result[index].new_msg_count;
        }
        data.new_msg_count = count;
        res.send(data);
    });
});
/**
 * 一对一聊天消息所有未读数。
 *
 * statistic/getp2punreadcount.im?uid=x
 *
 * 参数:
 * uid:信息所有者id
 */
router.get(endpoints.Msg.Users.PrivateUnreadMsgCount, function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getP2PChatAllUnRead(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        var data = {"uid":req.query.uid,"msg_type":1,"new_msg_count":0};
        if (result.length == 0) {
            res.send(data);
            return;
        }
        var count = 0;
        var index = 0;
        var length = result.length;
        for (; index < length; index++) {
            count += result[index].new_msg_count;
        }
        data.new_msg_count = count;
        res.send(data);
    });
});
/**
 * 所有聊天消息未读数。
 *
 * statistic/getallunreadmsgcount.im?uid=x
 *
 * 参数:
 * uid:信息所有者id
 */
router.get(endpoints.Msg.Users.UnreadMsgCount, function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getChatAllUnRead(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        var data = {"uid":req.query.uid,"msg_type":0,"new_msg_count":0};
        if (result.length == 0) {
            res.send(data);
            return;
        }
        var count = 0;
        var index = 0;
        var length = result.length;
        for (; index < length; index++) {
            count += result[index].new_msg_count;
        }
        data.new_msg_count = count;
        res.send(data);
    });
});
/**
 * 获取角标数。
 *
 * statistic/getbadgenum.im?uid=x
 *
 * 参数:
 * uid:用户id
 */
router.get('/getbadgenum.im', function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getBadgeNumber(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get badge error'});
            return;
        }
        var data = {"uid":req.query.uid,"badge":result};
        res.send(data);
    });
});
module.exports = router;

+ 3 - 3
routes/user.js

@ -5,7 +5,7 @@ var http = require('http');
var qs = require('querystring');
/**
 * 登录:/user/login.im?user_id=x&token=xx&client_id=xxx&platform=0
 * 登录:/users/login.im?user_id=x&token=xx&client_id=xxx&platform=0
 * 参数:
 * user_id:用户ID
 * token:个推的token
@ -43,7 +43,7 @@ router.get('/login.im', function (req, res, next) {
});
/**
 * 登出:/user/logout.im?user_id=x
 * 登出:/users/logout.im?user_id=x
 * 参数:
 * user_id:用户ID
 */
@ -65,7 +65,7 @@ router.get('/logout.im', function (req, res, next) {
});
/**
 * 更新app状态:/user/updatestatus.im?user_id=x&status=1
 * 更新app状态:/users/updatestatus.im?user_id=x&status=1
 * 参数:
 * user_id:用户ID
 * status:App状态,0在后台,1在前台

include/commons.js → src/doctor/include/commons.js


+ 32 - 0
src/doctor/include/endpoints.js

@ -0,0 +1,32 @@
var endpoints = {
    "Home": {
        "Base": '/'
    },
    Msg: {
        Base: "/message",
        Privates: '/privates',
        PrivateStat: '/privates/stat',
        Groups: '/groups',
        GroupStat: '/groups/stat',
        System: '/system',
        BadgeNo: '/badge_no'
    },
    Users: {
        Base: '/users',
        User: '/users/:user_id',
        ChatList: '/users/:user_id/chat_list',
        GroupUnreadMsgCount: '/users/:user_id/groups/:group_id/unread_count',
        PrivateUnreadMsgCount: '/users/:user_id/private/unread_count',
        UnreadMsgCount: '/users/:user_id/message/unread_count'
    },
    Management: {
        Base: '/management',
        DbStatus: "/db"
    }
};
exports.END_POINTS = endpoints;

+ 3 - 3
models/msg_group.js

@ -2,7 +2,7 @@
var log = require('../util/log');
var mysql_wlyy = require("../repository/mysql_wlyy");
var mysql_im = require("../repository/mysql_im");
var mysql_im = require("../repository/imRepo");
function isGroupUser(user_id, group_id, group_type, handler) {
	if (group_type == 1) {
@ -38,14 +38,14 @@ function getGroupUsers(group_id, group_type, handler) {
function saveGroupMsg(user_id, group_id, at_uid, type, content, handler) {
	mysql_im.execQuery({
		"sql": "INSERT INTO msg_group (to_gid,from_uid,at_uid,type,content) VALUES (?,?,?,?,?)",
		"sql": "INSERT INTO groups (to_gid,from_uid,at_uid,type,content) VALUES (?,?,?,?,?)",
		"args": [group_id, user_id, at_uid, type, content],
		"handler": handler
	});
}
function getGroupMsg(group_id, start, count, handler) {
	var sql = "SELECT from_uid,at_uid,type,content,timestamp from msg_group WHERE to_gid = ? GROUP BY timestamp DESC LIMIT ";
	var sql = "SELECT from_uid,at_uid,type,content,timestamp from groups WHERE to_gid = ? GROUP BY timestamp DESC LIMIT ";
	sql += start;
	sql += ",";
	sql += count;

+ 3 - 3
models/msg_p2p.js

@ -2,7 +2,7 @@
var log = require('../util/log');
var mysql_wlyy = require("../repository/mysql_wlyy");
var mysql_im = require("../repository/mysql_im");
var mysql_im = require("../repository/imRepo");
function isUserExist(to_uid, handler) {
	mysql_wlyy.execQuery({
@ -14,14 +14,14 @@ function isUserExist(to_uid, handler) {
function saveP2PMsg(to_uid, from_uid, type, content, handler) {
	mysql_im.execQuery({
		"sql": "INSERT INTO msg_p2p (to_uid,from_uid,type,content) VALUES (?,?,?,?)",
		"sql": "INSERT INTO privates (to_uid,from_uid,type,content) VALUES (?,?,?,?)",
		"args": [to_uid, from_uid, type, content],
		"handler": handler
	});
}
function getP2PMsg(to_uid, from_uid, start, count, handler) {
	var sql = "SELECT to_uid,from_uid,type,content,timestamp from msg_p2p WHERE (to_uid=? AND from_uid=?) OR (to_uid=? AND from_uid=?) GROUP BY timestamp DESC LIMIT ";
	var sql = "SELECT to_uid,from_uid,type,content,timestamp from privates WHERE (to_uid=? AND from_uid=?) OR (to_uid=? AND from_uid=?) GROUP BY timestamp DESC LIMIT ";
	sql += start;
	sql += ",";
	sql += count;

+ 16 - 16
models/msg_statistic.js

@ -4,24 +4,24 @@ var http = require('http');
var qs = require('querystring');
var async = require('async');
var commons = require('./include/commons');
var commons = require('./include/endpoints');
var config = require(commons.CONFIG_FILE);
var log = require('../util/log');
var mysql_wlyy = require("../repository/mysql_wlyy");
var mysql_im = require("../repository/mysql_im");
var mysql_wlyy = require("../repository/wlyyRepo");
var mysql_im = require("../repository/imRepo");
function updateGroupChatInfo(user_id, group_id, from_uid, at_me, type, content, msg_count_plus_one, handler) {
	var uuid = user_id + '_' + group_id;
    if (msg_count_plus_one) {
        mysql_im.execQuery({
            "sql": "INSERT INTO msg_statistic (uid,uuid,from_uid,from_gid,at_me,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE from_uid=?,at_me=?,last_content_type=?,last_content=?,new_msg_count=new_msg_count+1",
            "sql": "INSERT INTO statistic (uid,uuid,from_uid,from_gid,at_me,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE from_uid=?,at_me=?,last_content_type=?,last_content=?,new_msg_count=new_msg_count+1",
            "args": [user_id, uuid, from_uid, group_id, at_me, 2, type, content, 1, from_uid, at_me, type, content],
            "handler": handler
        });
    } else {
        mysql_im.execQuery({
            "sql": "INSERT INTO msg_statistic (uid,uuid,from_uid,from_gid,at_me,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE from_uid=?,at_me=?,last_content_type=?,last_content=?",
            "sql": "INSERT INTO statistic (uid,uuid,from_uid,from_gid,at_me,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE from_uid=?,at_me=?,last_content_type=?,last_content=?",
            "args": [user_id, uuid, from_uid, group_id, at_me, 2, type, content, 0, from_uid, at_me, type, content],
            "handler": handler
        });
@ -33,14 +33,14 @@ function updateP2PChatInfo(user_id, peer_uid, from_uid, type, content, handler)
    if (user_id == from_uid) {
        // 更新自身的统计信息
        mysql_im.execQuery({
            "sql": "INSERT INTO msg_statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?",
            "sql": "INSERT INTO statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?",
            "args": [user_id, uuid, from_uid, peer_uid, 1, type, content, 0, peer_uid, type, content],
            "handler": handler
        });
    } else {
        // 更新对端的统计信息
        mysql_im.execQuery({
            "sql": "INSERT INTO msg_statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?,new_msg_count=new_msg_count+1",
            "sql": "INSERT INTO statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?,new_msg_count=new_msg_count+1",
            "args": [user_id, uuid, from_uid, peer_uid, 1, type, content, 1, peer_uid, type, content],
            "handler": handler
        });
@ -50,7 +50,7 @@ function updateP2PChatInfo(user_id, peer_uid, from_uid, type, content, handler)
function clearGroupChatInfo(user_id, group_id, handler) {
	var uuid = user_id + '_' + group_id;
	mysql_im.execQuery({
		"sql": "UPDATE msg_statistic SET new_msg_count='0' WHERE uuid=?",
		"sql": "UPDATE statistic SET new_msg_count='0' WHERE uuid=?",
		"args": [uuid],
		"handler": handler
	});
@ -59,7 +59,7 @@ function clearGroupChatInfo(user_id, group_id, handler) {
function clearP2PChatInfo(user_id, peer_uid, handler) {
	var uuid = user_id + '_' + peer_uid;
	mysql_im.execQuery({
		"sql": "UPDATE msg_statistic SET new_msg_count='0' WHERE uuid=?",
		"sql": "UPDATE statistic SET new_msg_count='0' WHERE uuid=?",
		"args": [uuid],
		"handler": handler
	});
@ -68,7 +68,7 @@ function clearP2PChatInfo(user_id, peer_uid, handler) {
function getGroupChatInfo(user_id, group_id, handler) {
	var uuid = user_id + '_' + group_id;
	mysql_im.execQuery({
		"sql": "SELECT uid,from_uid,from_gid,at_me,last_content_type,last_content,new_msg_count,timestamp from msg_statistic WHERE uuid = ?",
		"sql": "SELECT uid,from_uid,from_gid,at_me,last_content_type,last_content,new_msg_count,timestamp from statistic WHERE uuid = ?",
		"args": [uuid],
		"handler": handler
	});
@ -77,7 +77,7 @@ function getGroupChatInfo(user_id, group_id, handler) {
function getP2PChatInfo(user_id, peer_uid, handler) {
	var uuid = user_id + '_' + peer_uid;
	mysql_im.execQuery({
		"sql": "SELECT uid,from_uid,last_content_type,last_content,new_msg_count,timestamp from msg_statistic WHERE uuid = ?",
		"sql": "SELECT uid,from_uid,last_content_type,last_content,new_msg_count,timestamp from statistic WHERE uuid = ?",
		"args": [uuid],
		"handler": handler
	});
@ -85,7 +85,7 @@ function getP2PChatInfo(user_id, peer_uid, handler) {
function getChatList(user_id, handler) {
    mysql_im.execQuery({
        "sql": "SELECT uid,from_uid,from_gid,peer_uid,at_me,msg_type,last_content_type,last_content,new_msg_count,timestamp from msg_statistic WHERE uid = ?",
        "sql": "SELECT uid,from_uid,from_gid,peer_uid,at_me,msg_type,last_content_type,last_content,new_msg_count,timestamp from statistic WHERE uid = ?",
        "args": [user_id],
        "handler": handler
    });
@ -93,7 +93,7 @@ function getChatList(user_id, handler) {
function getGroupChatAllUnRead(user_id, handler) {
	mysql_im.execQuery({
		"sql": "SELECT new_msg_count from msg_statistic WHERE uid=? AND msg_type=2 AND new_msg_count>0",
		"sql": "SELECT new_msg_count from statistic WHERE uid=? AND msg_type=2 AND new_msg_count>0",
		"args": [user_id],
		"handler": handler
	});
@ -101,7 +101,7 @@ function getGroupChatAllUnRead(user_id, handler) {
function getP2PChatAllUnRead(user_id, handler) {
	mysql_im.execQuery({
		"sql": "SELECT new_msg_count from msg_statistic WHERE uid=? AND msg_type=1 AND new_msg_count>0",
		"sql": "SELECT new_msg_count from statistic WHERE uid=? AND msg_type=1 AND new_msg_count>0",
		"args": [user_id],
		"handler": handler
	});
@ -109,7 +109,7 @@ function getP2PChatAllUnRead(user_id, handler) {
function getChatAllUnRead(user_id, handler) {
	mysql_im.execQuery({
		"sql": "SELECT new_msg_count from msg_statistic WHERE uid=? AND new_msg_count>0",
		"sql": "SELECT new_msg_count from statistic WHERE uid=? AND new_msg_count>0",
		"args": [user_id],
		"handler": handler
	});
@ -117,7 +117,7 @@ function getChatAllUnRead(user_id, handler) {
function getAppMsgAmount(user_id, handler) {
    mysql_wlyy.execQuery({
        "sql": "SELECT imei,token from wlyy_token WHERE user=?",
        "sql": "SELECT imei,token from wlyy_token WHERE users=?",
        "args": [user_id],
        "handler": function(err, result) {
            if (err) {

+ 2 - 2
models/msg_system.js

@ -1,11 +1,11 @@
"use strict";
var log = require('../util/log');
var mysql_im = require("../repository/mysql_im");
var mysql_im = require("../repository/imRepo");
function saveSystemMsg(to_uid, type, title, content, data, handler) {
    mysql_im.execQuery({
        "sql": "INSERT INTO msg_system (to_uid,type,title,content,data) VALUES (?,?,?,?,?)",
        "sql": "INSERT INTO system (to_uid,type,title,content,data) VALUES (?,?,?,?,?)",
        "args": [to_uid, type, title, content, data],
        "handler": handler
    });

+ 1 - 1
models/push_notify.js

@ -1,7 +1,7 @@
"use strict";
var log = require('../util/log');
var mysql_im = require("../repository/mysql_im");
var mysql_im = require("../repository/imRepo");
function savePushNotify(to_uid, type, title, content, data, has_pushed, handler) {
    mysql_im.execQuery({

+ 11 - 11
models/user.js

@ -1,43 +1,43 @@
"use strict";
var log = require('../util/log');
var mysql_im = require("../repository/mysql_im");
var imRepo = require("../repository/im.repo");
function login(user_id, token, client_id, platform, handler) {
    mysql_im.execQuery({
        "sql": "INSERT INTO user (user_id,token,client_id,platform,is_online,status) VALUES (?,?,?,?,1,1) ON DUPLICATE KEY UPDATE token=?,client_id=?,platform=?,is_online=1,status=1",
    imRepo.execQuery({
        "sql": "INSERT INTO users (user_id,token,client_id,platform,is_online,status) VALUES (?,?,?,?,1,1) ON DUPLICATE KEY UPDATE token=?,client_id=?,platform=?,is_online=1,status=1",
        "args": [user_id, token, client_id, platform, token, client_id, platform],
        "handler": handler
    });
}
function logout(user_id, handler) {
    mysql_im.execQuery({
        "sql": "UPDATE user SET is_online='0',status='0' WHERE user_id=?",
    imRepo.execQuery({
        "sql": "UPDATE users SET is_online='0',status='0' WHERE user_id=?",
        "args": [user_id],
        "handler": handler
    });
}
function getUserbyID(user_id, handler) {
    mysql_im.execQuery({
        "sql": "SELECT platform,token,client_id,is_online,status from user WHERE user_id = ?",
    imRepo.execQuery({
        "sql": "SELECT platform,token,client_id,is_online,status from users WHERE user_id = ?",
        "args": [user_id],
        "handler": handler
    });
}
function deleteToken(token, handler) {
    mysql_im.execQuery({
        "sql": "DELETE FROM user WHERE token=?",
    imRepo.execQuery({
        "sql": "DELETE FROM users WHERE token=?",
        "args": [token],
        "handler": handler
    });
}
function updateStatus(user_id, status, handler) {
    mysql_im.execQuery({
        "sql": "UPDATE user SET status=? WHERE user_id=?",
    imRepo.execQuery({
        "sql": "UPDATE users SET status=? WHERE user_id=?",
        "args": [status, user_id],
        "handler": handler
    });

+ 2 - 1
package.json

@ -4,7 +4,7 @@
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
    "start": "node ./bin/spawn.script.js"
  },
  "dependencies": {
@ -12,6 +12,7 @@
    "body-parser": "~1.12.4",
    "cookie-parser": "~1.3.5",
    "debug": "~2.2.0",
    "enum": "~2.3.0",
    "express": "~4.12.4",
    "jade": "~1.9.2",
    "morgan": "~1.5.3",

二进制
src/doctor/public/favicon.ico


+ 3 - 3
public/javascripts/sdk/im.client.js

@ -13,9 +13,9 @@ var servers = {
// REST接口相对路径
var restPath = {
    login: "/user/login.im",
    logout: "/user/logout.im",
    updateStatus: "/user/updatestatus.im",
    login: "/users/login.im",
    logout: "/users/logout.im",
    updateStatus: "/users/updatestatus.im",
    sendGroup: "/group/sendmsg.im",
    getGroupMsg: "/group/getmsg.im",
    sendOneByOne: "/p2p/sendmsg.im",

public/stylesheets/style.css → src/doctor/public/stylesheets/style.css


+ 2 - 2
repository/imRepo.js

@ -1,9 +1,9 @@
"use strict";
var mysql = require('mysql');
var commons = require('./include/commons');
var commons = require('./include/endpoints');
var config = require(commons.CONFIG_FILE);
var dbUtil = require('../util/DbUtil');
var dbUtil = require('../util/dbUtil');
var pool = mysql.createPool(config.imDbConfig);

+ 2 - 2
repository/wlyyRepo.js

@ -1,9 +1,9 @@
"use strict";
var mysql = require('mysql');
var commons = require('./include/commons');
var commons = require('./include/endpoints');
var config = require(commons.CONFIG_FILE);
var dbUtil = require('../util/DbUtil');
var dbUtil = require('../util/dbUtil');
var pool = mysql.createPool(config.wlyyDbConfig);

resources/config/config.dev.js → src/doctor/resources/config/config.dev.js


resources/config/config.pro.js → src/doctor/resources/config/config.pro.js


resources/config/config.test.js → src/doctor/resources/config/config.test.js


resources/schema/im_schema.sql → src/doctor/resources/schema/im_schema.sql


resources/schema/talk_group_schema.sql → src/doctor/resources/schema/talk_group_schema.sql


util/DbUtil.js → src/doctor/util/dbUtil.js


util/log.js → src/doctor/util/log.js


+ 108 - 0
src/doctor/util/mime.js

@ -0,0 +1,108 @@
var path = require('path');
var fs = require('fs');
function Mime() {
    // Map of extension -> mime type
    this.types = Object.create(null);
    // Map of mime type -> extension
    this.extensions = Object.create(null);
}
/**
 * Define mimetype -> extension mappings.  Each key is a mime-type that maps
 * to an array of extensions associated with the type.  The first extension is
 * used as the default extension for the type.
 *
 * e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']});
 *
 * @param map (Object) type definitions
 */
Mime.prototype.define = function (map) {
    for (var type in map) {
        var exts = map[type];
        for (var i = 0; i < exts.length; i++) {
            if (process.env.DEBUG_MIME && this.types[exts]) {
                console.warn(this._loading.replace(/.*\//, ''), 'changes "' + exts[i] + '" extension type from ' +
                    this.types[exts] + ' to ' + type);
            }
            this.types[exts[i]] = type;
        }
        // Default extension is the first one we encounter
        if (!this.extensions[type]) {
            this.extensions[type] = exts[0];
        }
    }
};
/**
 * Load an Apache2-style ".types" file
 *
 * This may be called multiple times (it's expected).  Where files declare
 * overlapping types/extensions, the last file wins.
 *
 * @param file (String) path of file to load.
 */
Mime.prototype.load = function(file) {
    this._loading = file;
    // Read file and split into lines
    var map = {},
        content = fs.readFileSync(file, 'ascii'),
        lines = content.split(/[\r\n]+/);
    lines.forEach(function(line) {
        // Clean up whitespace/comments, and split into fields
        var fields = line.replace(/\s*#.*|^\s*|\s*$/g, '').split(/\s+/);
        map[fields.shift()] = fields;
    });
    this.define(map);
    this._loading = null;
};
/**
 * Lookup a mime type based on extension
 */
Mime.prototype.lookup = function(path, fallback) {
    var ext = path.replace(/.*[\.\/\\]/, '').toLowerCase();
    return this.types[ext] || fallback || this.default_type;
};
/**
 * Return file extension associated with a mime type
 */
Mime.prototype.extension = function(mimeType) {
    var type = mimeType.match(/^\s*([^;\s]*)(?:;|\s|$)/)[1].toLowerCase();
    return this.extensions[type];
};
// Default instance
var mime = new Mime();
// Define built-in types
mime.define(require('./types.json'));
// Default type
mime.default_type = mime.lookup('bin');
//
// Additional API specific to the default instance
//
mime.Mime = Mime;
/**
 * Lookup a charset based on mime type.
 */
mime.charsets = {
    lookup: function(mimeType, fallback) {
        // Assume text types are utf8
        return (/^text\//).test(mimeType) ? 'UTF-8' : fallback;
    }
};
module.exports = mime;

+ 2586 - 0
src/doctor/util/mime.types.json

@ -0,0 +1,2586 @@
{
  "application/andrew-inset": [
    "ez"
  ],
  "application/applixware": [
    "aw"
  ],
  "application/atom+xml": [
    "atom"
  ],
  "application/atomcat+xml": [
    "atomcat"
  ],
  "application/atomsvc+xml": [
    "atomsvc"
  ],
  "application/ccxml+xml": [
    "ccxml"
  ],
  "application/cdmi-capability": [
    "cdmia"
  ],
  "application/cdmi-container": [
    "cdmic"
  ],
  "application/cdmi-domain": [
    "cdmid"
  ],
  "application/cdmi-object": [
    "cdmio"
  ],
  "application/cdmi-queue": [
    "cdmiq"
  ],
  "application/cu-seeme": [
    "cu"
  ],
  "application/dash+xml": [
    "mdp"
  ],
  "application/davmount+xml": [
    "davmount"
  ],
  "application/docbook+xml": [
    "dbk"
  ],
  "application/dssc+der": [
    "dssc"
  ],
  "application/dssc+xml": [
    "xdssc"
  ],
  "application/ecmascript": [
    "ecma"
  ],
  "application/emma+xml": [
    "emma"
  ],
  "application/epub+zip": [
    "epub"
  ],
  "application/exi": [
    "exi"
  ],
  "application/font-tdpfr": [
    "pfr"
  ],
  "application/font-woff": [
    "woff"
  ],
  "application/font-woff2": [
    "woff2"
  ],
  "application/gml+xml": [
    "gml"
  ],
  "application/gpx+xml": [
    "gpx"
  ],
  "application/gxf": [
    "gxf"
  ],
  "application/hyperstudio": [
    "stk"
  ],
  "application/inkml+xml": [
    "ink",
    "inkml"
  ],
  "application/ipfix": [
    "ipfix"
  ],
  "application/java-archive": [
    "jar"
  ],
  "application/java-serialized-object": [
    "ser"
  ],
  "application/java-vm": [
    "class"
  ],
  "application/javascript": [
    "js"
  ],
  "application/json": [
    "json",
    "map"
  ],
  "application/json5": [
    "json5"
  ],
  "application/jsonml+json": [
    "jsonml"
  ],
  "application/lost+xml": [
    "lostxml"
  ],
  "application/mac-binhex40": [
    "hqx"
  ],
  "application/mac-compactpro": [
    "cpt"
  ],
  "application/mads+xml": [
    "mads"
  ],
  "application/marc": [
    "mrc"
  ],
  "application/marcxml+xml": [
    "mrcx"
  ],
  "application/mathematica": [
    "ma",
    "nb",
    "mb"
  ],
  "application/mathml+xml": [
    "mathml"
  ],
  "application/mbox": [
    "mbox"
  ],
  "application/mediaservercontrol+xml": [
    "mscml"
  ],
  "application/metalink+xml": [
    "metalink"
  ],
  "application/metalink4+xml": [
    "meta4"
  ],
  "application/mets+xml": [
    "mets"
  ],
  "application/mods+xml": [
    "mods"
  ],
  "application/mp21": [
    "m21",
    "mp21"
  ],
  "application/mp4": [
    "mp4s",
    "m4p"
  ],
  "application/msword": [
    "doc",
    "dot"
  ],
  "application/mxf": [
    "mxf"
  ],
  "application/octet-stream": [
    "bin",
    "dms",
    "lrf",
    "mar",
    "so",
    "dist",
    "distz",
    "pkg",
    "bpk",
    "dump",
    "elc",
    "deploy",
    "buffer"
  ],
  "application/oda": [
    "oda"
  ],
  "application/oebps-package+xml": [
    "opf"
  ],
  "application/ogg": [
    "ogx"
  ],
  "application/omdoc+xml": [
    "omdoc"
  ],
  "application/onenote": [
    "onetoc",
    "onetoc2",
    "onetmp",
    "onepkg"
  ],
  "application/oxps": [
    "oxps"
  ],
  "application/patch-ops-error+xml": [
    "xer"
  ],
  "application/pdf": [
    "pdf"
  ],
  "application/pgp-encrypted": [
    "pgp"
  ],
  "application/pgp-signature": [
    "asc",
    "sig"
  ],
  "application/pics-rules": [
    "prf"
  ],
  "application/pkcs10": [
    "p10"
  ],
  "application/pkcs7-mime": [
    "p7m",
    "p7c"
  ],
  "application/pkcs7-signature": [
    "p7s"
  ],
  "application/pkcs8": [
    "p8"
  ],
  "application/pkix-attr-cert": [
    "ac"
  ],
  "application/pkix-cert": [
    "cer"
  ],
  "application/pkix-crl": [
    "crl"
  ],
  "application/pkix-pkipath": [
    "pkipath"
  ],
  "application/pkixcmp": [
    "pki"
  ],
  "application/pls+xml": [
    "pls"
  ],
  "application/postscript": [
    "ai",
    "eps",
    "ps"
  ],
  "application/prs.cww": [
    "cww"
  ],
  "application/pskc+xml": [
    "pskcxml"
  ],
  "application/rdf+xml": [
    "rdf"
  ],
  "application/reginfo+xml": [
    "rif"
  ],
  "application/relax-ng-compact-syntax": [
    "rnc"
  ],
  "application/resource-lists+xml": [
    "rl"
  ],
  "application/resource-lists-diff+xml": [
    "rld"
  ],
  "application/rls-services+xml": [
    "rs"
  ],
  "application/rpki-ghostbusters": [
    "gbr"
  ],
  "application/rpki-manifest": [
    "mft"
  ],
  "application/rpki-roa": [
    "roa"
  ],
  "application/rsd+xml": [
    "rsd"
  ],
  "application/rss+xml": [
    "rss"
  ],
  "application/rtf": [
    "rtf"
  ],
  "application/sbml+xml": [
    "sbml"
  ],
  "application/scvp-cv-request": [
    "scq"
  ],
  "application/scvp-cv-response": [
    "scs"
  ],
  "application/scvp-vp-request": [
    "spq"
  ],
  "application/scvp-vp-response": [
    "spp"
  ],
  "application/sdp": [
    "sdp"
  ],
  "application/set-payment-initiation": [
    "setpay"
  ],
  "application/set-registration-initiation": [
    "setreg"
  ],
  "application/shf+xml": [
    "shf"
  ],
  "application/smil+xml": [
    "smi",
    "smil"
  ],
  "application/sparql-query": [
    "rq"
  ],
  "application/sparql-results+xml": [
    "srx"
  ],
  "application/srgs": [
    "gram"
  ],
  "application/srgs+xml": [
    "grxml"
  ],
  "application/sru+xml": [
    "sru"
  ],
  "application/ssdl+xml": [
    "ssdl"
  ],
  "application/ssml+xml": [
    "ssml"
  ],
  "application/tei+xml": [
    "tei",
    "teicorpus"
  ],
  "application/thraud+xml": [
    "tfi"
  ],
  "application/timestamped-data": [
    "tsd"
  ],
  "application/vnd.3gpp.pic-bw-large": [
    "plb"
  ],
  "application/vnd.3gpp.pic-bw-small": [
    "psb"
  ],
  "application/vnd.3gpp.pic-bw-var": [
    "pvb"
  ],
  "application/vnd.3gpp2.tcap": [
    "tcap"
  ],
  "application/vnd.3m.post-it-notes": [
    "pwn"
  ],
  "application/vnd.accpac.simply.aso": [
    "aso"
  ],
  "application/vnd.accpac.simply.imp": [
    "imp"
  ],
  "application/vnd.acucobol": [
    "acu"
  ],
  "application/vnd.acucorp": [
    "atc",
    "acutc"
  ],
  "application/vnd.adobe.air-application-installer-package+zip": [
    "air"
  ],
  "application/vnd.adobe.formscentral.fcdt": [
    "fcdt"
  ],
  "application/vnd.adobe.fxp": [
    "fxp",
    "fxpl"
  ],
  "application/vnd.adobe.xdp+xml": [
    "xdp"
  ],
  "application/vnd.adobe.xfdf": [
    "xfdf"
  ],
  "application/vnd.ahead.space": [
    "ahead"
  ],
  "application/vnd.airzip.filesecure.azf": [
    "azf"
  ],
  "application/vnd.airzip.filesecure.azs": [
    "azs"
  ],
  "application/vnd.amazon.ebook": [
    "azw"
  ],
  "application/vnd.americandynamics.acc": [
    "acc"
  ],
  "application/vnd.amiga.ami": [
    "ami"
  ],
  "application/vnd.android.package-archive": [
    "apk"
  ],
  "application/vnd.anser-web-certificate-issue-initiation": [
    "cii"
  ],
  "application/vnd.anser-web-funds-transfer-initiation": [
    "fti"
  ],
  "application/vnd.antix.game-component": [
    "atx"
  ],
  "application/vnd.apple.installer+xml": [
    "mpkg"
  ],
  "application/vnd.apple.mpegurl": [
    "m3u8"
  ],
  "application/vnd.aristanetworks.swi": [
    "swi"
  ],
  "application/vnd.astraea-software.iota": [
    "iota"
  ],
  "application/vnd.audiograph": [
    "aep"
  ],
  "application/vnd.blueice.multipass": [
    "mpm"
  ],
  "application/vnd.bmi": [
    "bmi"
  ],
  "application/vnd.businessobjects": [
    "rep"
  ],
  "application/vnd.chemdraw+xml": [
    "cdxml"
  ],
  "application/vnd.chipnuts.karaoke-mmd": [
    "mmd"
  ],
  "application/vnd.cinderella": [
    "cdy"
  ],
  "application/vnd.claymore": [
    "cla"
  ],
  "application/vnd.cloanto.rp9": [
    "rp9"
  ],
  "application/vnd.clonk.c4group": [
    "c4g",
    "c4d",
    "c4f",
    "c4p",
    "c4u"
  ],
  "application/vnd.cluetrust.cartomobile-config": [
    "c11amc"
  ],
  "application/vnd.cluetrust.cartomobile-config-pkg": [
    "c11amz"
  ],
  "application/vnd.commonspace": [
    "csp"
  ],
  "application/vnd.contact.cmsg": [
    "cdbcmsg"
  ],
  "application/vnd.cosmocaller": [
    "cmc"
  ],
  "application/vnd.crick.clicker": [
    "clkx"
  ],
  "application/vnd.crick.clicker.keyboard": [
    "clkk"
  ],
  "application/vnd.crick.clicker.palette": [
    "clkp"
  ],
  "application/vnd.crick.clicker.template": [
    "clkt"
  ],
  "application/vnd.crick.clicker.wordbank": [
    "clkw"
  ],
  "application/vnd.criticaltools.wbs+xml": [
    "wbs"
  ],
  "application/vnd.ctc-posml": [
    "pml"
  ],
  "application/vnd.cups-ppd": [
    "ppd"
  ],
  "application/vnd.curl.car": [
    "car"
  ],
  "application/vnd.curl.pcurl": [
    "pcurl"
  ],
  "application/vnd.dart": [
    "dart"
  ],
  "application/vnd.data-vision.rdz": [
    "rdz"
  ],
  "application/vnd.dece.data": [
    "uvf",
    "uvvf",
    "uvd",
    "uvvd"
  ],
  "application/vnd.dece.ttml+xml": [
    "uvt",
    "uvvt"
  ],
  "application/vnd.dece.unspecified": [
    "uvx",
    "uvvx"
  ],
  "application/vnd.dece.zip": [
    "uvz",
    "uvvz"
  ],
  "application/vnd.denovo.fcselayout-link": [
    "fe_launch"
  ],
  "application/vnd.dna": [
    "dna"
  ],
  "application/vnd.dolby.mlp": [
    "mlp"
  ],
  "application/vnd.dpgraph": [
    "dpg"
  ],
  "application/vnd.dreamfactory": [
    "dfac"
  ],
  "application/vnd.ds-keypoint": [
    "kpxx"
  ],
  "application/vnd.dvb.ait": [
    "ait"
  ],
  "application/vnd.dvb.service": [
    "svc"
  ],
  "application/vnd.dynageo": [
    "geo"
  ],
  "application/vnd.ecowin.chart": [
    "mag"
  ],
  "application/vnd.enliven": [
    "nml"
  ],
  "application/vnd.epson.esf": [
    "esf"
  ],
  "application/vnd.epson.msf": [
    "msf"
  ],
  "application/vnd.epson.quickanime": [
    "qam"
  ],
  "application/vnd.epson.salt": [
    "slt"
  ],
  "application/vnd.epson.ssf": [
    "ssf"
  ],
  "application/vnd.eszigno3+xml": [
    "es3",
    "et3"
  ],
  "application/vnd.ezpix-album": [
    "ez2"
  ],
  "application/vnd.ezpix-package": [
    "ez3"
  ],
  "application/vnd.fdf": [
    "fdf"
  ],
  "application/vnd.fdsn.mseed": [
    "mseed"
  ],
  "application/vnd.fdsn.seed": [
    "seed",
    "dataless"
  ],
  "application/vnd.flographit": [
    "gph"
  ],
  "application/vnd.fluxtime.clip": [
    "ftc"
  ],
  "application/vnd.framemaker": [
    "fm",
    "frame",
    "maker",
    "book"
  ],
  "application/vnd.frogans.fnc": [
    "fnc"
  ],
  "application/vnd.frogans.ltf": [
    "ltf"
  ],
  "application/vnd.fsc.weblaunch": [
    "fsc"
  ],
  "application/vnd.fujitsu.oasys": [
    "oas"
  ],
  "application/vnd.fujitsu.oasys2": [
    "oa2"
  ],
  "application/vnd.fujitsu.oasys3": [
    "oa3"
  ],
  "application/vnd.fujitsu.oasysgp": [
    "fg5"
  ],
  "application/vnd.fujitsu.oasysprs": [
    "bh2"
  ],
  "application/vnd.fujixerox.ddd": [
    "ddd"
  ],
  "application/vnd.fujixerox.docuworks": [
    "xdw"
  ],
  "application/vnd.fujixerox.docuworks.binder": [
    "xbd"
  ],
  "application/vnd.fuzzysheet": [
    "fzs"
  ],
  "application/vnd.genomatix.tuxedo": [
    "txd"
  ],
  "application/vnd.geogebra.file": [
    "ggb"
  ],
  "application/vnd.geogebra.tool": [
    "ggt"
  ],
  "application/vnd.geometry-explorer": [
    "gex",
    "gre"
  ],
  "application/vnd.geonext": [
    "gxt"
  ],
  "application/vnd.geoplan": [
    "g2w"
  ],
  "application/vnd.geospace": [
    "g3w"
  ],
  "application/vnd.gmx": [
    "gmx"
  ],
  "application/vnd.google-earth.kml+xml": [
    "kml"
  ],
  "application/vnd.google-earth.kmz": [
    "kmz"
  ],
  "application/vnd.grafeq": [
    "gqf",
    "gqs"
  ],
  "application/vnd.groove-account": [
    "gac"
  ],
  "application/vnd.groove-help": [
    "ghf"
  ],
  "application/vnd.groove-identity-message": [
    "gim"
  ],
  "application/vnd.groove-injector": [
    "grv"
  ],
  "application/vnd.groove-tool-message": [
    "gtm"
  ],
  "application/vnd.groove-tool-template": [
    "tpl"
  ],
  "application/vnd.groove-vcard": [
    "vcg"
  ],
  "application/vnd.hal+xml": [
    "hal"
  ],
  "application/vnd.handheld-entertainment+xml": [
    "zmm"
  ],
  "application/vnd.hbci": [
    "hbci"
  ],
  "application/vnd.hhe.lesson-player": [
    "les"
  ],
  "application/vnd.hp-hpgl": [
    "hpgl"
  ],
  "application/vnd.hp-hpid": [
    "hpid"
  ],
  "application/vnd.hp-hps": [
    "hps"
  ],
  "application/vnd.hp-jlyt": [
    "jlt"
  ],
  "application/vnd.hp-pcl": [
    "pcl"
  ],
  "application/vnd.hp-pclxl": [
    "pclxl"
  ],
  "application/vnd.ibm.minipay": [
    "mpy"
  ],
  "application/vnd.ibm.modcap": [
    "afp",
    "listafp",
    "list3820"
  ],
  "application/vnd.ibm.rights-management": [
    "irm"
  ],
  "application/vnd.ibm.secure-container": [
    "sc"
  ],
  "application/vnd.iccprofile": [
    "icc",
    "icm"
  ],
  "application/vnd.igloader": [
    "igl"
  ],
  "application/vnd.immervision-ivp": [
    "ivp"
  ],
  "application/vnd.immervision-ivu": [
    "ivu"
  ],
  "application/vnd.insors.igm": [
    "igm"
  ],
  "application/vnd.intercon.formnet": [
    "xpw",
    "xpx"
  ],
  "application/vnd.intergeo": [
    "i2g"
  ],
  "application/vnd.intu.qbo": [
    "qbo"
  ],
  "application/vnd.intu.qfx": [
    "qfx"
  ],
  "application/vnd.ipunplugged.rcprofile": [
    "rcprofile"
  ],
  "application/vnd.irepository.package+xml": [
    "irp"
  ],
  "application/vnd.is-xpr": [
    "xpr"
  ],
  "application/vnd.isac.fcs": [
    "fcs"
  ],
  "application/vnd.jam": [
    "jam"
  ],
  "application/vnd.jcp.javame.midlet-rms": [
    "rms"
  ],
  "application/vnd.jisp": [
    "jisp"
  ],
  "application/vnd.joost.joda-archive": [
    "joda"
  ],
  "application/vnd.kahootz": [
    "ktz",
    "ktr"
  ],
  "application/vnd.kde.karbon": [
    "karbon"
  ],
  "application/vnd.kde.kchart": [
    "chrt"
  ],
  "application/vnd.kde.kformula": [
    "kfo"
  ],
  "application/vnd.kde.kivio": [
    "flw"
  ],
  "application/vnd.kde.kontour": [
    "kon"
  ],
  "application/vnd.kde.kpresenter": [
    "kpr",
    "kpt"
  ],
  "application/vnd.kde.kspread": [
    "ksp"
  ],
  "application/vnd.kde.kword": [
    "kwd",
    "kwt"
  ],
  "application/vnd.kenameaapp": [
    "htke"
  ],
  "application/vnd.kidspiration": [
    "kia"
  ],
  "application/vnd.kinar": [
    "kne",
    "knp"
  ],
  "application/vnd.koan": [
    "skp",
    "skd",
    "skt",
    "skm"
  ],
  "application/vnd.kodak-descriptor": [
    "sse"
  ],
  "application/vnd.las.las+xml": [
    "lasxml"
  ],
  "application/vnd.llamagraphics.life-balance.desktop": [
    "lbd"
  ],
  "application/vnd.llamagraphics.life-balance.exchange+xml": [
    "lbe"
  ],
  "application/vnd.lotus-1-2-3": [
    "123"
  ],
  "application/vnd.lotus-approach": [
    "apr"
  ],
  "application/vnd.lotus-freelance": [
    "pre"
  ],
  "application/vnd.lotus-notes": [
    "nsf"
  ],
  "application/vnd.lotus-organizer": [
    "org"
  ],
  "application/vnd.lotus-screencam": [
    "scm"
  ],
  "application/vnd.lotus-wordpro": [
    "lwp"
  ],
  "application/vnd.macports.portpkg": [
    "portpkg"
  ],
  "application/vnd.mcd": [
    "mcd"
  ],
  "application/vnd.medcalcdata": [
    "mc1"
  ],
  "application/vnd.mediastation.cdkey": [
    "cdkey"
  ],
  "application/vnd.mfer": [
    "mwf"
  ],
  "application/vnd.mfmp": [
    "mfm"
  ],
  "application/vnd.micrografx.flo": [
    "flo"
  ],
  "application/vnd.micrografx.igx": [
    "igx"
  ],
  "application/vnd.mif": [
    "mif"
  ],
  "application/vnd.mobius.daf": [
    "daf"
  ],
  "application/vnd.mobius.dis": [
    "dis"
  ],
  "application/vnd.mobius.mbk": [
    "mbk"
  ],
  "application/vnd.mobius.mqy": [
    "mqy"
  ],
  "application/vnd.mobius.msl": [
    "msl"
  ],
  "application/vnd.mobius.plc": [
    "plc"
  ],
  "application/vnd.mobius.txf": [
    "txf"
  ],
  "application/vnd.mophun.application": [
    "mpn"
  ],
  "application/vnd.mophun.certificate": [
    "mpc"
  ],
  "application/vnd.mozilla.xul+xml": [
    "xul"
  ],
  "application/vnd.ms-artgalry": [
    "cil"
  ],
  "application/vnd.ms-cab-compressed": [
    "cab"
  ],
  "application/vnd.ms-excel": [
    "xls",
    "xlm",
    "xla",
    "xlc",
    "xlt",
    "xlw"
  ],
  "application/vnd.ms-excel.addin.macroenabled.12": [
    "xlam"
  ],
  "application/vnd.ms-excel.sheet.binary.macroenabled.12": [
    "xlsb"
  ],
  "application/vnd.ms-excel.sheet.macroenabled.12": [
    "xlsm"
  ],
  "application/vnd.ms-excel.template.macroenabled.12": [
    "xltm"
  ],
  "application/vnd.ms-fontobject": [
    "eot"
  ],
  "application/vnd.ms-htmlhelp": [
    "chm"
  ],
  "application/vnd.ms-ims": [
    "ims"
  ],
  "application/vnd.ms-lrm": [
    "lrm"
  ],
  "application/vnd.ms-officetheme": [
    "thmx"
  ],
  "application/vnd.ms-pki.seccat": [
    "cat"
  ],
  "application/vnd.ms-pki.stl": [
    "stl"
  ],
  "application/vnd.ms-powerpoint": [
    "ppt",
    "pps",
    "pot"
  ],
  "application/vnd.ms-powerpoint.addin.macroenabled.12": [
    "ppam"
  ],
  "application/vnd.ms-powerpoint.presentation.macroenabled.12": [
    "pptm"
  ],
  "application/vnd.ms-powerpoint.slide.macroenabled.12": [
    "sldm"
  ],
  "application/vnd.ms-powerpoint.slideshow.macroenabled.12": [
    "ppsm"
  ],
  "application/vnd.ms-powerpoint.template.macroenabled.12": [
    "potm"
  ],
  "application/vnd.ms-project": [
    "mpp",
    "mpt"
  ],
  "application/vnd.ms-word.document.macroenabled.12": [
    "docm"
  ],
  "application/vnd.ms-word.template.macroenabled.12": [
    "dotm"
  ],
  "application/vnd.ms-works": [
    "wps",
    "wks",
    "wcm",
    "wdb"
  ],
  "application/vnd.ms-wpl": [
    "wpl"
  ],
  "application/vnd.ms-xpsdocument": [
    "xps"
  ],
  "application/vnd.mseq": [
    "mseq"
  ],
  "application/vnd.musician": [
    "mus"
  ],
  "application/vnd.muvee.style": [
    "msty"
  ],
  "application/vnd.mynfc": [
    "taglet"
  ],
  "application/vnd.neurolanguage.nlu": [
    "nlu"
  ],
  "application/vnd.nitf": [
    "ntf",
    "nitf"
  ],
  "application/vnd.noblenet-directory": [
    "nnd"
  ],
  "application/vnd.noblenet-sealer": [
    "nns"
  ],
  "application/vnd.noblenet-web": [
    "nnw"
  ],
  "application/vnd.nokia.n-gage.data": [
    "ngdat"
  ],
  "application/vnd.nokia.radio-preset": [
    "rpst"
  ],
  "application/vnd.nokia.radio-presets": [
    "rpss"
  ],
  "application/vnd.novadigm.edm": [
    "edm"
  ],
  "application/vnd.novadigm.edx": [
    "edx"
  ],
  "application/vnd.novadigm.ext": [
    "ext"
  ],
  "application/vnd.oasis.opendocument.chart": [
    "odc"
  ],
  "application/vnd.oasis.opendocument.chart-template": [
    "otc"
  ],
  "application/vnd.oasis.opendocument.database": [
    "odb"
  ],
  "application/vnd.oasis.opendocument.formula": [
    "odf"
  ],
  "application/vnd.oasis.opendocument.formula-template": [
    "odft"
  ],
  "application/vnd.oasis.opendocument.graphics": [
    "odg"
  ],
  "application/vnd.oasis.opendocument.graphics-template": [
    "otg"
  ],
  "application/vnd.oasis.opendocument.image": [
    "odi"
  ],
  "application/vnd.oasis.opendocument.image-template": [
    "oti"
  ],
  "application/vnd.oasis.opendocument.presentation": [
    "odp"
  ],
  "application/vnd.oasis.opendocument.presentation-template": [
    "otp"
  ],
  "application/vnd.oasis.opendocument.spreadsheet": [
    "ods"
  ],
  "application/vnd.oasis.opendocument.spreadsheet-template": [
    "ots"
  ],
  "application/vnd.oasis.opendocument.text": [
    "odt"
  ],
  "application/vnd.oasis.opendocument.text-master": [
    "odm"
  ],
  "application/vnd.oasis.opendocument.text-template": [
    "ott"
  ],
  "application/vnd.oasis.opendocument.text-web": [
    "oth"
  ],
  "application/vnd.olpc-sugar": [
    "xo"
  ],
  "application/vnd.oma.dd2+xml": [
    "dd2"
  ],
  "application/vnd.openofficeorg.extension": [
    "oxt"
  ],
  "application/vnd.openxmlformats-officedocument.presentationml.presentation": [
    "pptx"
  ],
  "application/vnd.openxmlformats-officedocument.presentationml.slide": [
    "sldx"
  ],
  "application/vnd.openxmlformats-officedocument.presentationml.slideshow": [
    "ppsx"
  ],
  "application/vnd.openxmlformats-officedocument.presentationml.template": [
    "potx"
  ],
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [
    "xlsx"
  ],
  "application/vnd.openxmlformats-officedocument.spreadsheetml.template": [
    "xltx"
  ],
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document": [
    "docx"
  ],
  "application/vnd.openxmlformats-officedocument.wordprocessingml.template": [
    "dotx"
  ],
  "application/vnd.osgeo.mapguide.package": [
    "mgp"
  ],
  "application/vnd.osgi.dp": [
    "dp"
  ],
  "application/vnd.osgi.subsystem": [
    "esa"
  ],
  "application/vnd.palm": [
    "pdb",
    "pqa",
    "oprc"
  ],
  "application/vnd.pawaafile": [
    "paw"
  ],
  "application/vnd.pg.format": [
    "str"
  ],
  "application/vnd.pg.osasli": [
    "ei6"
  ],
  "application/vnd.picsel": [
    "efif"
  ],
  "application/vnd.pmi.widget": [
    "wg"
  ],
  "application/vnd.pocketlearn": [
    "plf"
  ],
  "application/vnd.powerbuilder6": [
    "pbd"
  ],
  "application/vnd.previewsystems.box": [
    "box"
  ],
  "application/vnd.proteus.magazine": [
    "mgz"
  ],
  "application/vnd.publishare-delta-tree": [
    "qps"
  ],
  "application/vnd.pvi.ptid1": [
    "ptid"
  ],
  "application/vnd.quark.quarkxpress": [
    "qxd",
    "qxt",
    "qwd",
    "qwt",
    "qxl",
    "qxb"
  ],
  "application/vnd.realvnc.bed": [
    "bed"
  ],
  "application/vnd.recordare.musicxml": [
    "mxl"
  ],
  "application/vnd.recordare.musicxml+xml": [
    "musicxml"
  ],
  "application/vnd.rig.cryptonote": [
    "cryptonote"
  ],
  "application/vnd.rim.cod": [
    "cod"
  ],
  "application/vnd.rn-realmedia": [
    "rm"
  ],
  "application/vnd.rn-realmedia-vbr": [
    "rmvb"
  ],
  "application/vnd.route66.link66+xml": [
    "link66"
  ],
  "application/vnd.sailingtracker.track": [
    "st"
  ],
  "application/vnd.seemail": [
    "see"
  ],
  "application/vnd.sema": [
    "sema"
  ],
  "application/vnd.semd": [
    "semd"
  ],
  "application/vnd.semf": [
    "semf"
  ],
  "application/vnd.shana.informed.formdata": [
    "ifm"
  ],
  "application/vnd.shana.informed.formtemplate": [
    "itp"
  ],
  "application/vnd.shana.informed.interchange": [
    "iif"
  ],
  "application/vnd.shana.informed.package": [
    "ipk"
  ],
  "application/vnd.simtech-mindmapper": [
    "twd",
    "twds"
  ],
  "application/vnd.smaf": [
    "mmf"
  ],
  "application/vnd.smart.teacher": [
    "teacher"
  ],
  "application/vnd.solent.sdkm+xml": [
    "sdkm",
    "sdkd"
  ],
  "application/vnd.spotfire.dxp": [
    "dxp"
  ],
  "application/vnd.spotfire.sfs": [
    "sfs"
  ],
  "application/vnd.stardivision.calc": [
    "sdc"
  ],
  "application/vnd.stardivision.draw": [
    "sda"
  ],
  "application/vnd.stardivision.impress": [
    "sdd"
  ],
  "application/vnd.stardivision.math": [
    "smf"
  ],
  "application/vnd.stardivision.writer": [
    "sdw",
    "vor"
  ],
  "application/vnd.stardivision.writer-global": [
    "sgl"
  ],
  "application/vnd.stepmania.package": [
    "smzip"
  ],
  "application/vnd.stepmania.stepchart": [
    "sm"
  ],
  "application/vnd.sun.xml.calc": [
    "sxc"
  ],
  "application/vnd.sun.xml.calc.template": [
    "stc"
  ],
  "application/vnd.sun.xml.draw": [
    "sxd"
  ],
  "application/vnd.sun.xml.draw.template": [
    "std"
  ],
  "application/vnd.sun.xml.impress": [
    "sxi"
  ],
  "application/vnd.sun.xml.impress.template": [
    "sti"
  ],
  "application/vnd.sun.xml.math": [
    "sxm"
  ],
  "application/vnd.sun.xml.writer": [
    "sxw"
  ],
  "application/vnd.sun.xml.writer.global": [
    "sxg"
  ],
  "application/vnd.sun.xml.writer.template": [
    "stw"
  ],
  "application/vnd.sus-calendar": [
    "sus",
    "susp"
  ],
  "application/vnd.svd": [
    "svd"
  ],
  "application/vnd.symbian.install": [
    "sis",
    "sisx"
  ],
  "application/vnd.syncml+xml": [
    "xsm"
  ],
  "application/vnd.syncml.dm+wbxml": [
    "bdm"
  ],
  "application/vnd.syncml.dm+xml": [
    "xdm"
  ],
  "application/vnd.tao.intent-module-archive": [
    "tao"
  ],
  "application/vnd.tcpdump.pcap": [
    "pcap",
    "cap",
    "dmp"
  ],
  "application/vnd.tmobile-livetv": [
    "tmo"
  ],
  "application/vnd.trid.tpt": [
    "tpt"
  ],
  "application/vnd.triscape.mxs": [
    "mxs"
  ],
  "application/vnd.trueapp": [
    "tra"
  ],
  "application/vnd.ufdl": [
    "ufd",
    "ufdl"
  ],
  "application/vnd.uiq.theme": [
    "utz"
  ],
  "application/vnd.umajin": [
    "umj"
  ],
  "application/vnd.unity": [
    "unityweb"
  ],
  "application/vnd.uoml+xml": [
    "uoml"
  ],
  "application/vnd.vcx": [
    "vcx"
  ],
  "application/vnd.visio": [
    "vsd",
    "vst",
    "vss",
    "vsw"
  ],
  "application/vnd.visionary": [
    "vis"
  ],
  "application/vnd.vsf": [
    "vsf"
  ],
  "application/vnd.wap.wbxml": [
    "wbxml"
  ],
  "application/vnd.wap.wmlc": [
    "wmlc"
  ],
  "application/vnd.wap.wmlscriptc": [
    "wmlsc"
  ],
  "application/vnd.webturbo": [
    "wtb"
  ],
  "application/vnd.wolfram.player": [
    "nbp"
  ],
  "application/vnd.wordperfect": [
    "wpd"
  ],
  "application/vnd.wqd": [
    "wqd"
  ],
  "application/vnd.wt.stf": [
    "stf"
  ],
  "application/vnd.xara": [
    "xar"
  ],
  "application/vnd.xfdl": [
    "xfdl"
  ],
  "application/vnd.yamaha.hv-dic": [
    "hvd"
  ],
  "application/vnd.yamaha.hv-script": [
    "hvs"
  ],
  "application/vnd.yamaha.hv-voice": [
    "hvp"
  ],
  "application/vnd.yamaha.openscoreformat": [
    "osf"
  ],
  "application/vnd.yamaha.openscoreformat.osfpvg+xml": [
    "osfpvg"
  ],
  "application/vnd.yamaha.smaf-audio": [
    "saf"
  ],
  "application/vnd.yamaha.smaf-phrase": [
    "spf"
  ],
  "application/vnd.yellowriver-custom-menu": [
    "cmp"
  ],
  "application/vnd.zul": [
    "zir",
    "zirz"
  ],
  "application/vnd.zzazz.deck+xml": [
    "zaz"
  ],
  "application/voicexml+xml": [
    "vxml"
  ],
  "application/widget": [
    "wgt"
  ],
  "application/winhlp": [
    "hlp"
  ],
  "application/wsdl+xml": [
    "wsdl"
  ],
  "application/wspolicy+xml": [
    "wspolicy"
  ],
  "application/x-7z-compressed": [
    "7z"
  ],
  "application/x-abiword": [
    "abw"
  ],
  "application/x-ace-compressed": [
    "ace"
  ],
  "application/x-apple-diskimage": [
    "dmg"
  ],
  "application/x-authorware-bin": [
    "aab",
    "x32",
    "u32",
    "vox"
  ],
  "application/x-authorware-map": [
    "aam"
  ],
  "application/x-authorware-seg": [
    "aas"
  ],
  "application/x-bcpio": [
    "bcpio"
  ],
  "application/x-bittorrent": [
    "torrent"
  ],
  "application/x-blorb": [
    "blb",
    "blorb"
  ],
  "application/x-bzip": [
    "bz"
  ],
  "application/x-bzip2": [
    "bz2",
    "boz"
  ],
  "application/x-cbr": [
    "cbr",
    "cba",
    "cbt",
    "cbz",
    "cb7"
  ],
  "application/x-cdlink": [
    "vcd"
  ],
  "application/x-cfs-compressed": [
    "cfs"
  ],
  "application/x-chat": [
    "chat"
  ],
  "application/x-chess-pgn": [
    "pgn"
  ],
  "application/x-chrome-extension": [
    "crx"
  ],
  "application/x-conference": [
    "nsc"
  ],
  "application/x-cpio": [
    "cpio"
  ],
  "application/x-csh": [
    "csh"
  ],
  "application/x-debian-package": [
    "deb",
    "udeb"
  ],
  "application/x-dgc-compressed": [
    "dgc"
  ],
  "application/x-director": [
    "dir",
    "dcr",
    "dxr",
    "cst",
    "cct",
    "cxt",
    "w3d",
    "fgd",
    "swa"
  ],
  "application/x-doom": [
    "wad"
  ],
  "application/x-dtbncx+xml": [
    "ncx"
  ],
  "application/x-dtbook+xml": [
    "dtb"
  ],
  "application/x-dtbresource+xml": [
    "res"
  ],
  "application/x-dvi": [
    "dvi"
  ],
  "application/x-envoy": [
    "evy"
  ],
  "application/x-eva": [
    "eva"
  ],
  "application/x-font-bdf": [
    "bdf"
  ],
  "application/x-font-ghostscript": [
    "gsf"
  ],
  "application/x-font-linux-psf": [
    "psf"
  ],
  "application/x-font-otf": [
    "otf"
  ],
  "application/x-font-pcf": [
    "pcf"
  ],
  "application/x-font-snf": [
    "snf"
  ],
  "application/x-font-ttf": [
    "ttf",
    "ttc"
  ],
  "application/x-font-type1": [
    "pfa",
    "pfb",
    "pfm",
    "afm"
  ],
  "application/x-freearc": [
    "arc"
  ],
  "application/x-futuresplash": [
    "spl"
  ],
  "application/x-gca-compressed": [
    "gca"
  ],
  "application/x-glulx": [
    "ulx"
  ],
  "application/x-gnumeric": [
    "gnumeric"
  ],
  "application/x-gramps-xml": [
    "gramps"
  ],
  "application/x-gtar": [
    "gtar"
  ],
  "application/x-hdf": [
    "hdf"
  ],
  "application/x-install-instructions": [
    "install"
  ],
  "application/x-iso9660-image": [
    "iso"
  ],
  "application/x-java-jnlp-file": [
    "jnlp"
  ],
  "application/x-latex": [
    "latex"
  ],
  "application/x-lua-bytecode": [
    "luac"
  ],
  "application/x-lzh-compressed": [
    "lzh",
    "lha"
  ],
  "application/x-mie": [
    "mie"
  ],
  "application/x-mobipocket-ebook": [
    "prc",
    "mobi"
  ],
  "application/x-ms-application": [
    "application"
  ],
  "application/x-ms-shortcut": [
    "lnk"
  ],
  "application/x-ms-wmd": [
    "wmd"
  ],
  "application/x-ms-wmz": [
    "wmz"
  ],
  "application/x-ms-xbap": [
    "xbap"
  ],
  "application/x-msaccess": [
    "mdb"
  ],
  "application/x-msbinder": [
    "obd"
  ],
  "application/x-mscardfile": [
    "crd"
  ],
  "application/x-msclip": [
    "clp"
  ],
  "application/x-msdownload": [
    "exe",
    "dll",
    "com",
    "bat",
    "msi"
  ],
  "application/x-msmediaview": [
    "mvb",
    "m13",
    "m14"
  ],
  "application/x-msmetafile": [
    "wmf",
    "wmz",
    "emf",
    "emz"
  ],
  "application/x-msmoney": [
    "mny"
  ],
  "application/x-mspublisher": [
    "pub"
  ],
  "application/x-msschedule": [
    "scd"
  ],
  "application/x-msterminal": [
    "trm"
  ],
  "application/x-mswrite": [
    "wri"
  ],
  "application/x-netcdf": [
    "nc",
    "cdf"
  ],
  "application/x-nzb": [
    "nzb"
  ],
  "application/x-pkcs12": [
    "p12",
    "pfx"
  ],
  "application/x-pkcs7-certificates": [
    "p7b",
    "spc"
  ],
  "application/x-pkcs7-certreqresp": [
    "p7r"
  ],
  "application/x-rar-compressed": [
    "rar"
  ],
  "application/x-research-info-systems": [
    "ris"
  ],
  "application/x-sh": [
    "sh"
  ],
  "application/x-shar": [
    "shar"
  ],
  "application/x-shockwave-flash": [
    "swf"
  ],
  "application/x-silverlight-app": [
    "xap"
  ],
  "application/x-sql": [
    "sql"
  ],
  "application/x-stuffit": [
    "sit"
  ],
  "application/x-stuffitx": [
    "sitx"
  ],
  "application/x-subrip": [
    "srt"
  ],
  "application/x-sv4cpio": [
    "sv4cpio"
  ],
  "application/x-sv4crc": [
    "sv4crc"
  ],
  "application/x-t3vm-image": [
    "t3"
  ],
  "application/x-tads": [
    "gam"
  ],
  "application/x-tar": [
    "tar"
  ],
  "application/x-tcl": [
    "tcl"
  ],
  "application/x-tex": [
    "tex"
  ],
  "application/x-tex-tfm": [
    "tfm"
  ],
  "application/x-texinfo": [
    "texinfo",
    "texi"
  ],
  "application/x-tgif": [
    "obj"
  ],
  "application/x-ustar": [
    "ustar"
  ],
  "application/x-wais-source": [
    "src"
  ],
  "application/x-web-app-manifest+json": [
    "webapp"
  ],
  "application/x-x509-ca-cert": [
    "der",
    "crt"
  ],
  "application/x-xfig": [
    "fig"
  ],
  "application/x-xliff+xml": [
    "xlf"
  ],
  "application/x-xpinstall": [
    "xpi"
  ],
  "application/x-xz": [
    "xz"
  ],
  "application/x-zmachine": [
    "z1",
    "z2",
    "z3",
    "z4",
    "z5",
    "z6",
    "z7",
    "z8"
  ],
  "application/xaml+xml": [
    "xaml"
  ],
  "application/xcap-diff+xml": [
    "xdf"
  ],
  "application/xenc+xml": [
    "xenc"
  ],
  "application/xhtml+xml": [
    "xhtml",
    "xht"
  ],
  "application/xml": [
    "xml",
    "xsl",
    "xsd"
  ],
  "application/xml-dtd": [
    "dtd"
  ],
  "application/xop+xml": [
    "xop"
  ],
  "application/xproc+xml": [
    "xpl"
  ],
  "application/xslt+xml": [
    "xslt"
  ],
  "application/xspf+xml": [
    "xspf"
  ],
  "application/xv+xml": [
    "mxml",
    "xhvml",
    "xvml",
    "xvm"
  ],
  "application/yang": [
    "yang"
  ],
  "application/yin+xml": [
    "yin"
  ],
  "application/zip": [
    "zip"
  ],
  "audio/adpcm": [
    "adp"
  ],
  "audio/basic": [
    "au",
    "snd"
  ],
  "audio/midi": [
    "mid",
    "midi",
    "kar",
    "rmi"
  ],
  "audio/mp4": [
    "mp4a",
    "m4a"
  ],
  "audio/mpeg": [
    "mpga",
    "mp2",
    "mp2a",
    "mp3",
    "m2a",
    "m3a"
  ],
  "audio/ogg": [
    "oga",
    "ogg",
    "spx"
  ],
  "audio/s3m": [
    "s3m"
  ],
  "audio/silk": [
    "sil"
  ],
  "audio/vnd.dece.audio": [
    "uva",
    "uvva"
  ],
  "audio/vnd.digital-winds": [
    "eol"
  ],
  "audio/vnd.dra": [
    "dra"
  ],
  "audio/vnd.dts": [
    "dts"
  ],
  "audio/vnd.dts.hd": [
    "dtshd"
  ],
  "audio/vnd.lucent.voice": [
    "lvp"
  ],
  "audio/vnd.ms-playready.media.pya": [
    "pya"
  ],
  "audio/vnd.nuera.ecelp4800": [
    "ecelp4800"
  ],
  "audio/vnd.nuera.ecelp7470": [
    "ecelp7470"
  ],
  "audio/vnd.nuera.ecelp9600": [
    "ecelp9600"
  ],
  "audio/vnd.rip": [
    "rip"
  ],
  "audio/webm": [
    "weba"
  ],
  "audio/x-aac": [
    "aac"
  ],
  "audio/x-aiff": [
    "aif",
    "aiff",
    "aifc"
  ],
  "audio/x-caf": [
    "caf"
  ],
  "audio/x-flac": [
    "flac"
  ],
  "audio/x-matroska": [
    "mka"
  ],
  "audio/x-mpegurl": [
    "m3u"
  ],
  "audio/x-ms-wax": [
    "wax"
  ],
  "audio/x-ms-wma": [
    "wma"
  ],
  "audio/x-pn-realaudio": [
    "ram",
    "ra"
  ],
  "audio/x-pn-realaudio-plugin": [
    "rmp"
  ],
  "audio/x-wav": [
    "wav"
  ],
  "audio/xm": [
    "xm"
  ],
  "chemical/x-cdx": [
    "cdx"
  ],
  "chemical/x-cif": [
    "cif"
  ],
  "chemical/x-cmdf": [
    "cmdf"
  ],
  "chemical/x-cml": [
    "cml"
  ],
  "chemical/x-csml": [
    "csml"
  ],
  "chemical/x-xyz": [
    "xyz"
  ],
  "font/opentype": [
    "otf"
  ],
  "image/bmp": [
    "bmp"
  ],
  "image/cgm": [
    "cgm"
  ],
  "image/g3fax": [
    "g3"
  ],
  "image/gif": [
    "gif"
  ],
  "image/ief": [
    "ief"
  ],
  "image/jpeg": [
    "jpeg",
    "jpg",
    "jpe"
  ],
  "image/ktx": [
    "ktx"
  ],
  "image/png": [
    "png"
  ],
  "image/prs.btif": [
    "btif"
  ],
  "image/sgi": [
    "sgi"
  ],
  "image/svg+xml": [
    "svg",
    "svgz"
  ],
  "image/tiff": [
    "tiff",
    "tif"
  ],
  "image/vnd.adobe.photoshop": [
    "psd"
  ],
  "image/vnd.dece.graphic": [
    "uvi",
    "uvvi",
    "uvg",
    "uvvg"
  ],
  "image/vnd.djvu": [
    "djvu",
    "djv"
  ],
  "image/vnd.dvb.subtitle": [
    "sub"
  ],
  "image/vnd.dwg": [
    "dwg"
  ],
  "image/vnd.dxf": [
    "dxf"
  ],
  "image/vnd.fastbidsheet": [
    "fbs"
  ],
  "image/vnd.fpx": [
    "fpx"
  ],
  "image/vnd.fst": [
    "fst"
  ],
  "image/vnd.fujixerox.edmics-mmr": [
    "mmr"
  ],
  "image/vnd.fujixerox.edmics-rlc": [
    "rlc"
  ],
  "image/vnd.ms-modi": [
    "mdi"
  ],
  "image/vnd.ms-photo": [
    "wdp"
  ],
  "image/vnd.net-fpx": [
    "npx"
  ],
  "image/vnd.wap.wbmp": [
    "wbmp"
  ],
  "image/vnd.xiff": [
    "xif"
  ],
  "image/webp": [
    "webp"
  ],
  "image/x-3ds": [
    "3ds"
  ],
  "image/x-cmu-raster": [
    "ras"
  ],
  "image/x-cmx": [
    "cmx"
  ],
  "image/x-freehand": [
    "fh",
    "fhc",
    "fh4",
    "fh5",
    "fh7"
  ],
  "image/x-icon": [
    "ico"
  ],
  "image/x-mrsid-image": [
    "sid"
  ],
  "image/x-pcx": [
    "pcx"
  ],
  "image/x-pict": [
    "pic",
    "pct"
  ],
  "image/x-portable-anymap": [
    "pnm"
  ],
  "image/x-portable-bitmap": [
    "pbm"
  ],
  "image/x-portable-graymap": [
    "pgm"
  ],
  "image/x-portable-pixmap": [
    "ppm"
  ],
  "image/x-rgb": [
    "rgb"
  ],
  "image/x-tga": [
    "tga"
  ],
  "image/x-xbitmap": [
    "xbm"
  ],
  "image/x-xpixmap": [
    "xpm"
  ],
  "image/x-xwindowdump": [
    "xwd"
  ],
  "message/rfc822": [
    "eml",
    "mime"
  ],
  "model/iges": [
    "igs",
    "iges"
  ],
  "model/mesh": [
    "msh",
    "mesh",
    "silo"
  ],
  "model/vnd.collada+xml": [
    "dae"
  ],
  "model/vnd.dwf": [
    "dwf"
  ],
  "model/vnd.gdl": [
    "gdl"
  ],
  "model/vnd.gtw": [
    "gtw"
  ],
  "model/vnd.mts": [
    "mts"
  ],
  "model/vnd.vtu": [
    "vtu"
  ],
  "model/vrml": [
    "wrl",
    "vrml"
  ],
  "model/x3d+binary": [
    "x3db",
    "x3dbz"
  ],
  "model/x3d+vrml": [
    "x3dv",
    "x3dvz"
  ],
  "model/x3d+xml": [
    "x3d",
    "x3dz"
  ],
  "text/cache-manifest": [
    "appcache",
    "manifest"
  ],
  "text/calendar": [
    "ics",
    "ifb"
  ],
  "text/coffeescript": [
    "coffee"
  ],
  "text/css": [
    "css"
  ],
  "text/csv": [
    "csv"
  ],
  "text/hjson": [
    "hjson"
  ],
  "text/html": [
    "html",
    "htm"
  ],
  "text/jade": [
    "jade"
  ],
  "text/jsx": [
    "jsx"
  ],
  "text/less": [
    "less"
  ],
  "text/n3": [
    "n3"
  ],
  "text/plain": [
    "txt",
    "text",
    "conf",
    "def",
    "list",
    "log",
    "in",
    "ini"
  ],
  "text/prs.lines.tag": [
    "dsc"
  ],
  "text/richtext": [
    "rtx"
  ],
  "text/sgml": [
    "sgml",
    "sgm"
  ],
  "text/stylus": [
    "stylus",
    "styl"
  ],
  "text/tab-separated-values": [
    "tsv"
  ],
  "text/troff": [
    "t",
    "tr",
    "roff",
    "man",
    "me",
    "ms"
  ],
  "text/turtle": [
    "ttl"
  ],
  "text/uri-list": [
    "uri",
    "uris",
    "urls"
  ],
  "text/vcard": [
    "vcard"
  ],
  "text/vnd.curl": [
    "curl"
  ],
  "text/vnd.curl.dcurl": [
    "dcurl"
  ],
  "text/vnd.curl.mcurl": [
    "mcurl"
  ],
  "text/vnd.curl.scurl": [
    "scurl"
  ],
  "text/vnd.dvb.subtitle": [
    "sub"
  ],
  "text/vnd.fly": [
    "fly"
  ],
  "text/vnd.fmi.flexstor": [
    "flx"
  ],
  "text/vnd.graphviz": [
    "gv"
  ],
  "text/vnd.in3d.3dml": [
    "3dml"
  ],
  "text/vnd.in3d.spot": [
    "spot"
  ],
  "text/vnd.sun.j2me.app-descriptor": [
    "jad"
  ],
  "text/vnd.wap.wml": [
    "wml"
  ],
  "text/vnd.wap.wmlscript": [
    "wmls"
  ],
  "text/vtt": [
    "vtt"
  ],
  "text/x-asm": [
    "s",
    "asm"
  ],
  "text/x-c": [
    "c",
    "cc",
    "cxx",
    "cpp",
    "h",
    "hh",
    "dic"
  ],
  "text/x-component": [
    "htc"
  ],
  "text/x-fortran": [
    "f",
    "for",
    "f77",
    "f90"
  ],
  "text/x-handlebars-template": [
    "hbs"
  ],
  "text/x-java-source": [
    "java"
  ],
  "text/x-lua": [
    "lua"
  ],
  "text/x-markdown": [
    "markdown",
    "md",
    "mkd"
  ],
  "text/x-nfo": [
    "nfo"
  ],
  "text/x-opml": [
    "opml"
  ],
  "text/x-pascal": [
    "p",
    "pas"
  ],
  "text/x-sass": [
    "sass"
  ],
  "text/x-scss": [
    "scss"
  ],
  "text/x-setext": [
    "etx"
  ],
  "text/x-sfv": [
    "sfv"
  ],
  "text/x-uuencode": [
    "uu"
  ],
  "text/x-vcalendar": [
    "vcs"
  ],
  "text/x-vcard": [
    "vcf"
  ],
  "text/yaml": [
    "yaml",
    "yml"
  ],
  "video/3gpp": [
    "3gp"
  ],
  "video/3gpp2": [
    "3g2"
  ],
  "video/h261": [
    "h261"
  ],
  "video/h263": [
    "h263"
  ],
  "video/h264": [
    "h264"
  ],
  "video/jpeg": [
    "jpgv"
  ],
  "video/jpm": [
    "jpm",
    "jpgm"
  ],
  "video/mj2": [
    "mj2",
    "mjp2"
  ],
  "video/mp2t": [
    "ts"
  ],
  "video/mp4": [
    "mp4",
    "mp4v",
    "mpg4"
  ],
  "video/mpeg": [
    "mpeg",
    "mpg",
    "mpe",
    "m1v",
    "m2v"
  ],
  "video/ogg": [
    "ogv"
  ],
  "video/quicktime": [
    "qt",
    "mov"
  ],
  "video/vnd.dece.hd": [
    "uvh",
    "uvvh"
  ],
  "video/vnd.dece.mobile": [
    "uvm",
    "uvvm"
  ],
  "video/vnd.dece.pd": [
    "uvp",
    "uvvp"
  ],
  "video/vnd.dece.sd": [
    "uvs",
    "uvvs"
  ],
  "video/vnd.dece.video": [
    "uvv",
    "uvvv"
  ],
  "video/vnd.dvb.file": [
    "dvb"
  ],
  "video/vnd.fvt": [
    "fvt"
  ],
  "video/vnd.mpegurl": [
    "mxu",
    "m4u"
  ],
  "video/vnd.ms-playready.media.pyv": [
    "pyv"
  ],
  "video/vnd.uvvu.mp4": [
    "uvu",
    "uvvu"
  ],
  "video/vnd.vivo": [
    "viv"
  ],
  "video/webm": [
    "webm"
  ],
  "video/x-f4v": [
    "f4v"
  ],
  "video/x-fli": [
    "fli"
  ],
  "video/x-flv": [
    "flv"
  ],
  "video/x-m4v": [
    "m4v"
  ],
  "video/x-matroska": [
    "mkv",
    "mk3d",
    "mks"
  ],
  "video/x-mng": [
    "mng"
  ],
  "video/x-ms-asf": [
    "asf",
    "asx"
  ],
  "video/x-ms-vob": [
    "vob"
  ],
  "video/x-ms-wm": [
    "wm"
  ],
  "video/x-ms-wmv": [
    "wmv"
  ],
  "video/x-ms-wmx": [
    "wmx"
  ],
  "video/x-ms-wvx": [
    "wvx"
  ],
  "video/x-msvideo": [
    "avi"
  ],
  "video/x-sgi-movie": [
    "movie"
  ],
  "video/x-smv": [
    "smv"
  ],
  "x-conference/x-cooltalk": [
    "ice"
  ]
}

views/error.jade → src/doctor/views/error.jade


views/index.jade → src/doctor/views/index.jade


views/layout.jade → src/doctor/views/layout.jade


+ 0 - 1
views/error.html

@ -1 +0,0 @@
{"errno":"404"}

+ 0 - 11
views/index.html

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
  </body>
</html>