This commit is contained in:
satori 2021-11-28 22:50:56 +08:00
parent 2f2568e46f
commit e01dc765da
4 changed files with 339 additions and 232 deletions

View File

@ -19,11 +19,56 @@ node index
pm2 start node --name shizukana -- index
```
## doc
## 特征
1. 抽象化以扩大泛用性
2. 无须配置依赖项的
### 程序结构
1. 抽象细分原则模块化
2. 免配置原则
3. 分层原则
4. 分布式原则
5. 解耦扩容原则
#### 接口层
对外接口允许多服务终端, 服务集群皆为公开阵列
允许即刻熔断
1. 每地区一台或多台伺服器, 伺服器只是节点, 不属于中心
2. 具体落盘数据格式与冗余如何与接口节点无关
则终端连接伺服器通过可用时期节点列表缓存
终端连接信任域列表, 使用证书校验节点有效性
终端从信任域获取列表, 并将列表缓存, 特定更新周期或事件对伺服器列表更新
/全功能伺服器
任意节点都是伺服器之一, 没有分级且发挥节点所有效能
节点给出可用节点列表,
节点之间如何通信实现可用列表和数据的同步?
1. 主动从节点阵列获取
2. 被动从节点阵列收取
事件模型中, 当伺服节点产生事件, 向有限域传递事件
但由于传递过程时间有滞后, 无法保障事件发生顺序准确性
因而, 有因果勾连的事物须在唯一时空与媒介发生
不同节点并不能属于同一时空, 且节点本以在不同时空降低滞后性效用而存在, 以及隔离
小团体伺服节点在不同伺服器之间无感转移(媒介)
1. 媒介作为通讯会话, 可轻易建立与销毁
2. 伺服器作为沟通节点提供连接,如 websocket
3. 在节点与节点之间建立通信, 为媒介
4. 多个节点可能同时通信
5. 但效率不如此次会话所有终端连接同一伺服节点
6. 且算力是定量, 无论节点多少
### 使用示例

420
index.js
View File

@ -1,86 +1,59 @@
import nedb from 'nedb'
import express from 'express'
import expressWs from 'express-ws'
import session from 'express-session'
import random from 'string-random'
import nedb from 'nedb'
import express from 'express'
import expressWs from 'express-ws'
import session from 'express-session'
import sessionDb from 'express-session-nedb'
import random from 'string-random'
import formidable from 'formidable'
import md5 from 'md5-node'
import md5 from 'md5-node'
process.on('SIGINT', function() {
console.log('Got SIGINT. Press Control-D/Control-C to exit.')
process.exit(0)
})
//process.on('SIGINT', function () {
// console.log('Got SIGINT. Press Control-D/Control-C to exit.')
// process.exit(0)
//})
const app = expressWs(express()).app
const databases = new Map() // 所有数据库
const wsstores = new Map() // 所有 websocket 连接
//const wsstores = new Map() // 所有 websocket 连接
const db = (name) => (databases.get(name) || function(){
let database = new nedb({filename:`./data/db/${name}.db`,autoload:true,timestampData:true})
const db = (name) => (databases.get(name) || function () {
let database = new nedb({ filename: `./data/db/${name}.db`, autoload: true, timestampData: true })
databases.set(name, database)
return database
}())
// 通道: 自动构建 ws 列表
const wsstore = name => (wsstores.get(name) || function() {
let list = new Map()
wsstores.set(name, list)
return list
}())
// const wsstore = name => (wsstores.get(name) || function () {
// let list = new Map()
// wsstores.set(name, list)
// return list
// }())
// 组件: 要求登录 (普通成员路由参数附上uid以分离权限)
const online = function(req, res, next) {
const session_store = sessionDb(session, db('session'))
// 登录验证
const online = function (req, res, next) {
if (!req.session.account) return res.status(401).send('未登录')
if (req.session.account.gid != 1) req.params.uid = req.session.account.uid
next()
}
const SessionStore = function (session) {
function NedbStore(options, cb) {
var callback = cb || function () {};
this.db = db('session');
this.db.loadDatabase(callback);
}
NedbStore.prototype.__proto__ = session.Store.prototype;
NedbStore.prototype.get = function (sid, callback) {
this.db.findOne({ sid: sid }, function (err, sess) {
if (err) { return callback(err); }
if (!sess) { return callback(null, null); }
return callback(null, sess.data);
});
};
NedbStore.prototype.set = function (sid, data, callback) {
this.db.update({ sid: sid }, { sid: sid, data: data }, { multi: false, upsert: true }, function (err) {
return callback(err);
});
};
NedbStore.prototype.destroy = function (sid, callback) {
this.db.remove({ sid: sid }, { multi: false }, function (err) {
return callback(err);
});
};
return NedbStore;
};
const session_store = new (SessionStore(session))
// 列表计量
const count_load = async (name, query) => await new Promise(resolve => db(name).count(query, (err, count) => resolve(count)))
// 条件查询
const list_load = async (name, query) => await new Promise(resolve => db(name).find(query, function(err, docs) {
return resolve(docs.Map((item, index) => Object.assign({}, {_id: item.id})))
const list_load = async (name, query) => await new Promise(resolve => db(name).find(query, function (err, docs) {
return resolve(docs.Map((item, index) => Object.assign({}, { _id: item.id })))
}))
const user_load = async (_id) => await new Promise(resolve => db('user').findOne({_id}, function(err, doc) {
const user_load = async (_id) => await new Promise(resolve => db('user').findOne({ _id }, function (err, doc) {
if (!doc) return resolve(doc)
let { salt, password, mobile, email, ...user } = doc
return resolve(user)
}))
// 特定类型查询时参数特性: message
const message = async function(req, res, next) {
const message = async function (req, res, next) {
if (req.query.unread) req.query.unread = (req.query.unread === 'true')
if (req.query.archive) req.query.archive = (req.query.archive === 'true')
if (req.query.to) {
@ -91,153 +64,35 @@ const message = async function(req, res, next) {
}
}
// 标准列表
const ListView = async function(req, res, next) {
let { pagesize, page, count, like, post, tid, top, uid, user, ...query } = req.query
if (tid) query.tid = Number(tid) // 某些查询参数需要转换类型
if (top) query.top = Number(top) // 某些查询参数需要转换类型
if (uid && uid !== req.session?.account?.uid) query.public = true // 如果查询条件限定为自己的, 则不用限制范围到公开的
page = Number(page) || 1 // 默认页码1
pagesize = Number(pagesize) || 20 // 默认分页20
let skip = (page - 1) * pagesize // 截取点
// 基于登录状态的查询, 查询点赞过的, 查询评论过的
if (req.session?.account?.uid) {
if (like) query.$or = await list_load('like',{name:req.params.name, uid:req.session.account.uid})
if (post) query.$or = await list_load('post',{name:req.params.name, uid:req.session.account.uid})
}
if (count) {
await new Promise(resolve => db(req.params.name).count(query, function(err, count) {
res.header('count', count)
res.header('page', page)
res.header('pages', Math.ceil(count/pagesize))
res.header('pagesize', pagesize)
resolve()
}))
}
db(req.params.name).find(query).skip(skip).limit(pagesize).sort({createdAt: -1}).exec(async function(err, docs) {
for (let item of docs) {
item.posts = await count_load('post', {name:req.params.name, id:item._id}) // 附加评论数量
item.likes = await count_load('like', {name:req.params.name, id:item._id}) // 附加点赞数量
}
if (!req.params.name) {
docs.forEach(async item => {
let { salt, password, mobile, email, ...user } = item
item = user
})
}
if (user && req.params.name !== 'user') for (let item of docs) {
item.user = await user_load(item.uid)
}
res.json(docs)
})
}
const OneView = async function(req, res, next) {
db(req.params.name).findOne({_id: req.params._id}, async function(err, doc) {
if (err || !doc) return res.status(404).send('目标资源不存在')
if (!doc.public && doc.uid !== req.session?.account?.uid) {
return res.status(403).send('没有权限读取')
}
// 附加用户信息
if (req.query.user) doc.user = await user_load(doc.uid)
res.send(doc)
})
}
const object_create = async function(req, res, next) {
if (req.session?.account?.gid != 1) {
delete req.body._id // 普通用户禁止设置, 权限
delete req.body.uid // 普通用户禁止设置, 权限
delete req.body.top // 普通用户禁止设置, 权限
delete req.body.user // 普通用户禁止设置, 计算
delete req.body.createdAt // 普通用户禁止设置, 自动
delete req.body.updatedAt // 普通用户禁止设置, 自动
delete req.body.views // 普通用户禁止设置, 统计
delete req.body.posts // 普通用户禁止设置, 统计
delete req.body.likes // 普通用户禁止设置, 统计
delete req.body.files // 普通用户禁止设置, 统计
}
if (!req.params.name) {
req.body.name = req.body.name || random(12) // 默认用户名
req.body.avatar = req.body.avatar || '' // 默认用户头像
req.body.gid = (await count_load('user', {})) ? 0 : 1 // 默认是管理员为首个注册用户
req.body.salt = random(32) // 密码加盐
req.body.password = md5(req.body.password + req.body.salt) // 必要设置密码
req.body.public = true // 默认公开
} else if (req.session?.account?.uid) {
req.body.uid = req.session.account.uid // 为发表对象附上作者ID
req.body.public = true // 默认公开
}
db(req.params.name ?? 'user').insert(req.body, function(err, doc) {
doc ? res.json(doc) : res.status(500).send('创建失败')
})
}
const session_list = (req, res) => session_store.db.find({ "data.account.uid": req.session.account.uid }, function(err, docs) {
const session_list = (req, res) => session_store.db.find({ "data.account.uid": req.session.account.uid }, function (err, docs) {
err ? res.status(500).send('错误') : res.json(docs)
})
const session_create = (req, res) => db('user').findOne({name: req.body.name}, function(err, doc) {
const session_create = (req, res) => db('user').findOne({ name: req.body.name }, function (err, doc) {
if (!doc) return res.status(400).send('账户不存在')
if (md5(req.body.password + doc.salt) !== doc.password) return res.status(400).send('密码错误')
req.session.regenerate(function(err) {
req.session.regenerate(function (err) {
req.session.account = { uid: doc._id, gid: doc.gid ?? 0 }
let { salt, password, ...user} = doc
let { salt, password, ...user } = doc
res.json(user)
})
})
const session_delete_self = (req, res) => req.session.destroy(function(err) {
err ? res.status(500).send('错误') : res.send('退出登录')
})
const sessionDeleteSelf = function (req, res) {
return req.session.destroy(function (err) {
return res.status(err ? 500 : 200).send(err ? '错误' : '退出登录')
})
}
// TODO: 必须是自己的 UID
const session_delete = (req, res) => req.sessionStore.destroy(req.params.sid, function(err) {
const session_delete = (req, res) => req.sessionStore.destroy(req.params.sid, function (err) {
err ? res.status(500).send('错误') : res.send('退出登录')
})
const home = (req, res) => res.send(`<DOCTYPE html><p> Hello World</p>`)
const files_delete = (req, res) => res.status(400).send('拒绝操作')
const files_upload = (req, res) => {
formidable({ multiples: true, uploadDir: 'data/file', keepExtensions: true, maxFieldsSize: 200 * 1024 * 1024 }).parse(req, function(err, fields, files) {
let list = []
for (let key in files) {
let arr = Array.isArray(files[key]) ? files[key] : [files[key]]
arr.forEach(({size, path, name, type}) => list.push({size, path, name, type}))
}
res.json(list)
//db(req.params.name).update({_id:req.params._id}, {$addToSet: {file:list}}, {}, function (err, count, docs) {
// if (!count) return res.status(404).send('目标挂载对象不存在')
// res.send(docs)
//})
})
}
const object_remove = async function(req, res, next) {
// TODO: 账户操作 会话操作 收藏操作
if (!req.params.name) {
if (req.session.account.gid !== 1 && req.session.account.uid !== req.params.id) {
return res.status(400).send('没有权限删除此账户')
}
if (await count_load('account', {_id: req.params.id, gid: 1}) === 1) {
return res.status(400).send('不可以删除唯一的管理员账户')
}
}
// TODO: 先移除依赖数据 like post...
let {name, ...query} = req.params
db(name).remove(query, function(err, count) {
count ? res.send('删除成功') : res.status(403).send('删除失败')
// TODO: 当对象被删除时通过此连接通知所有在线终端
})
}
// app.use('/like', online, admin)
const profile = function(req, res) {
return db('user').findOne({_id: req.session.account.uid}, function(err, doc) {
const profile = function (req, res) {
return db('user').findOne({ _id: req.session.account.uid }, function (err, doc) {
if (err) return res.status(401).send('尚未登录')
delete doc.salt
delete doc.password
@ -245,23 +100,200 @@ const profile = function(req, res) {
})
}
// 当你需要指定权限 express-power
// 权力与会话同级, 但不是每个会话都复制一份
// 有限会话授权, 如果授权者权力过期, 需要向下传播, 即使用的仍旧是授权者所拥有的权柄
// 列表对象
const object_list = async function (req, res) {
let { pagesize, page, count, like, post, tid, top, uid, user, ...query } = req.query
if (tid) query.tid = Number(tid) // 某些查询参数需要转换类型
if (top) query.top = Number(top) // 某些查询参数需要转换类型
if (uid && uid !== req.session?.account?.uid) query.public = true // 如果查询条件限定为自己的, 则不用限制范围到公开的
page = Number(page) || 1 // 默认页码1
pagesize = Number(pagesize) || 20 // 默认分页20
let skip = (page - 1) * pagesize // 截取点
// 登录状态时, 查询自己点赞过的和评论过的
if (req.session?.account?.uid) {
if (like) query.$or = await list_load('like', { attach: req.params.name, uid: req.session.account.uid })
if (post) query.$or = await list_load('post', { attach: req.params.name, uid: req.session.account.uid })
}
// 要求附带统计信息
if (count) await new Promise(resolve => db(req.params.name).count(query, function (err, count) {
res.header('count', count)
res.header('page', page)
res.header('pages', Math.ceil(count / pagesize))
res.header('pagesize', pagesize)
resolve()
}))
return db(req.params.name).find(query).skip(skip).limit(pagesize).sort({ createdAt: -1 }).exec(async function (err, docs) {
return res.json(await Promise.all(docs.map(async item => {
item.posts = await count_load('post', { attach: req.params.name, aid: item._id }) // 附加评论数量
item.likes = await count_load('like', { attach: req.params.name, aid: item._id }) // 附加点赞数量
item.user = await user_load(item.uid) // 附加用户信息(user对象没有作者)
if (req.params.name === 'user') {
delete item.salt
delete item.password
delete item.mobile
delete item.email
}
return item
})))
})
}
// 创建对象
const object_create = async function (req, res) {
if (req.session?.account?.gid != 1) {
delete req.body._id // 游客和普通用户禁止设置, 权限
delete req.body.uid // 游客和普通用户禁止设置, 权限
delete req.body.top // 游客和普通用户禁止设置, 权限
delete req.body.user // 游客和普通用户禁止设置, 计算
delete req.body.createdAt // 游客和普通用户禁止设置, 自动
delete req.body.updatedAt // 游客和普通用户禁止设置, 自动
delete req.body.views // 游客和普通用户禁止设置, 统计
delete req.body.posts // 游客和普通用户禁止设置, 统计
delete req.body.likes // 游客和普通用户禁止设置, 统计
delete req.body.files // 游客和普通用户禁止设置, 统计
}
// 如果创建对象是用户作一些特殊处理
if (req.params.name === 'user') {
req.body.name = req.body.name || random(12) // 默认用户名(检查用户名是否可用)
req.body.avatar = req.body.avatar || '' // 默认用户头像
req.body.gid = (await count_load('user', {})) ? 0 : 1 // 默认是管理员为首个注册用户
req.body.salt = random(32) // 密码加盐
req.body.password = md5(req.body.password + req.body.salt) // 必要设置密码
req.body.public = true // 默认公开
} else {
req.body.uid = req.session.account.uid // 为发表对象附上作者ID
req.body.public = true // 默认公开
req.body.views = 0 // 再生计数
}
// 如果是挂载对象到指定目标
if (req.body.attach && req.body.aid) {
let count = await count_load(req.body.attach, { _id: req.body.aid })
if (!count) return res.status(404).send('目标挂载对象不存在')
}
// 写入对象
return db(req.params.name).insert(req.body, async function (err, doc) {
if (!doc) return res.status(500).send('创建失败')
if (req.params.name !== 'user') doc.user = await user_load(doc.uid)
return res.json(doc)
})
}
// 删除对象
const object_remove = function (req, res) {
return db(req.params.name).findOne({ _id: req.params._id }, async function (err, doc) {
if (doc) return res.status(404).send('目标对象不存在')
// 如果是删除用户作一些特殊处理
if (req.params.name === 'user') {
if (req.session.account.gid !== 1 && req.session.account.uid !== doc._id) {
return res.status(400).send('没有权限删除此账户')
}
if (await count_load('account', { _id: req.params.id, gid: 1 }) === 1) {
return res.status(400).send('不可以删除唯一的管理员账户')
}
} else {
if (req.session.account.gid !== 1 && req.session.account.uid !== doc.uid) {
return res.status(403).send('没有权限删除此对象')
}
}
// 处理掉一些附属对象
return db(req.params.name).remove({ _id: req.params._id }, function (err, count) {
return count ? res.send('删除成功') : res.status(403).send('删除失败')
// TODO: 当对象被删除时通过此连接通知所有在线终端
})
})
}
// 读取对象
const object_load = function (req, res) {
return db(req.params.name).findOne({ _id: req.params._id }, async function (err, doc) {
if (!doc) return res.status(404).send('目标资源不存在')
if (!doc.public && doc.uid !== session?.account?.uid) return res.status(403).send('没有权限读取')
db(req.params.name).update({ _id }, { $set: { views: doc.views ? doc.views + 1 : 1 } })
return res.status(200).json({ user: await user_load(doc.uid), ...doc })
})
}
// 修改对象
const object_patch = function (req, res) {
return db(req.params.name).findOne({ _id: req.params._id }, function (err, doc) {
if (!doc) return res.status(404).send('目标对象不存在')
// 如果是 user 做一些特殊处理
if (res.params.name === 'user') {
if (res.session.account.uid !== doc._id && res.session.account.gid !== 1) {
return res.status(403).send('没有权限修改账户')
}
if (res.body.gid && res.session.account.gid !== 1) {
return res.status(403).send('没有权限修改权限')
}
if (res.body.password) {
req.body.salt = random(32) // 密码加盐
res.body.password = md5(req.body.password + req.body.salt) // 设置密码
}
if (res.body.name) {
// 检查用户名是否可用
}
} else {
if (res.session.account.uid !== doc.uid && res.session.account.gid !== 1) {
return res.status(403).send('没有权限修改对象')
}
if (res.body.uid && res.session.account.gid !== 1) {
return res.status(403).send('没有权限修改归属')
}
}
return db(req.params.name).update({ _id: req.params._id }, data, function (err, count) {
if (!count) return res.status(500).send('修改失败')
return res.send('修改成功')
})
})
}
// 附件上传
const file_upload = function (req, res) {
return db(req.params.name).findOne({ _id: req.params._id }, function (err, doc) {
if (!doc) return res.status(404).send('目标对象不存在')
if (req.session.account.uid !== doc.uid && req.session.account.gid !== 1) {
return res.status(403).send('没有权限上传')
}
return formidable({ multiples: true, uploadDir: 'data/file', keepExtensions: true, maxFieldsSize: 200 * 1024 * 1024 }).parse(req, function (err, fields, files) {
let list = []
for (let key in files) {
(Array.isArray(files[key]) ? files[key] : [files[key]]).map(({ filepath, mimetype, mtime, newFilename, originalFilename, size }) => list.push({
filepath, mimetype, mtime, newFilename, originalFilename, size
}))
}
return db(req.params.name).update({ _id: req.params._id }, { $addToSet: { file: { $each: list } } }, function (err, count) {
if (!count) return res.status(500).send('附件挂载对象失败')
console.log(list)
return res.json(list)
})
})
})
}
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(session({secret: 'shizukana', name:'sid', resave: false, saveUninitialized: false, cookie: { maxAge: 180 * 24 * 3600000 }, store: session_store}))
app.use(session({ secret: 'shizukana', name: 'sid', resave: false, saveUninitialized: false, cookie: { maxAge: 180 * 24 * 3600000 }, store: session_store }))
app.use('/data/file/', express.static('data/file'))
app.route('/').get(home)
app.route('/user').post(object_create)
app.route('/account').get(online, profile)
app.route('/session').get(online, session_list).post(session_create).delete(online, session_delete_self)
app.route('/session/:sid').delete(online, session_delete) // 会话
app.route('/:name').get(ListView).post(online, object_create) // 列表
app.route('/:name/:_id').get(OneView).put().patch().delete(online, object_remove) // 对象
app.route('/:name/:_id/files').post(online, files_upload) // 附件
app.route('/:name/:_id/files/:id').delete(online, files_delete) // 附件
app.route('/session').get(online, session_list).post(session_create).delete(online, sessionDeleteSelf)
app.route('/session/:sid').delete(online, session_delete)
app.route('/:name').get(object_list).post(online, object_create)
app.route('/:name/:_id').get(object_load).post(online, file_upload).put().patch(online, object_patch).delete(online, object_remove)
app.listen(2333)

View File

@ -7,11 +7,11 @@
"author": "satori <huan0016@gmail.com>",
"license": "MIT",
"dependencies": {
"connect-nedb-session": "^0.0.3",
"express": "^4.17.1",
"express-session": "^1.17.2",
"express-session-nedb": "^1.0.1",
"express-ws": "^5.0.2",
"formidable": "^1.2.2",
"formidable": "^2.0.1",
"md5-node": "^1.0.1",
"nedb": "^1.8.0",
"string-random": "^0.1.3"

View File

@ -15,7 +15,12 @@ array-flatten@1.1.1:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
async@0.2.10, async@~0.2.8:
asap@^2.0.0:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
async@0.2.10:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
@ -48,13 +53,6 @@ bytes@3.1.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
connect-nedb-session@^0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/connect-nedb-session/-/connect-nedb-session-0.0.3.tgz#e10f642d3d604f609bb23a450021dd1f0579c5ac"
integrity sha1-4Q9kLT1gT2CbsjpFACHdHwV5xaw=
dependencies:
nedb "0.0.6"
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
@ -104,6 +102,14 @@ destroy@~1.0.4:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
dezalgo@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456"
integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=
dependencies:
asap "^2.0.0"
wrappy "1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -124,6 +130,11 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
express-session-nedb@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/express-session-nedb/-/express-session-nedb-1.0.1.tgz#0fbb148d24db24bcc7b33b1c478f6fddca04c851"
integrity sha512-06J0wSG+GRx0cThEtlmwPUIByjH0zHB0KqRe3mutow7IBsaFdOmHIFOeVnASxOIBahhAaglaeCxo+Sat8eo1Ow==
express-session@^1.17.2:
version "1.17.2"
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.2.tgz#397020374f9bf7997f891b85ea338767b30d0efd"
@ -194,10 +205,15 @@ finalhandler@~1.1.2:
statuses "~1.5.0"
unpipe "~1.0.0"
formidable@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9"
integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==
formidable@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff"
integrity sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==
dependencies:
dezalgo "1.0.3"
hexoid "1.0.0"
once "1.4.0"
qs "6.9.3"
forwarded@0.2.0:
version "0.2.0"
@ -209,6 +225,11 @@ fresh@0.5.2:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
hexoid@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
http-errors@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
@ -266,9 +287,9 @@ lie@3.1.1:
immediate "~3.0.5"
localforage@^1.3.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1"
integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==
version "1.10.0"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4"
integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==
dependencies:
lie "3.1.1"
@ -292,17 +313,17 @@ methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
mime-db@1.48.0:
version "1.48.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d"
integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==
mime-db@1.51.0:
version "1.51.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
mime-types@~2.1.24:
version "2.1.31"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b"
integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==
version "2.1.34"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24"
integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==
dependencies:
mime-db "1.48.0"
mime-db "1.51.0"
mime@1.6.0:
version "1.6.0"
@ -331,14 +352,6 @@ ms@2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
nedb@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/nedb/-/nedb-0.0.6.tgz#a2e6c02cb2fcacf91245b02431f44cf1664aa85f"
integrity sha1-oubALLL8rPkSRbAkMfRM8WZKqF8=
dependencies:
async "~0.2.8"
underscore "~1.4.4"
nedb@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/nedb/-/nedb-1.8.0.tgz#0e3502cd82c004d5355a43c9e55577bd7bd91d88"
@ -367,6 +380,13 @@ on-headers@~1.0.2:
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
once@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@ -390,6 +410,11 @@ qs@6.7.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@6.9.3:
version "6.9.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e"
integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==
random-bytes@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
@ -509,7 +534,12 @@ vary@~1.1.2:
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@^7.4.6:
version "7.5.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74"
integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==
version "7.5.5"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881"
integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==