!UNSTABLE 优化数据库结构,为domain功能准备

This commit is contained in:
masnn committed 2020-05-29 13:05:52 +08:00
1 parent 7d27e56018
commit fff2172c38
40 files changed
+1411 -1012

No files matched your search

+2 -2
View File
@@ -15,10 +15,10 @@ function del(ip) {
return coll.deleteOne({ _id: ip });
}
function index() {
function ensureIndexes() {
return coll.createIndex('expireAt', { expireAfterSeconds: 0 });
}
global.Hydro.model.blacklist = module.exports = {
add, get, del, index,
add, get, del, ensureIndexes,
};
+5 -5
View File
@@ -1,10 +1,10 @@
const perm = require('../permission');
const BUILTIN_ROLES = [
{ _id: 'guest', perm: perm.PERM_BASIC },
{ _id: 'default', perm: perm.PERM_DEFAULT, count: 0 },
{ _id: 'root', perm: perm.PERM_ALL, count: 0 },
];
const BUILTIN_ROLES = {
guest: { perm: perm.PERM_BASIC },
default: { perm: perm.PERM_DEFAULT },
admin: { perm: perm.PERM_ADMIN },
};
const DEFAULT_NODES = {
探索: [
+53 -42
View File
@@ -7,10 +7,7 @@ const {
const { PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD } = require('../permission');
const validator = require('../lib/validator');
const ranked = require('../lib/rank');
const db = require('../service/db');
const coll = db.collection('contest');
const collStatus = db.collection('contest.status');
const document = require('./document');
const acm = {
TEXT: 'ACM/ICPC',
@@ -197,6 +194,7 @@ const RULES = {
*/
/**
* @param {string} domainId
* @param {string} title
* @param {string} content
* @param {number} owner
@@ -207,7 +205,7 @@ const RULES = {
* @param {object} data
* @returns {ObjectID} tid
*/
async function add(title, content, owner, rule,
function add(domainId, title, content, owner, rule,
beginAt = new Date(), endAt = new Date(), pids = [], data = {}) {
validator.checkTitle(title);
validator.checkContent(content);
@@ -217,15 +215,18 @@ async function add(title, content, owner, rule,
content, owner, title, rule, beginAt, endAt, pids, attend: 0,
});
this.RULES[rule].check(data);
const res = await coll.insertOne(data);
return res.insertedId;
return document.add(domainId, content, owner, document.TYPE_CONTEST, null, null, null, {
...data, title, rule, beginAt, endAt, pids, attend: 0,
});
}
/**
* @param {string} domainId
* @param {ObjectID} tid
* @param {object} $set
* @returns {Tdoc} tdoc after modification
*/
async function edit(tid, $set) {
async function edit(domainId, tid, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkIntro($set.content);
if ($set.rule) { if (!this.RULES[$set.rule]) throw new ValidationError('rule'); }
@@ -234,53 +235,55 @@ async function edit(tid, $set) {
throw new ValidationError('beginAt', 'endAt');
}
}
const tdoc = await coll.findOne({ tid });
const tdoc = await document.get(domainId, document.TYPE_CONTEST, tid);
if (!tdoc) throw new ContestNotFoundError(tid);
this.RULES[$set.rule || tdoc.rule].check(Object.assign(tdoc, $set));
await coll.findOneAndUpdate({ tid }, { $set });
return tdoc;
return await document.set(domainId, document.TYPE_CONTEST, tid, $set);
}
/**
* @param {string} domainId
* @param {ObjectID} tid
* @returns {Tdoc}
*/
async function get(tid) {
const tdoc = await coll.findOne({ _id: tid });
async function get(domainId, tid) {
const tdoc = await document.get(domainId, document.TYPE_CONTEST, tid);
if (!tdoc) throw new ContestNotFoundError(tid);
return tdoc;
}
async function updateStatus(tid, uid, rid, pid, accept, score) {
const tdoc = await get(tid);
const tsdoc = await collStatus.findOneAndUpdate({ tid: tdoc._id, uid }, {
$push: {
journal: {
rid, pid, accept, score,
},
},
$inc: { rev: 1 },
}, { upsert: true });
async function updateStatus(domainId, tid, uid, rid, pid, accept, score) {
await get(domainId, tid);
const tsdoc = await document.revPushStatus(domainId, document.TYPE_CONTEST, tid, uid, 'journal', {
rid, pid, accept, score,
});
if (!tsdoc.value.attend) throw new ContestNotAttendedError(tid, uid);
}
function getStatus(tid, uid) {
return collStatus.findOne({ tid, uid });
function getStatus(domainId, tid, uid) {
return document.getStatus(domainId, tid, uid);
}
async function getListStatus(uid, tids) {
async function getListStatus(domainId, uid, tids) {
const r = {};
// eslint-disable-next-line no-await-in-loop
for (const tid of tids) r[tid] = await getStatus(tid, uid);
for (const tid of tids) r[tid] = await getStatus(domainId, tid, uid);
return r;
}
async function attend(tid, uid) {
async function attend(domainId, tid, uid) {
try {
await collStatus.insertOne({ tid, uid, attend: 1 });
await document.cappedIncStatus(domainId, document.TYPE_CONTEST, tid, uid, 'attend', 1, 0, 1);
} catch (e) {
throw new ContestAlreadyAttendedError(tid, uid);
}
await coll.findOneAndUpdate({ _id: tid }, { $inc: { attend: 1 } });
await document.inc(domainId, document, document.TYPE_CONTEST, tid, 'attend', 1);
return {};
}
function getMultiStatus(query) {
return collStatus.find(query);
return document.getMultiStatus(query);
}
function isNew(tdoc, days = 1) {
const now = new Date().getTime();
const readyAt = tdoc.beginAt.getTime();
@@ -319,29 +322,37 @@ const ContestHandlerMixin = (c) => class extends c {
return false;
}
async getScoreboard(tid, isExport = false) {
const tdoc = await get(tid);
async getScoreboard(domainId, tid, isExport = false) {
const tdoc = await get(domainId, tid);
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
const tsdocs = await getMultiStatus(tid).sort(RULES[tdoc.rule].statusSort).toArray();
const tsdocs = await getMultiStatus(domainId, tid)
.sort(RULES[tdoc.rule].statusSort).toArray();
const uids = [];
for (const tsdoc of tsdocs) uids.push(tsdoc.uid);
const [udict, pdict] = await Promise.all([user.getList(uids), problem.getList(tdoc.pids)]);
const [udict, pdict] = await Promise.all([
user.getList(uids),
problem.getList(domainId, tdoc.pids),
]);
const rankedTsdocs = RULES[tdoc.rule].rank(tsdocs);
const rows = RULES[tdoc.rule].scoreboard(isExport, (str) => (str ? str.toString().translate(this.user.language) : ''), tdoc, rankedTsdocs, udict, pdict);
return [tdoc, rows, udict];
}
async verifyProblems(pids) { // eslint-disable-line class-methods-use-this
async verifyProblems(domainId, pids) { // eslint-disable-line class-methods-use-this
const r = [];
for (const pid of pids) {
const res = await problem.get(pid); // eslint-disable-line no-await-in-loop
if (res) r.push(res._id);
const res = await problem.get(domainId, pid); // eslint-disable-line no-await-in-loop
if (res) r.push(res.docId);
else throw new ProblemNotFoundError(pid);
}
return r;
}
};
function setStatus(domainId, tid, uid, $set) {
return document.setStatus(domainId, document.TYPE_CONTEST, tid, uid, $set);
}
global.Hydro.model.contest = module.exports = {
RULES,
ContestHandlerMixin,
@@ -352,9 +363,9 @@ global.Hydro.model.contest = module.exports = {
get,
updateStatus,
getStatus,
count: (query) => coll.find(query).count(),
getMulti: (query) => coll.find(query),
setStatus: (tid, uid, $set) => collStatus.findOneAndUpdate({ tid, uid }, { $set }),
count: (query) => document.count({ ...query, docType: document.TYPE_CONTEST }),
getMulti: (query = {}) => document.getMulti({ ...query, docType: document.TYPE_CONTEST }),
setStatus,
isNew,
isUpcoming,
isNotStarted,
@@ -390,7 +401,7 @@ async def recalc_status(domainId: str, doc_type: int, cid: objeccid.Objeccid):
tdoc = await document.get(domainId, doc_type, cid)
async with document.get_multi_status(domainId=domainId,
doc_type=doc_type,
doc_id=tdoc._id) as tsdocs:
doc_id=tdoc.docId) as tsdocs:
async for tsdoc in tsdocs:
if 'journal' not in tsdoc or not tsdoc['journal']:
continue
+75 -92
View File
@@ -1,157 +1,140 @@
const { ObjectID } = require('bson');
const problem = require('./problem');
const document = require('./document');
const contest = require('./contest');
const { PERM_VIEW_PROBLEM_HIDDEN } = require('../permission');
const { DocumentNotFoundError } = require('../error');
const db = require('../service/db');
const coll = db.collection('discussion');
const collReply = db.collection('discussion.reply');
const collStatus = db.collection('discussion.status');
const collNode = db.collection('discussion.node');
async function add(parentType, parentId, owner, title, content, ip = null, highlight = false) {
const res = await coll.insertOne({
owner,
title,
content,
ip,
highlight,
parentType,
parentId,
nReply: 0,
updateAt: new Date(),
});
return res.insertedId;
function add(domainId, parentType, parentId, owner, title, content, ip = null, highlight = false) {
return document.add(
domainId, content, owner, document.TYPE_DISCUSSION,
null, parentType, parentId,
{
title,
ip,
highlight,
nReply: 0,
updateAt: new Date(),
},
);
}
function get(did) {
return coll.findOne({ _id: did });
function get(domainId, did) {
return document.get(domainId, document.TYPE_DISCUSSION, did);
}
function edit(did, title, content, highlight) {
return coll.findOneAndUpdate({ _id: did }, { $set: { title, content, highlight } });
function edit(domainId, did, title, content, highlight) {
return document.set(domainId, document.TYPE_DISCUSSION, did, { title, content, highlight });
}
function del(did) {
function del(domainId, did) {
// TODO(masnn) delete status
return Promise.all([
coll.deleteOne({ _id: did }),
collReply.deleteMany({ did }),
collStatus.deleteMany({ did }),
document.deleteOne(domainId, document.TYPE_DISCUSSION, did),
document.deleteMulti(domainId, document.TYPE_DISCUSSION_REPLY, {
parentType: document.TYPE_DISCUSSION, parentId: did,
}),
]);
}
function count(query) {
return coll.find(query).count();
function count(domainId, query) {
return document.count(domainId, document.TYPE_DISCUSSION, query);
}
function getMulti(query) {
return coll.find(query).sort('updateAt', -1);
function getMulti(domainId, query) {
return document.getMulti(domainId, document.TYPE_DISCUSSION, query).sort('updateAt', -1);
}
async function addReply(did, owner, content, ip) {
async function addReply(domainId, did, owner, content, ip) {
const [drdoc] = await Promise.all([
collReply.insertOne({
owner, did, content, ip,
}),
coll.updateOne({ _id: did }, { $inc: { nReply: 1 }, $set: { updateAt: new Date() } }),
document.add(
domainId, content, owner, document.TYPE_DISCUSSION_REPLY,
null, document.TYPE_DISCUSSION, did, { ip },
),
document.incAndSet(domainId, document.TYPE_DISCUSSION, did, 'nReply', 1, { updateAt: new Date() }),
]);
return drdoc;
}
function getReply(drid) {
return collReply.findOne({ _id: drid });
function getReply(domainId, drid) {
return document.get(domainId, document.TYPE_DISCUSSION_REPLY, drid);
}
function editReply(drid, content) {
return collReply.updateOne({ _id: drid }, { $set: { content } });
function editReply(domainId, drid, content) {
return document.set(domainId, document.TYPE_DISCUSSION_REPLY, drid, { content });
}
async function delReply(drid) {
const drdoc = await getReply(drid);
async function delReply(domainId, drid) {
const drdoc = await getReply(domainId, drid);
if (!drdoc) throw new DocumentNotFoundError(drid);
return await Promise.all([ // eslint-disable-line no-return-await
collReply.deleteOne({ _id: drid }),
coll.updateOne({ _id: drdoc.did }, { $inc: { nReply: -1 } }),
document.deleteOne(domainId, document.TYPE_DISCUSSION_REPLY, drid),
document.inc(domainId, document.TYPE_DISCUSSION, drdoc.parentId, 'nReply', -1),
]);
}
function getMultiReply(did) {
return coll.find({ did }).sort('_id', -1);
function getMultiReply(domainId, did) {
return document.getMulti(
domainId, document.TYPE_DISCUSSION_REPLY,
{ parentType: document.TYPE_DISCUSSION, parentId: did },
).sort('_id', -1);
}
function getListReply(did) {
return getMultiReply({ did }).toArray();
function getListReply(domainId, did) {
return getMultiReply(domainId, did).toArray();
}
async function addTailReply(drid, owner, content, ip) {
let drdoc = await collReply.findOne({ _id: drid });
async function addTailReply(domainId, drid, owner, content, ip) {
let drdoc = await document.get(domainId, document.TYPE_DISCUSSION_REPLY, drid);
const sid = new ObjectID();
await Promise.all([
collReply.updateOne({ _id: drid }, {
$push: {
reply: {
_id: sid, content, owner, ip,
},
},
}),
coll.updateOne({ _id: drdoc.did }, { $set: { updateAt: new Date() } }),
[drdoc] = await Promise.all([
document.push(domainId, document.TYPE_DISCUSSION_REPLY, drid, 'reply', content, owner, { ip }),
document.set(domainId, document.TYPE_DISCUSSION, drdoc.parentId, { updateAt: new Date() }),
]);
drdoc = await collReply.findOne({ _id: drid });
return [drdoc, sid];
}
async function getTailReply(drid, drrid) {
const drdoc = await collReply.findOne({ _id: drid, reply: { $elemMatch: { _id: drrid } } });
if (!drdoc) return [null, null];
for (const drrdoc of drdoc) if (drrdoc._id === drrid) return [drdoc, drrdoc];
return [drdoc, null];
function getTailReply(domainId, drid, drrid) {
return document.getSub(domainId, document.TYPE_DISCUSSION_REPLY, drid, 'reply', drrid);
}
async function editTailReply(drid, drrid, content) {
const drdoc = await collReply.findOne({ _id: drid, reply: { $elemMatch: { _id: drrid } } });
const { reply } = drdoc;
for (const i in reply) {
if (reply[i]._id === drrid) {
reply[i].content = content;
break;
}
}
// eslint-disable-next-line no-return-await
return await collReply.findOneAndUpdate({ _id: drdoc._id }, { $set: { reply } });
function editTailReply(domainId, drid, drrid, content) {
return document.setSub(domainId, document.TYPE_DISCUSSION_REPLY, drid, 'reply', drrid, { content });
}
function delTailReply(drid, drrid) {
return coll.findOneAndUpdate({ _id: drid }, { $pull: { reply: { _id: drrid } } });
function delTailReply(domainId, drid, drrid) {
return document.deleteSub(domainId, document.TYPE_DISCUSSION_REPLY, drid, 'reply', drrid);
}
function setStar(did, uid, star) {
return collStatus.findOneAndUpdate({ did, uid }, { $set: { star } }, { upsert: true });
function setStar(domainId, did, uid, star) {
return document.setStatus(domainId, document.TYPE_DISCUSSION, did, uid, { star });
}
function getStatus(did, uid) {
return collStatus.findOne({ did, uid });
function getStatus(domainId, did, uid) {
return document.getStatus(domainId, document.TYPE_DISCUSSION, did, uid);
}
function addNode(_id, category) {
return collNode.insertOne({ _id, category });
function addNode(domainId, _id, category) {
return document.add(
domainId, _id, 1, document.TYPE_DISCUSSION_NODE, null, null, null, { category },
);
}
function getNode(_id) {
return collNode.findOne({ _id });
function getNode(domainId, _id) {
return document.get(domainId, document.TYPE_DISCUSSION_NODE, _id);
}
async function getVnode(ddoc, handler) {
async function getVnode(domainId, ddoc, handler) {
if (ddoc.parentType === 'problem') {
const pdoc = await problem.getById(ddoc.parentId);
const pdoc = await problem.getById(domainId, ddoc.parentId);
if (!pdoc) return null;
if (pdoc.hidden && handler) handler.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
return { ...pdoc, type: ddoc.parentType, id: ddoc.parentId };
} if (ddoc.parentType === 'contest') {
const tdoc = await contest.get(ddoc.parentId);
const tdoc = await contest.get(domainId, ddoc.parentId);
return { ...tdoc, type: ddoc.parentType, id: ddoc.parentId };
} if (ddoc.parentType === 'node') {
const ndoc = await getNode(ddoc.parentId);
const ndoc = await getNode(domainId, ddoc.parentId);
return { title: ndoc._id, type: ddoc.parentType, id: ddoc.parentId };
}
return {
@@ -161,11 +144,11 @@ async function getVnode(ddoc, handler) {
};
}
async function getListVnodes(ddocs, handler) {
async function getListVnodes(domainId, ddocs, handler) {
const res = {};
for (const ddoc of ddocs) {
// FIXME no-await-in-loop
res[ddoc._id] = await getVnode(ddoc, handler); // eslint-disable-line no-await-in-loop
res[ddoc._id] = await getVnode(domainId, ddoc, handler); // eslint-disable-line no-await-in-loop
}
return res;
}
+366
View File
@@ -0,0 +1,366 @@
const assert = require('assert');
const { ObjectID } = require('bson');
const db = require('../service/db');
const coll = db.collection('document');
const collStatus = db.collection('document.status');
const TYPE_DOMAIN_ROLE = 0;
const TYPE_PROBLEM = 10;
const TYPE_PROBLEM_SOLUTION = 11;
const TYPE_PROBLEM_LIST = 12;
const TYPE_DISCUSSION_NODE = 20;
const TYPE_DISCUSSION = 21;
const TYPE_DISCUSSION_REPLY = 22;
const TYPE_CONTEST = 30;
const TYPE_TRAINING = 40;
const TYPE_FILE = 50;
const TYPE_HOMEWORK = 60;
async function add(
domainId, content, owner, docType, docId,
parentType = null, parentId = null, args = {},
) {
const _id = new ObjectID();
const doc = {
content,
owner,
domainId,
docType,
docId: docId || _id,
...args,
};
if (parentType || parentId) {
assert(parentType && parentId);
doc.parentType = parentType;
doc.parentId = parentId;
}
const res = await coll.insertOne(doc);
return docId || res.insertedId;
}
function get(domainId, docType, docId) {
return coll.findOne({ domainId, docType, docId });
}
async function set(domainId, docType, docId, args) {
const res = await coll.findOneAndUpdate(
{ domainId, docType, docId },
{ $set: { args } },
{ returnOriginal: false },
);
return res.value;
}
function deleteOne(domainId, docType, docId) {
// TODO(twd2): delete status?
return coll.deleteOne({ domainId, docType, docId });
}
function deleteMulti(domainId, docType, args = {}) {
return coll.deleteMany({ ...args, domainId, docType });
}
function deleteMultiStatus(domainId, docType, args = {}) {
return collStatus.deleteMany({ ...args, domainId, docType });
}
function getMulti(domainId, docType, args = {}) {
return coll.find({ ...args, docType, domainId });
}
async function inc(domainId, docType, docId, key, value) {
const res = await coll.findOneAndUpdate(
{ domainId, docType, docId },
{ $inc: { [key]: value } },
{ returnOriginal: false },
);
return res.value;
}
async function incAndSet(domainId, docType, docId, key, value, args) {
const res = await coll.findOneAndUpdate(
{ domainId, docType, docId },
{ $inc: { [key]: value }, $set: args },
{ returnOriginal: false },
);
return res.value;
}
function count(domainId, docType, query) {
return coll.find({ ...query, docType, domainId }).count();
}
async function push(domainId, docType, docId, key, content, owner, args) {
const _id = new ObjectID();
const doc = await coll.findOneAndUpdate(
{ domainId, docType, docId },
{
$push: {
[key]: {
...args, content, owner, _id,
},
},
},
{ returnOriginal: false },
);
return [doc.value, _id];
}
async function pull(domainId, docType, docId, setKey, contents) {
const res = await coll.findOneAndUpdate(
{ domainId, docType, docId },
{ $pull: { [setKey]: { $in: contents } } },
{ returnOriginal: false },
);
return res.value;
}
async function deleteSub(domainId, docType, docId, key, subId) {
const res = await coll.findOneAndUpdate(
{ domainId, docType, docId },
{ $pull: { [key]: { _id: subId } } },
{ returnOriginal: false },
);
return res.value;
}
async function getSub(domainId, docType, docId, key, subId) {
const doc = await coll.findOne({
domainId,
docType,
docId,
[key]: { $elemMatch: { _id: subId } },
});
if (!doc) return [null, null];
for (const sdoc of doc[key] || []) {
if (sdoc._id === subId) {
return [doc, sdoc];
}
}
return [doc, null];
}
async function setSub(domainId, docType, docId, key, subId, args) {
const $set = {};
for (const k in args) {
$set[`${key}.$.${k}`] = args[k];
}
const res = await coll.findOneAndUpdate(
{
domainId,
docType,
docId,
[key]: { $elemMatch: { _id: subId } },
},
{ $set },
{ returnOriginal: false },
);
return res.value;
}
async function addToSet(domainId, docType, docId, setKey, content) {
const res = await coll.findOneAndUpdate(
{ domainId, docType, docId },
{ $addToSet: { [setKey]: content } },
{ returnOriginal: false },
);
return res.value;
}
function getStatus(domainId, docType, docId, uid) {
return collStatus.findOne({
domainId, docType, docId, uid,
});
}
function getMultiStatus(domainId, docType, args) {
return collStatus.find({ ...args, docType, domainId });
}
async function setStatus(domainId, docType, docId, uid, args) {
const res = await collStatus.findOneAndUpdate(
{
domainId, docType, docId, uid,
},
{ $set: args },
{ upsert: true, returnOriginal: false },
);
return res.value;
}
async function setIfNotStatus(domainId, docType, docId, uid, key, value, ifNot, args) {
const res = await collStatus.findOneAndUpdate(
{
domainId, docType, docId, uid, key: { $not: { $eq: ifNot } },
},
{ $set: { [key]: value, args } },
{ upsert: true, returnOriginal: false },
);
return res.value;
}
async function cappedIncStatus(
domainId, docType, docId, uid,
key, value, minValue = -1, maxValue = 1,
) {
assert(value !== 0);
const $not = value > 0 ? { $gte: maxValue } : { $lte: minValue };
const res = await collStatus.findOneAndUpdate(
{
domainId, docType, docId, uid, [key]: { $not },
},
{ $inc: { key: value } },
{ upsert: true, returnOriginal: false },
);
return res.value;
}
async function incStatus(domainId, docType, docId, uid, key, value) {
const res = await collStatus.findOneAndUpdate(
{
domainId, docType, docId, uid,
},
{ $inc: { [key]: value } },
{ upsert: true, returnOriginal: false },
);
return res.value;
}
async function revPushStatus(domainId, docType, docId, uid, key, value) {
const res = await collStatus.findOneAndUpdate(
{
domainId, docType, docId, uid,
},
{ $push: { [key]: value }, $inc: { rev: 1 } },
{ upsert: true, returnOriginal: false },
);
return res.value;
}
async function revInitStatus(domainId, docType, docId, uid) {
const res = await collStatus.findOneAndUpdate(
{
domainId, docType, docId, uid,
},
{ $inc: { rev: 1 } },
{ upsert: true, returnOriginal: false },
);
return res.value;
}
async function revSetStatus(domainId, docType, docId, uid, rev, args, returnDoc = true) {
const filter = {
domainId, docType, docId, uid, rev,
};
const update = { $set: args, $inc: { rev: 1 } };
if (returnDoc) {
const res = await coll.findOneAndUpdate(filter, update, { returnOriginal: false });
return res.value;
}
const res = await coll.updateOne(filter, update);
return res;
}
async function ensureIndexes() {
await coll.createIndex({ domainId: 1, docType: 1, docId: 1 }, { unique: true });
await coll.createIndex({
domainId: 1, docType: 1, owner: 1, docId: -1,
});
// For problem
await coll.createIndex({
domainId: 1, docType: 1, category: 1, docId: 1,
}, { sparse: true });
await coll.createIndex({
domainId: 1, docType: 1, hidden: 1, category: 1, docId: 1,
}, { sparse: true });
await coll.createIndex({
domainId: 1, docType: 1, tag: 1, docId: 1,
}, { sparse: true });
await coll.createIndex({
domainId: 1, docType: 1, hidden: 1, tag: 1, docId: 1,
}, { sparse: true });
// For problem solution
await coll.createIndex({
domainId: 1, docType: 1, parentType: 1, parentId: 1, vote: -1, docId: -1,
}, { sparse: true });
// For discussion
await coll.createIndex({
domainId: 1, docType: 1, updateAt: -1, docId: -1,
}, { sparse: true });
await coll.createIndex({
domainId: 1, docType: 1, parentType: 1, parentId: 1, updateAt: -1, docId: -1,
}, { sparse: true });
// Hidden doc
await coll.createIndex({
domainId: 1, docType: 1, hidden: 1, docId: -1,
}, { sparse: true });
// For contest
await coll.createIndex({ domainId: 1, docType: 1, pids: 1 }, { sparse: true });
await coll.createIndex({
domainId: 1, docType: 1, rule: 1, docId: -1,
}, { sparse: true });
// For training
await coll.createIndex({ domainId: 1, docType: 1, 'dag.pids': 1 }, { sparse: true });
await collStatus.createIndex({
domainId: 1, docType: 1, uid: 1, docId: 1,
}, { unique: true });
// For rp system
await collStatus.createIndex({
domainId: 1, docType: 1, docId: 1, status: 1, rid: 1, rp: 1,
}, { sparse: true });
// For contest rule OI
await collStatus.createIndex({
domainId: 1, docType: 1, docId: 1, score: -1,
}, { sparse: true });
// For contest rule ACM
await collStatus.createIndex({
domainId: 1, docType: 1, docId: 1, accept: -1, time: 1,
}, { sparse: true });
// For training
await collStatus.createIndex({
domainId: 1, docType: 1, uid: 1, enroll: 1, docId: 1,
}, { sparse: true });
}
global.Hydro.model.document = module.exports = {
add,
addToSet,
cappedIncStatus,
count,
deleteMulti,
deleteMultiStatus,
deleteOne,
deleteSub,
ensureIndexes,
get,
getMulti,
getMultiStatus,
getStatus,
getSub,
inc,
incAndSet,
incStatus,
pull,
push,
revInitStatus,
revPushStatus,
revSetStatus,
set,
setIfNotStatus,
setStatus,
setSub,
TYPE_DOMAIN_ROLE,
TYPE_CONTEST,
TYPE_DISCUSSION,
TYPE_DISCUSSION_NODE,
TYPE_DISCUSSION_REPLY,
TYPE_HOMEWORK,
TYPE_PROBLEM,
TYPE_PROBLEM_LIST,
TYPE_PROBLEM_SOLUTION,
TYPE_FILE,
TYPE_TRAINING,
};
+96
View File
@@ -0,0 +1,96 @@
const builtin = require('./builtin');
const document = require('./document');
const db = require('../service/db');
const validator = require('../lib/validator');
const coll = db.collection('domain');
/**
* @typedef {import('../interface').Pdoc} Pdoc
* @typedef {import('bson').ObjectID} ObjectID
* @typedef {import('mongodb').Cursor} Cursor
*/
/**
* @param {string} domainId
* @param {number} owner
*/
function add(domainId, owner) {
const tasks = [
coll.insertOne({ _id: domainId, owner, bulletin: '' }),
];
for (const id in builtin.BUILTIN_ROLES) {
tasks.push(
document.add(
domainId, builtin.BUILTIN_ROLES[id].perm, owner,
document.TYPE_DOMAIN_ROLE, id,
),
);
}
return Promise.all(tasks);
}
/**
* @param {string} domainId
* @returns {Ddoc}
*/
async function get(domainId) {
return coll.findOne({ _id: domainId });
}
/**
* @param {object} query
* @param {object} sort
* @param {number} page
* @param {number} limit
* @returns {Ddoc[]}
*/
function getMany(query, sort, page, limit) {
return coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit)
.toArray();
}
/**
* @param {object} query
* @returns {Cursor}
*/
function getMulti(query) {
return coll.find(query);
}
/**
* @param {string} domainId
* @param {object} $set
* @returns {Ddoc}
*/
function edit(domainId, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkContent($set.content);
return coll.updateOne({ _id: domainId }, { $set });
}
async function inc(domainId, field, n) {
const res = await coll.findOneAndUpdate(
{ _id: domainId },
{ $inc: { [field]: n } },
{ returnOriginal: false },
);
return res.value;
}
async function getList(domainIds) {
const r = {};
// eslint-disable-next-line no-await-in-loop
for (const domainId of domainIds) r[domainId] = await get(domainId);
return r;
}
global.Hydro.model.domain = module.exports = {
add,
inc,
get,
getMany,
edit,
getMulti,
getList,
};
+1
View File
@@ -2,6 +2,7 @@ module.exports = {
blacklist: require('./blacklist'),
builtin: require('./builtin'),
contest: require('./contest'),
document: require('./document'),
opcount: require('./opcount'),
problem: require('./problem'),
record: require('./record'),
+2 -2
View File
@@ -55,7 +55,7 @@ function getMulti(uid) {
return coll.find({ $or: [{ from: uid }, { to: uid }] });
}
function index() {
function ensureIndexes() {
return Promise.all([
coll.createIndex({ to: 1, _id: -1 }),
coll.createIndex({ from: 1, _id: -1 }),
@@ -69,5 +69,5 @@ global.Hydro.model.message = module.exports = {
getMany,
getMulti,
send,
index,
ensureIndexes,
};
+70 -92
View File
@@ -1,12 +1,10 @@
const { ObjectID } = require('bson');
const { STATUS_ACCEPTED } = require('./builtin').STATUS;
const file = require('./file');
const document = require('./document');
const domain = require('./domain');
const { ProblemNotFoundError } = require('../error');
const validator = require('../lib/validator');
const db = require('../service/db');
const coll = db.collection('problem');
const collStatus = db.collection('problem.status');
/**
* @typedef {import('../interface').Pdoc} Pdoc
@@ -15,6 +13,7 @@ const collStatus = db.collection('problem.status');
*/
/**
* @param {string} domainId
* @param {string} title
* @param {string} content
* @param {number} owner
@@ -24,10 +23,7 @@ const collStatus = db.collection('problem.status');
* @param {string[]} tag
* @param {boolean} hidden
*/
async function add({
title,
content,
owner,
async function add(domainId, title, content, owner, {
pid = null,
data = null,
category = [],
@@ -36,134 +32,118 @@ async function add({
}) {
validator.checkTitle(title);
validator.checkContent(content);
const res = await coll.insertOne({
content,
owner,
pid,
title,
data,
category,
tag,
hidden,
nSubmit: 0,
nAccept: 0,
const d = await domain.inc(domainId, 'pidCounter', 1);
if (!pid) pid = d.pidCounter.toString();
return await document.add(domainId, content, owner, document.TYPE_PROBLEM, d.pidCounter, null, null, {
pid, title, data, category, tag, hidden, nSubmit: 0, nAccept: 0,
});
return res.insertedId;
}
/**
* @param {string|ObjectID} pid
* @param {string} domainId
* @param {string|number} pid
* @param {number} uid
* @returns {Pdoc}
*/
async function get(pid, uid = null) {
let query = {};
if (pid.generationTime || pid.length === 24) query = { _id: new ObjectID(pid) };
else query = { pid };
const pdoc = await coll.findOne(query);
async function get(domainId, pid, uid = null) {
const pdoc = await document.get(domainId, document.TYPE_PROBLEM, pid);
if (!pdoc) throw new ProblemNotFoundError(pid);
if (uid) {
query.uid = uid;
pdoc.psdoc = await collStatus.findOne(query);
pdoc.psdoc = await document.getStatus(domainId, document.TYPE_PROBLEM, pdoc.docId, uid);
}
return pdoc;
}
/**
* @param {ObjectID} pid
* @returns {Pdoc}
*/
async function getById(_id) {
_id = new ObjectID(_id);
const pdoc = await coll.findOne({ _id });
if (!pdoc) throw new ProblemNotFoundError(_id);
return pdoc;
}
/**
* @param {string|ObjectID} pid
* @param {number} uid
* @param {string} domainId
* @param {object} query
* @param {object} sort
* @param {number} page
* @param {number} limit
* @returns {Pdoc[]}
*/
function getMany(query, sort, page, limit) {
return coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit)
function getMany(domainId, query, sort, page, limit) {
return document.getMulti(domainId, query).sort(sort).skip((page - 1) * limit).limit(limit)
.toArray();
}
/**
* @param {string} domainId
* @param {object} query
* @returns {Cursor}
*/
function getMulti(query) {
return coll.find(query);
function getMulti(domainId, query) {
return document.getMulti(domainId, document.TYPE_PROBLEM, query);
}
/**
* @param {string} domainId
* @param {object} query
* @returns {Cursor}
*/
function getMultiStatus(query) {
return collStatus.find(query);
function getMultiStatus(domainId, query) {
return document.getMultiStatus(domainId, document.TYPE_PROBLEM, query);
}
/**
* @param {string} domainId
* @param {ObjectID} _id
* @param {object} query
* @returns {Pdoc}
*/
async function edit(_id, $set) {
function edit(domainId, _id, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkContent($set.content);
await coll.findOneAndUpdate({ _id }, { $set });
const pdoc = await getById(_id);
if (!pdoc) throw new ProblemNotFoundError(_id);
return pdoc;
return document.set(domainId, document.TYPE_CONTEST, _id, $set);
}
async function inc(_id, field, n) {
await coll.findOneAndUpdate({ _id }, { $inc: { [field]: n } });
const pdoc = await getById(_id);
if (!pdoc) throw new ProblemNotFoundError(_id);
return pdoc;
function inc(domainId, _id, field, n) {
return document.inc(domainId, document.TYPE_PROBLEM, _id, field, n);
}
function count(query) {
return coll.find(query).count();
function count(domainId, query) {
return document.count(domainId, query);
}
async function random(query) {
const cursor = coll.find(query);
async function random(domainId, query) {
const cursor = document.getMulti(domainId, query);
const pcount = await cursor.count();
if (pcount) {
const pdoc = await cursor.skip(Math.floor(Math.random() * pcount)).limit(1).toArray();
return pdoc[0].pid;
} return null;
}
async function getList(pids) {
async function getList(domainId, pids) {
const r = {};
for (const pid of pids) r[pid] = await get(pid); // eslint-disable-line no-await-in-loop
// eslint-disable-next-line no-await-in-loop
for (const pid of pids) r[pid] = await get(domainId, pid);
return r;
}
async function getListStatus(uid, pids) {
const psdocs = await getMultiStatus({ uid, pid: { $in: Array.from(new Set(pids)) } }).toArray();
const r = {};
for (const psdoc of psdocs) r[psdoc.pid] = psdoc;
return r;
}
async function updateStatus(pid, uid, rid, status) {
const cur = collStatus.findOne({ pid, uid });
if (cur && status !== STATUS_ACCEPTED) {
if (cur.status !== STATUS_ACCEPTED) return false;
}
await collStatus.updateOne(
{ pid, uid },
{ $set: { rid, status } },
{ upsert: true },
);
return true;
}
async function setTestdata(_id, readStream) {
const pdoc = await getById(_id);
const id = await file.add(readStream, 'data.zip');
if (pdoc.data && typeof pdoc.data === 'object') file.dec(this.pdoc.data);
return await edit(_id, { data: id }); // eslint-disable-line no-return-await
}
async function getData(pid) {
const pdoc = await get(pid);
if (!pdoc.data) return null;
return file.get(pdoc.data);
async function getListStatus(domainId, uid, pids) {
const psdocs = await getMultiStatus(
domainId, { uid, docId: { $in: Array.from(new Set(pids)) } },
).toArray();
const r = {};
for (const psdoc of psdocs) r[psdoc.docId] = psdoc;
return r;
}
async function updateStatus(domainId, pid, uid, rid, status) {
try {
await document.setIfNotStatus(domainId, document.TYPE_PROBLEM, pid, uid, 'status', status, STATUS_ACCEPTED, { rid });
} catch (e) {
return false;
}
return true;
}
async function setTestdata(domainId, _id, readStream) {
const pdoc = await get(domainId, _id);
const id = await file.add(readStream, 'data.zip');
if (pdoc.data && typeof pdoc.data === 'object') file.dec(this.pdoc.data);
return await edit(domainId, _id, { data: id }); // eslint-disable-line no-return-await
}
global.Hydro.model.problem = module.exports = {
@@ -174,12 +154,10 @@ global.Hydro.model.problem = module.exports = {
edit,
count,
random,
getById,
getMulti,
getList,
getListStatus,
getMultiStatus,
setTestdata,
updateStatus,
getData,
};
+40 -27
View File
@@ -9,11 +9,13 @@ const db = require('../service/db');
const coll = db.collection('record');
/**
* @param {string} domainId
* @param {import('../interface').Record} data
*/
async function add(data) {
async function add(domainId, data) {
_.defaults(data, {
status: STATUS_WAITING,
domainId,
score: 0,
time: 0,
memory: 0,
@@ -27,32 +29,37 @@ async function add(data) {
const res = await coll.insertOne(data);
return res.insertedId;
}
/**
* @param {string} rid
* @param {string} domainId
* @param {ObjectID} rid
* @returns {import('../interface').Record}
*/
async function get(rid) {
async function get(domainId, rid) {
const _id = new ObjectID(rid);
const rdoc = await coll.findOne({ _id });
const rdoc = await coll.findOne({ domainId, _id });
if (!rdoc) throw new RecordNotFoundError(rid);
return rdoc;
}
function getMany(query, sort, page, limit) {
return coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit)
function getMany(domainId, query, sort, page, limit) {
return coll.find({ ...query, domainId }).sort(sort).skip((page - 1) * limit).limit(limit)
.toArray();
}
async function update(rid, $set, $push) {
async function update(domainId, rid, $set, $push) {
const _id = new ObjectID(rid);
const upd = {};
if ($set && Object.keys($set).length) upd.$set = $set;
if ($push && Object.keys($push).length) upd.$push = $push;
await coll.findOneAndUpdate({ _id }, upd);
await coll.findOneAndUpdate({ domainId, _id }, upd);
const rdoc = await coll.findOne({ _id });
if (!rdoc) throw new RecordNotFoundError(rid);
return rdoc;
}
function reset(rid) {
return update(rid, {
function reset(domainId, rid) {
return update(domainId, rid, {
score: 0,
status: STATUS_WAITING,
time: 0,
@@ -65,24 +72,29 @@ function reset(rid) {
rejudged: true,
});
}
function count(query) {
return coll.find(query).count();
}
async function getList(rids) {
const r = {};
for (const rid of rids) r[rid] = await get(rid); // eslint-disable-line no-await-in-loop
return r;
}
function getUserInProblemMulti(uid, pid) {
return coll.find({ owner: uid, pid });
function count(domainId, query) {
return coll.find({ domainId, ...query }).count();
}
async function judge(rid) {
const rdoc = await get(rid);
const pdoc = await problem.getById(rdoc.pid);
async function getList(domainId, rids) {
const r = {};
// eslint-disable-next-line no-await-in-loop
for (const rid of rids) r[rid] = await get(domainId, rid);
return r;
}
function getUserInProblemMulti(domainId, uid, pid) {
return coll.find({ domainId, owner: uid, pid });
}
async function judge(domainId, rid) {
const rdoc = await get(domainId, rid);
const pdoc = await problem.get(domainId, rdoc.pid);
await task.add({
type: 'judge',
rid,
domainId,
pid: rdoc.pid,
data: pdoc.data,
lang: rdoc.lang,
@@ -90,13 +102,14 @@ async function judge(rid) {
});
}
async function rejudge(rid) {
await reset(rid);
const rdoc = await get(rid);
const pdoc = await problem.getById(rdoc.pid);
async function rejudge(domainId, rid) {
await reset(domainId, rid);
const rdoc = await get(domainId, rid);
const pdoc = await problem.get(domainId, rdoc.pid);
await task.add({
type: 'judge',
rid,
domainId,
pid: rdoc.pid,
data: pdoc.data,
lang: rdoc.lang,
+39 -66
View File
@@ -1,107 +1,80 @@
const { ObjectID } = require('bson');
const document = require('./document');
const { SolutionNotFoundError } = require('../error');
const validator = require('../lib/validator');
const db = require('../service/db');
const coll = db.collection('solution');
const collStatus = db.collection('solution.status');
/**
* @param {string} pid
* @param {number} owner
* @param {string} content
*/
async function add(pid, owner, content) {
validator.checkContent(content);
pid = new ObjectID(pid);
const res = await coll.insertOne({
content, owner, pid, reply: [], vote: 0,
});
return res.insertedId;
function add(domainId, pid, owner, content) {
return document.add(
domainId, content, owner, document.TYPE_PROBLEM_SOLUTION,
null, document.TYPE_PROBLEM, pid, { reply: [], vote: 0 },
);
}
async function get(psid) {
psid = new ObjectID(psid);
const psdoc = await coll.findOne({ _id: psid });
async function get(domainId, psid) {
const psdoc = await document.get(domainId, document.TYPE_PROBLEM_SOLUTION, psid);
if (!psdoc) throw new SolutionNotFoundError();
return psdoc;
}
function getMany(query, sort, page, limit) {
return coll.find(query).sort(sort)
function getMany(domainId, query, sort, page, limit) {
return document.getMulti(domainId, document.TYPE_PROBLEM_SOLUTION, query)
.sort(sort)
.skip((page - 1) * limit).limit(limit)
.toArray();
}
async function edit(_id, content) {
validator.checkContent(content);
await coll.updateOne({ _id }, { $set: { content } });
const psdoc = await get(_id);
if (!psdoc) throw new SolutionNotFoundError(_id);
return psdoc;
function edit(domainId, psid, content) {
return document.set(domainId, document.TYPE_PROBLEM_SOLUTION, psid, { content });
}
function del(psid) {
return coll.deleteOne({ _id: psid });
function del(domainId, psid) {
return document.deleteOne(domainId, document.TYPE_PROBLEM_SOLUTION, psid);
}
function count(query) {
return coll.find(query).count();
function count(domainId, query) {
return document.count(domainId, document.TYPE_PROBLEM_SOLUTION, query);
}
function getMulti(pid) {
return coll.find({ pid }).sort({ vote: -1 });
function getMulti(domainId, pid) {
return document.getMulti(
domainId, document.TYPE_PROBLEM_SOLUTION,
{ parentType: document.TYPE_PROBLEM, parentId: pid },
).sort({ vote: -1 });
}
function reply(psid, owner, content) {
validator.checkContent(content);
return coll.findOneAndUpdate(
{ _id: psid },
{ $push: { reply: { content, owner, _id: new ObjectID() } } },
);
function reply(domainId, psid, owner, content) {
return document.push(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', content, owner);
}
async function getReply(psid, psrid) {
const psdoc = await coll.findOne({ _id: psid, reply: { $elemMatch: { _id: psrid } } });
if (!psdoc) return [null, null];
for (const psrdoc of psdoc) if (psrdoc._id === psrid) return [psdoc, psrdoc];
return [psdoc, null];
function getReply(domainId, psid, psrid) {
return document.getSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid);
}
async function editReply(psid, psrid, content) {
validator.checkContent(content);
psid = new ObjectID(psid);
psrid = new ObjectID(psrid);
const psdoc = await coll.findOne({ _id: psid, reply: { $elemMatch: { _id: psrid } } });
const { reply } = psdoc; // eslint-disable-line no-shadow
for (const i in reply) {
if (reply[i]._id === psrid) {
reply[i].content = content;
break;
}
}
// eslint-disable-next-line no-return-await
return await coll.updateOne({ _id: psdoc._id }, { $set: { reply } });
function editReply(domainId, psid, psrid, content) {
return document.setSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid, { content });
}
function delReply(psid, psrid) {
return coll.findOneAndUpdate({ _id: psid }, { $pull: { reply: { _id: psrid } } });
function delReply(domainId, psid, psrid) {
return document.deleteSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid);
}
async function vote(psid, uid, value) {
let pssdoc = await collStatus.findOne({ psid, uid });
if (pssdoc) await collStatus.deleteOne({ psid, uid });
await collStatus.insertOne({ psid, uid, vote: value });
async function vote(domainId, psid, uid, value) {
let pssdoc = await document.getStatus(domainId, document.TYPE_PROBLEM_SOLUTION, psid, uid);
await document.setStatus(domainId, document.TYPE_PROBLEM_SOLUTION, psid, uid, { vote: value });
if (pssdoc) value += -pssdoc.vote;
await coll.updateOne({ _id: psid }, { $inc: { vote: value } });
pssdoc = await collStatus.findOne({ psid, uid });
const psdoc = await coll.findOne({ _id: psid });
const psdoc = await document.inc(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'vote', value);
pssdoc = await document.getStatus(domainId, document.TYPE_PROBLEM_SOLUTION, psid, uid);
return [psdoc, pssdoc];
}
async function getListStatus(list, uid) {
async function getListStatus(domainId, list, uid) {
const result = {};
const res = await collStatus.find({ uid, psid: { $in: list } }).toArray();
const res = await document.getMultiStatus(
domainId, document.TYPE_PROBLEM_SOLUTION, { uid, psid: { $in: list } },
).toArray();
for (const i of res) result[i.psid] = i;
return result;
}
+56 -58
View File
@@ -1,38 +1,67 @@
const assert = require('assert');
const document = require('./document');
const validator = require('../lib/validator');
const { ValidationError, TrainingNotFoundError, TrainingAlreadyEnrollError } = require('../error');
const db = require('../service/db');
const coll = db.collection('traning');
const collStatus = db.collection('training.status');
function getStatus(tid, uid) {
return collStatus.findOne({ tid, uid });
function getStatus(domainId, tid, uid) {
return document.getStatus(domainId, document.TYPE_TRAINING, tid, uid);
}
function getMultiStatus(query) {
return collStatus.find(query);
function getMultiStatus(domainId, query) {
return document.getMultiStatus(domainId, document.TYPE_TRAINING, query);
}
async function getListStatus(uid, tids) {
const tsdocs = await getMultiStatus({ uid, tid: { $in: Array.from(new Set(tids)) } }).toArray();
async function getListStatus(domainId, uid, tids) {
const tsdocs = await getMultiStatus(
domainId, { uid, tid: { $in: Array.from(new Set(tids)) } },
).toArray();
const r = {};
for (const tsdoc of tsdocs) r[tsdoc.pid] = tsdoc;
return r;
}
async function enroll(tid, uid) {
async function enroll(domainId, tid, uid) {
try {
await collStatus.insertOne({ tid, uid, enroll: 1 });
await document.setStatus(domainId, document.TYPE_TRAINING, tid, uid, { enroll: 1 });
} catch (e) {
throw new TrainingAlreadyEnrollError(tid, uid);
}
await coll.updateOne({ _id: tid }, { $inc: { enroll: 1 } });
return await document.inc(domainId, document.TYPE_TRAINING, tid, 'enroll', 1);
}
async function setStatus(tid, uid, $set) {
await collStatus.updateOne({ tid, uid }, { $set });
return await collStatus.findOne({ tid, uid }); // eslint-disable-line no-return-await
function setStatus(domainId, tid, uid, $set) {
return document.setStatus(domainId, document.TYPE_TRAINING, tid, uid, $set);
}
function add(domainId, title, content, owner, dag = [], description = '') {
validator.checkTitle(title);
validator.checkIntro(content);
validator.checkDescription(description);
for (const node of dag) {
for (const nid of node.requireNids) {
if (nid >= node._id) throw new ValidationError('dag');
}
}
return document.add(domainId, content, owner, document.TYPE_TRAINING, null, null, null, {
dag,
title,
description,
enroll: 0,
});
}
function edit(domainId, tid, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkIntro($set.content);
if ($set.desc) validator.checkDescription($set.description);
if ($set.dag) {
for (const node of $set.dag) {
for (const nid of node.requireNids) {
assert(nid >= node._id, new ValidationError('dag'));
}
}
}
return document.set(domainId, document.TYPE_TRAINING, tid, $set);
}
global.Hydro.model.training = module.exports = {
@@ -66,54 +95,23 @@ global.Hydro.model.training = module.exports = {
isInvalid(node, doneNids) {
return (!Set.isSuperset(new Set(doneNids), new Set(node.requireNids)));
},
async add(title, content, owner, dag = [], description = '') {
validator.checkTitle(title);
validator.checkIntro(content);
validator.checkDescription(description);
for (const node of dag) {
for (const nid of node.requireNids) {
if (nid >= node._id) throw new ValidationError('dag');
}
}
const res = await coll.insertOne({
content,
owner,
dag,
title,
description,
enroll: 0,
});
return res.insertedId;
},
count: (query) => coll.find(query).count(),
async edit(tid, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkIntro($set.content);
if ($set.desc) validator.checkDescription($set.description);
if ($set.dag) {
for (const node of $set.dag) {
for (const nid of node.requireNids) {
assert(nid >= node._id, new ValidationError('dag'));
}
}
}
await coll.findOneAndUpdate({ _id: tid }, { $set });
const tdoc = await coll.findOne({ _id: tid });
add,
edit,
count: (domainId, query) => document.count(domainId, document.TYPE_TRAINING, query),
async get(domainId, tid) {
const tdoc = await document.get(domainId, document.TYPE_TRAINING, tid);
if (!tdoc) throw new TrainingNotFoundError(tid);
return tdoc;
},
async get(tid) {
const tdoc = await coll.findOne({ _id: tid });
if (!tdoc) throw new TrainingNotFoundError(tid);
return tdoc;
},
async getList(tids) {
const tdocs = await this.getMulti({ _id: { $in: Array.from(new Set(tids)) } }).toArray();
async getList(domainId, tids) {
const tdocs = await this.getMulti(
domainId, { _id: { $in: Array.from(new Set(tids)) } },
).toArray();
const r = {};
for (const tdoc of tdocs) r[tdoc._id] = tdoc;
for (const tdoc of tdocs) r[tdoc.docId] = tdoc;
return r;
},
getMulti: (query) => coll.find(query),
getMulti: (domainId, query) => document.getMulti(domainId, document.TYPE_TRAINING, query),
getMultiStatus,
getStatus,
enroll,
+59 -60
View File
@@ -1,3 +1,4 @@
const document = require('./document');
const system = require('./system');
const { UserNotFoundError, UserAlreadyExistError } = require('../error');
const perm = require('../permission');
@@ -5,7 +6,6 @@ const pwhash = require('../lib/hash.hydro');
const db = require('../service/db');
const coll = db.collection('user');
const collRole = db.collection('role');
class USER {
constructor(user) {
@@ -37,38 +37,46 @@ class USER {
}
}
async function getById(_id) {
async function getPerm(domainId, udoc) {
if (udoc.priv === 1) {
const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, 'admin');
return p.content;
}
const role = await document.getStatus(domainId, document.TYPE_DOMAIN_ROLE, 0, udoc._id);
const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, role || 'default');
return p.content;
}
async function getById(domainId, _id) {
const udoc = await coll.findOne({ _id });
if (!udoc) throw new UserNotFoundError(_id);
const role = await collRole.findOne({ _id: udoc.role || 'default' });
udoc.perm = role.perm;
udoc.perm = await getPerm(domainId, udoc);
return new USER(udoc);
}
async function getList(uids) {
async function getList(domainId, uids) {
const r = {};
for (const uid of uids) r[uid] = await getById(uid); // eslint-disable-line no-await-in-loop
// eslint-disable-next-line no-await-in-loop
for (const uid of uids) r[uid] = await getById(domainId, uid);
return r;
}
async function getByUname(uname) {
async function getByUname(domainId, uname) {
const unameLower = uname.trim().toLowerCase();
const udoc = await coll.findOne({ unameLower });
if (!udoc) throw new UserNotFoundError(uname);
const role = await collRole.findOne({ _id: udoc.role || 'default' });
udoc.perm = role.perm;
udoc.perm = await getPerm(domainId, udoc);
return new USER(udoc);
}
async function getByEmail(mail, ignoreMissing = false) {
async function getByEmail(domainId, mail, ignoreMissing = false) {
const mailLower = mail.trim().toLowerCase();
const udoc = await coll.findOne({ mailLower });
if (!udoc) {
if (ignoreMissing) return null;
throw new UserNotFoundError(mail);
}
const role = await collRole.findOne({ _id: udoc.role || 'default' });
udoc.perm = role.perm;
udoc.perm = await getPerm(domainId, udoc);
return new USER(udoc);
}
@@ -106,30 +114,27 @@ async function inc(_id, field, n = 1) {
}
async function create({
uid, mail, uname, password, regip = '127.0.0.1', role = 'default',
uid, mail, uname, password, regip = '127.0.0.1', priv = perm.PRIV_NONE,
}) {
const salt = String.random();
if (!uid) uid = system.inc('user');
try {
await Promise.all([
coll.insertOne({
_id: uid,
mail,
mailLower: mail.trim().toLowerCase(),
uname,
unameLower: uname.trim().toLowerCase(),
password: pwhash.hash(password, salt),
salt,
hashType: 'hydro',
regat: new Date(),
regip,
loginat: new Date(),
loginip: regip,
role,
gravatar: mail,
}),
collRole.updateOne({ _id: role }, { $inc: { count: 1 } }),
]);
await coll.insertOne({
_id: uid,
mail,
mailLower: mail.trim().toLowerCase(),
uname,
unameLower: uname.trim().toLowerCase(),
password: pwhash.hash(password, salt),
salt,
hashType: 'hydro',
regat: new Date(),
regip,
loginat: new Date(),
loginip: regip,
priv,
gravatar: mail,
});
} catch (e) {
throw new UserAlreadyExistError([uid, uname, mail]);
}
@@ -146,37 +151,31 @@ async function getPrefixList(prefix, limit = 50) {
return udocs;
}
async function setRole(uid, role) {
const udoc = await getById(uid);
return await Promise.all([ // eslint-disable-line no-return-await
coll.findOneAndUpdate({ _id: uid }, { $set: { role } }),
collRole.updateOne({ _id: udoc.role }, { $inc: { count: -1 } }),
collRole.updateOne({ _id: role }, { $inc: { count: 1 } }),
function setRole(domainId, uid, role) {
return document.setStatus(domainId, document.TYPE_DOMAIN_ROLE, 0, uid, { role });
}
function getRoles(domainId) {
return document.getMulti(domainId, document.TYPE_DOMAIN_ROLE).sort('_id', 1).toArray();
}
function getRole(domainId, name) {
return document.get(domainId, document.TYPE_DOMAIN_ROLE, name);
}
function addRole(domainId, name, permission) {
return document.add(domainId, permission, 1, document.TYPE_DOMAIN_ROLE, name);
}
function deleteRoles(domainId, roles) {
return Promise.all([
document.deleteMulti(domainId, document.TYPE_DOMAIN_ROLE, { docId: { $in: roles } }),
document.deleteMultiStatus(domainId, document.TYPE_DOMAIN_ROLE, { role: { $in: roles } }),
]);
}
function getRoles() {
return collRole.find().sort('_id', 1).toArray();
}
function getRole(name) {
return collRole.findOne({ _id: name });
}
function addRole(name, permission) {
return collRole.insertOne({ _id: name, perm: permission, count: 0 });
}
function deleteRoles(roles) {
function ensureIndexes() {
return Promise.all([
coll.updateMany({ role: { $in: roles } }, { $set: { role: 'default' } }),
collRole.deleteMany({ _id: { $in: roles } }),
]);
}
function index() {
return Promise.all([
collRole.updateOne({ _id: 'root' }, { $set: { perm: perm.PERM_ALL } }),
coll.createIndex('unameLower', { unique: true }),
coll.createIndex('mailLower', { sparse: true }),
]);
@@ -200,5 +199,5 @@ global.Hydro.model.user = module.exports = {
getRoles,
addRole,
deleteRoles,
index,
ensureIndexes,
};