import assert from 'assert'; import { ObjectID } from 'mongodb'; import * as db from '../service/db'; type DocID = ObjectID | string | number; const coll = db.collection('document'); const collStatus = db.collection('document.status'); export const TYPE_DOMAIN_USER = 0; export const TYPE_PROBLEM = 10; export const TYPE_PROBLEM_SOLUTION = 11; export const TYPE_PROBLEM_LIST = 12; export const TYPE_DISCUSSION_NODE = 20; export const TYPE_DISCUSSION = 21; export const TYPE_DISCUSSION_REPLY = 22; export const TYPE_CONTEST = 30; export const TYPE_TRAINING = 40; export const TYPE_FILE = 50; export const TYPE_HOMEWORK = 60; export async function add( domainId: string, content: string, owner: number, docType: number, docId: DocID = null, parentType: number | null = null, parentId: DocID = null, args: any = {}, ) { const _id = new ObjectID(); const doc: any = { _id, 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; } export function get(domainId: string, docType: number, docId: DocID) { return coll.findOne({ domainId, docType, docId }); } export async function set(domainId: string, docType: number, docId: DocID, args: any) { const res = await coll.findOneAndUpdate( { domainId, docType, docId }, { $set: args }, { returnOriginal: false }, ); return res.value; } export function deleteOne(domainId: string, docType: number, docId: DocID) { return Promise.all([ coll.deleteMany({ domainId, docType, docId }), coll.deleteOne({ domainId, docType, docId }), ]); } export function deleteMulti(domainId: string, docType: number, args: any = {}) { return coll.deleteMany({ ...args, domainId, docType }); } export function deleteMultiStatus(domainId: string, docType: number, args: any = {}) { return collStatus.deleteMany({ ...args, domainId, docType }); } export function getMulti(domainId: string, docType: number, args: any = {}) { return coll.find({ ...args, docType, domainId }); } export async function inc( domainId: string, docType: number, docId: DocID, key: string, value: number, ) { const res = await coll.findOneAndUpdate( { domainId, docType, docId }, { $inc: { [key]: value } }, { returnOriginal: false }, ); return res.value; } export async function incAndSet( domainId: string, docType: number, docId: DocID, key: string, value: number, args: any, ) { const res = await coll.findOneAndUpdate( { domainId, docType, docId }, { $inc: { [key]: value }, $set: args }, { returnOriginal: false }, ); return res.value; } export function count(domainId: string, docType: number, query: any) { return coll.find({ ...query, docType, domainId }).count(); } export async function push( domainId: string, docType: number, docId: DocID, key: string, content: string, owner: number, args: any = {}, ) { const _id = new ObjectID(); const doc = await coll.findOneAndUpdate( { domainId, docType, docId }, { $push: { [key]: { ...args, content, owner, _id, }, }, }, { returnOriginal: false }, ); return [doc.value, _id]; } export async function pull( domainId: string, docType: number, docId: DocID, setKey: string, contents: string[], ) { const res = await coll.findOneAndUpdate( { domainId, docType, docId }, { $pull: { [setKey]: { $in: contents } } }, { returnOriginal: false }, ); return res.value; } export async function deleteSub( domainId: string, docType: number, docId: DocID, key: string, subId: ObjectID, ) { const res = await coll.findOneAndUpdate( { domainId, docType, docId }, { $pull: { [key]: { _id: subId } } }, { returnOriginal: false }, ); return res.value; } export async function getSub( domainId: string, docType: number, docId: DocID, key: string, subId: ObjectID, ) { 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]; } export async function setSub( domainId: string, docType: number, docId: DocID, key: string, subId: ObjectID, args: any, ) { 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; } export async function addToSet( domainId: string, docType: number, docId: DocID, setKey: string, content: string, ) { const res = await coll.findOneAndUpdate( { domainId, docType, docId }, { $addToSet: { [setKey]: content } }, { returnOriginal: false }, ); return res.value; } export function getStatus(domainId: string, docType: number, docId: DocID, uid: number) { return collStatus.findOne({ domainId, docType, docId, uid, }); } export function getMultiStatus(domainId: string, docType: number, args: any) { return collStatus.find({ ...args, docType, domainId }); } export async function setStatus( domainId: string, docType: number, docId: DocID, uid: number, args: any, ) { const res = await collStatus.findOneAndUpdate( { domainId, docType, docId, uid, }, { $set: args }, { upsert: true, returnOriginal: false }, ); return res.value; } export function setMultiStatus(domainId: string, docType: number, query: any, args: any) { return collStatus.updateMany( { domainId, docType, ...query }, { $set: args }, ); } export async function setIfNotStatus( domainId: string, docType: number, docId: DocID, uid: number, key: string, value: number, ifNot: any, args: any, ) { const res = await collStatus.findOneAndUpdate( { domainId, docType, docId, uid, key: { $not: { $eq: ifNot } }, }, { $set: { [key]: value, args } }, { upsert: true, returnOriginal: false }, ); return res.value; } export async function cappedIncStatus( domainId: string, docType: number, docId: DocID, uid: number, key: string, value: number, 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; } export async function incStatus( domainId: string, docType: number, docId: DocID, uid: number, key: string, value: number, ) { const res = await collStatus.findOneAndUpdate( { domainId, docType, docId, uid, }, { $inc: { [key]: value } }, { upsert: true, returnOriginal: false }, ); return res.value; } export async function revPushStatus( domainId: string, docType: number, docId: DocID, uid: number, key: string, value: any, ) { const res = await collStatus.findOneAndUpdate( { domainId, docType, docId, uid, }, { $push: { [key]: value }, $inc: { rev: 1 } }, { upsert: true, returnOriginal: false }, ); return res.value; } export async function revInitStatus( domainId: string, docType: number, docId: DocID, uid: number, ) { const res = await collStatus.findOneAndUpdate( { domainId, docType, docId, uid, }, { $inc: { rev: 1 } }, { upsert: true, returnOriginal: false }, ); return res.value; } export async function revSetStatus( domainId: string, docType: number, docId: DocID, uid: number, rev: number, args: any, returnDoc = true, ) { const filter = { domainId, docType, docId, uid, rev, }; const update = { $set: args, $inc: { rev: 1 } }; if (returnDoc) { const res = await collStatus.findOneAndUpdate(filter, update, { returnOriginal: false }); return res.value; } return await collStatus.updateOne(filter, update); } export 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 = { 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, setMultiStatus, setSub, TYPE_DOMAIN_USER, TYPE_CONTEST, TYPE_DISCUSSION, TYPE_DISCUSSION_NODE, TYPE_DISCUSSION_REPLY, TYPE_HOMEWORK, TYPE_PROBLEM, TYPE_PROBLEM_LIST, TYPE_PROBLEM_SOLUTION, TYPE_FILE, TYPE_TRAINING, };