diff --git a/packages/fps-importer/handler.ts b/packages/fps-importer/handler.ts index 3d1c53562..2f07b4ef7 100644 --- a/packages/fps-importer/handler.ts +++ b/packages/fps-importer/handler.ts @@ -8,7 +8,7 @@ import type { ProblemConfig } from 'hydrooj'; import { Route, Handler } from 'hydrooj/dist/service/server'; import { buildContent } from 'hydrooj/dist/lib/content'; import { ProblemAdd } from 'hydrooj/dist/lib/ui'; -import * as solution from 'hydrooj/dist/model/solution'; +import solution from 'hydrooj/dist/model/solution'; import { PERM } from 'hydrooj/dist/model/builtin'; import { FileTooLargeError, ValidationError } from 'hydrooj/dist/error'; diff --git a/packages/fps-importer/package.json b/packages/fps-importer/package.json index a6021e09d..1cc0fef2a 100644 --- a/packages/fps-importer/package.json +++ b/packages/fps-importer/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/fps-importer", - "version": "1.1.9", + "version": "1.1.10", "description": "Import FPS problems", "main": "package.json", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 2861a8e9d..044783701 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.20.21", + "version": "2.20.22", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 15fd385cc..624663876 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -13,10 +13,10 @@ import * as contest from '../model/contest'; import * as system from '../model/system'; import * as document from '../model/document'; import * as problem from '../model/problem'; -import * as domain from '../model/domain'; -import * as record from '../model/record'; +import domain from '../model/domain'; +import record from '../model/record'; import user from '../model/user'; -import * as message from '../model/message'; +import message from '../model/message'; import { Route, Handler, Types, param, } from '../service/server'; diff --git a/packages/hydrooj/src/handler/discussion.ts b/packages/hydrooj/src/handler/discussion.ts index 699c8e838..131d1f3e8 100644 --- a/packages/hydrooj/src/handler/discussion.ts +++ b/packages/hydrooj/src/handler/discussion.ts @@ -8,7 +8,7 @@ import paginate from '../lib/paginate'; import * as system from '../model/system'; import user from '../model/user'; import * as oplog from '../model/oplog'; -import * as message from '../model/message'; +import message from '../model/message'; import * as discussion from '../model/discussion'; import * as document from '../model/document'; import { PERM, PRIV } from '../model/builtin'; diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index c0cfbe0db..165a5b1a0 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -7,7 +7,7 @@ import { import { log2 } from '../utils'; import type { DomainDoc } from '../interface'; import user from '../model/user'; -import * as domain from '../model/domain'; +import domain from '../model/domain'; import * as discussion from '../model/discussion'; import * as system from '../model/system'; import { DOMAIN_SETTINGS, DOMAIN_SETTINGS_BY_KEY } from '../model/setting'; diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index 286e0ce7d..cae4762a3 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -11,12 +11,12 @@ import * as misc from '../lib/misc'; import { md5 } from '../lib/crypto'; import * as mail from '../lib/mail'; import * as contest from '../model/contest'; -import * as message from '../model/message'; +import message from '../model/message'; import * as document from '../model/document'; import * as system from '../model/system'; import user from '../model/user'; import * as setting from '../model/setting'; -import * as domain from '../model/domain'; +import domain from '../model/domain'; import * as discussion from '../model/discussion'; import token from '../model/token'; import * as training from '../model/training'; diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index 60201bcc6..8a3433edd 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -12,14 +12,14 @@ import { Route, Handler, Types, param, } from '../service/server'; import * as bus from '../service/bus'; -import * as domain from '../model/domain'; +import domain from '../model/domain'; import { PERM } from '../model/builtin'; import user from '../model/user'; import * as system from '../model/system'; import * as contest from '../model/contest'; import * as discussion from '../model/discussion'; import * as problem from '../model/problem'; -import * as record from '../model/record'; +import record from '../model/record'; import * as document from '../model/document'; import paginate from '../lib/paginate'; import { isTitle, isContent } from '../lib/validator'; diff --git a/packages/hydrooj/src/handler/judge.ts b/packages/hydrooj/src/handler/judge.ts index 15740eacf..4a2f69ae6 100644 --- a/packages/hydrooj/src/handler/judge.ts +++ b/packages/hydrooj/src/handler/judge.ts @@ -3,12 +3,12 @@ import { JudgeResultBody, Rdoc, TestCase } from '../interface'; import { sleep } from '../utils'; import { Logger } from '../logger'; import difficultyAlgorithm from '../lib/difficulty'; -import * as record from '../model/record'; +import record from '../model/record'; import * as problem from '../model/problem'; import * as builtin from '../model/builtin'; import * as contest from '../model/contest'; -import * as domain from '../model/domain'; -import * as task from '../model/task'; +import domain from '../model/domain'; +import task from '../model/task'; import * as bus from '../service/bus'; import { Route, Handler, Connection, ConnectionHandler, post, Types, diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index 38ef0be9c..1a759655e 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -7,7 +7,7 @@ import * as setting from '../model/setting'; import * as system from '../model/system'; import user from '../model/user'; import { STATUS, PRIV } from '../model/builtin'; -import * as record from '../model/record'; +import record from '../model/record'; import { Route, Connection, Handler, ConnectionHandler, param, Types, } from '../service/server'; diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 7bc13cd69..0e1be59d3 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -13,10 +13,10 @@ import { isTitle, isContent, isPid } from '../lib/validator'; import difficultyAlgorithm from '../lib/difficulty'; import * as system from '../model/system'; import * as problem from '../model/problem'; -import * as record from '../model/record'; -import * as domain from '../model/domain'; +import record from '../model/record'; +import domain from '../model/domain'; import user from '../model/user'; -import * as solution from '../model/solution'; +import solution from '../model/solution'; import { PERM, PRIV } from '../model/builtin'; import storage from '../service/storage'; import * as bus from '../service/bus'; diff --git a/packages/hydrooj/src/handler/record.ts b/packages/hydrooj/src/handler/record.ts index 61e10b414..e82b5827b 100644 --- a/packages/hydrooj/src/handler/record.ts +++ b/packages/hydrooj/src/handler/record.ts @@ -4,7 +4,7 @@ import { Rdoc } from '../interface'; import { PERM, STATUS, PRIV } from '../model/builtin'; import * as system from '../model/system'; import * as problem from '../model/problem'; -import * as record from '../model/record'; +import record from '../model/record'; import * as contest from '../model/contest'; import user from '../model/user'; import paginate from '../lib/paginate'; diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index 00f9c9664..62e9d25e7 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -8,11 +8,11 @@ import { Route, Handler, Types, param, post, } from '../service/server'; import user from '../model/user'; -import * as oauth from '../model/oauth'; +import oauth from '../model/oauth'; import token from '../model/token'; -import * as record from '../model/record'; +import record from '../model/record'; import * as problem from '../model/problem'; -import * as task from '../model/task'; +import task from '../model/task'; import * as system from '../model/system'; import { PERM, PRIV } from '../model/builtin'; import { isEmail, isPassword, isUname } from '../lib/validator'; diff --git a/packages/hydrooj/src/model/domain.ts b/packages/hydrooj/src/model/domain.ts index 977654ba9..e96e4a335 100644 --- a/packages/hydrooj/src/model/domain.ts +++ b/packages/hydrooj/src/model/domain.ts @@ -2,6 +2,7 @@ import { Dictionary } from 'lodash'; import { FilterQuery } from 'mongodb'; import { BUILTIN_ROLES, PRIV } from './builtin'; import { DomainDoc } from '../interface'; +import { ArgMethod } from '../utils'; import { NumberKeys } from '../typeutils'; import * as bus from '../service/bus'; import db from '../service/db'; @@ -9,232 +10,218 @@ import db from '../service/db'; const coll = db.collection('domain'); const collUser = db.collection('domain.user'); -export const JOIN_METHOD_NONE = 0; -export const JOIN_METHOD_ALL = 1; -export const JOIN_METHOD_CODE = 2; -export const JOIN_METHOD_RANGE = { - [JOIN_METHOD_NONE]: 'No user is allowed to join this domain', - [JOIN_METHOD_ALL]: 'Any user is allowed to join this domain', - [JOIN_METHOD_CODE]: 'Any user is allowed to join this domain with an invitation code', -}; - -export const JOIN_EXPIRATION_KEEP_CURRENT = 0; -export const JOIN_EXPIRATION_UNLIMITED = -1; - -export const JOIN_EXPIRATION_RANGE = { - [JOIN_EXPIRATION_KEEP_CURRENT]: 'Keep current expiration', - 3: 'In 3 hours', - 24: 'In 1 day', - [24 * 3]: 'In 3 days', - [24 * 7]: 'In 1 week', - [24 * 30]: 'In 1 month', - [JOIN_EXPIRATION_UNLIMITED]: 'Never expire', -}; - -export async function add(domainId: string, owner: number, name: string, bulletin: string) { - const ddoc: DomainDoc = { - _id: domainId, - owner, - name, - bulletin, - roles: {}, - gravatar: '', - pidCounter: 0, - }; - await bus.serial('domain/create', ddoc); - await coll.insertOne(ddoc); - return domainId; -} - -export async function get(domainId: string): Promise { - const query: FilterQuery = { _id: domainId }; - await bus.serial('domain/before-get', query); - const result = await coll.findOne(query); - await bus.serial('domain/get', result); - return result; -} - -export async function getByHost(host: string): Promise { - const query: FilterQuery = { host }; - await bus.serial('domain/before-get', query); - const result = await coll.findOne(query); - await bus.serial('domain/get', result); - return result; -} - -export function getMulti(query: FilterQuery = {}) { - return coll.find(query); -} - -export async function edit(domainId: string, $set: Partial) { - await bus.serial('domain/before-update', domainId, $set); - const result = await coll.findOneAndUpdate({ _id: domainId }, { $set }, { returnOriginal: false }); - await bus.serial('domain/update', domainId, $set, result.value); - return result.value; -} - -export async function inc(domainId: string, field: NumberKeys, n: number): Promise { - const res = await coll.findOneAndUpdate( - { _id: domainId }, - // FIXME - // @ts-ignore - { $inc: { [field]: n } }, - { returnOriginal: false }, - ); - return res.value[field]; -} - -export async function getList(domainIds: string[]): Promise> { - const r = {}; - // eslint-disable-next-line no-await-in-loop - for (const domainId of domainIds) r[domainId] = await get(domainId); - return r; -} - -export function setUserRole(domainId: string, uid: number, role: string) { - return collUser.updateOne({ uid, domainId }, { $set: { role } }, { upsert: true }); -} - -export async function getRoles(domainId: string): Promise; -export async function getRoles(domain: DomainDoc): Promise; -export async function getRoles(arg: string | DomainDoc) { - let ddoc: DomainDoc; - if (typeof arg === 'string') ddoc = await get(arg); - else ddoc = arg; - const roles = []; - const r = []; - for (const role in ddoc.roles) { - roles.push({ _id: role, perm: BigInt(ddoc.roles[role]) }); - r.push(role); - } - for (const role in BUILTIN_ROLES) { - if (!r.includes(role)) { - roles.push({ _id: role, perm: BUILTIN_ROLES[role] }); - } - } - return roles; -} - -export async function setRoles(domainId: string, roles: Dictionary) { - const current = await get(domainId); - for (const role in roles) { - current.roles[role] = roles[role].toString(); - } - return await coll.updateOne({ _id: domainId }, { $set: { roles: current.roles } }); -} - -export async function addRole(domainId: string, name: string, permission: bigint) { - const current = await get(domainId); - current.roles[name] = permission.toString(); - return await coll.updateOne({ _id: domainId }, { $set: { roles: current.roles } }); -} - -export async function deleteRoles(domainId: string, roles: string[]) { - const current = await get(domainId); - for (const role of roles) delete current.roles[role]; - await Promise.all([ - coll.updateOne({ _id: domainId }, { $set: current }), - collUser.updateMany({ domainId, role: { $in: roles } }, { $set: { role: 'default' } }), - ]); -} - interface DomainUserArg { _id: number, priv: number, } -export async function getDomainUser(domainId: string, udoc: DomainUserArg) { - let dudoc = await collUser.findOne({ domainId, uid: udoc._id }); - dudoc = dudoc || {}; - if (!(udoc.priv & PRIV.PRIV_USER_PROFILE)) dudoc.role = 'guest'; - if (udoc.priv & PRIV.PRIV_MANAGE_ALL_DOMAIN) dudoc.role = 'admin'; - dudoc.role = dudoc.role || 'default'; - const ddoc = await get(domainId); - if (ddoc.owner === udoc._id) dudoc.role = 'admin'; - dudoc.perm = ddoc.roles[dudoc.role] - ? BigInt(ddoc.roles[dudoc.role]) - : BUILTIN_ROLES[dudoc.role]; - return dudoc; -} -export function setMultiUserInDomain(domainId: string, query: any, params: any) { - return collUser.updateMany({ domainId, ...query }, { $set: params }); -} +class DomainModel { + static JOIN_METHOD_NONE = 0; + static JOIN_METHOD_ALL = 1; + static JOIN_METHOD_CODE = 2; -export function getMultiUserInDomain(domainId: string, query: any = {}) { - return collUser.find({ domainId, ...query }); -} + static JOIN_METHOD_RANGE = { + [DomainModel.JOIN_METHOD_NONE]: 'No user is allowed to join this domain', + [DomainModel.JOIN_METHOD_ALL]: 'Any user is allowed to join this domain', + [DomainModel.JOIN_METHOD_CODE]: 'Any user is allowed to join this domain with an invitation code', + }; -export function setUserInDomain(domainId: string, uid: number, params: any) { - return collUser.updateOne({ domainId, uid }, { $set: params }); -} + static JOIN_EXPIRATION_KEEP_CURRENT = 0; + static JOIN_EXPIRATION_UNLIMITED = -1; -export async function incUserInDomain(domainId: string, uid: number, field: string, n = 1) { - // @ts-ignore - const dudoc = await getDomainUser(domainId, { _id: uid }); - dudoc[field] = dudoc[field] + n || n; - await setUserInDomain(domainId, uid, { [field]: dudoc[field] }); - return dudoc; -} + static JOIN_EXPIRATION_RANGE = { + [DomainModel.JOIN_EXPIRATION_KEEP_CURRENT]: 'Keep current expiration', + 3: 'In 3 hours', + 24: 'In 1 day', + [24 * 3]: 'In 3 days', + [24 * 7]: 'In 1 week', + [24 * 30]: 'In 1 month', + [DomainModel.JOIN_EXPIRATION_UNLIMITED]: 'Never expire', + }; -export async function getDictUserByDomainId(uid: number) { - const [dudocs, ddocs] = await Promise.all([ - collUser.find({ uid }).toArray(), - coll.find({ owner: uid }).toArray(), - ]); - const dudict = {}; - for (const dudoc of dudocs) { - // eslint-disable-next-line no-await-in-loop - dudict[dudoc.domainId] = await get(dudoc.domainId); - dudict[dudoc.domainId].role = dudoc.role; + @ArgMethod + static async add(domainId: string, owner: number, name: string, bulletin: string) { + const ddoc: DomainDoc = { + _id: domainId, + owner, + name, + bulletin, + roles: {}, + gravatar: '', + pidCounter: 0, + }; + await bus.serial('domain/create', ddoc); + await coll.insertOne(ddoc); + return domainId; + } + + @ArgMethod + static async get(domainId: string): Promise { + const query: FilterQuery = { _id: domainId }; + await bus.serial('domain/before-get', query); + const result = await coll.findOne(query); + await bus.serial('domain/get', result); + return result; + } + + @ArgMethod + static async getByHost(host: string): Promise { + const query: FilterQuery = { host }; + await bus.serial('domain/before-get', query); + const result = await coll.findOne(query); + await bus.serial('domain/get', result); + return result; + } + + static getMulti(query: FilterQuery = {}) { + return coll.find(query); + } + + static async edit(domainId: string, $set: Partial) { + await bus.serial('domain/before-update', domainId, $set); + const result = await coll.findOneAndUpdate({ _id: domainId }, { $set }, { returnOriginal: false }); + await bus.serial('domain/update', domainId, $set, result.value); + return result.value; + } + + @ArgMethod + static async inc(domainId: string, field: NumberKeys, n: number): Promise { + const res = await coll.findOneAndUpdate( + { _id: domainId }, + // FIXME + // @ts-ignore + { $inc: { [field]: n } }, + { returnOriginal: false }, + ); + return res.value[field]; + } + + @ArgMethod + static async getList(domainIds: string[]): Promise> { + const r = {}; + // eslint-disable-next-line no-await-in-loop + for (const domainId of domainIds) r[domainId] = await DomainModel.get(domainId); + return r; + } + + @ArgMethod + static setUserRole(domainId: string, uid: number, role: string) { + return collUser.updateOne({ uid, domainId }, { $set: { role } }, { upsert: true }); + } + + static async getRoles(domainId: string): Promise; + static async getRoles(domain: DomainDoc): Promise; + @ArgMethod + static async getRoles(arg: string | DomainDoc) { + let ddoc: DomainDoc; + if (typeof arg === 'string') ddoc = await DomainModel.get(arg); + else ddoc = arg; + const roles = []; + const r = []; + for (const role in ddoc.roles) { + roles.push({ _id: role, perm: BigInt(ddoc.roles[role]) }); + r.push(role); + } + for (const role in BUILTIN_ROLES) { + if (!r.includes(role)) { + roles.push({ _id: role, perm: BUILTIN_ROLES[role] }); + } + } + return roles; + } + + static async setRoles(domainId: string, roles: Dictionary) { + const current = await DomainModel.get(domainId); + for (const role in roles) { + current.roles[role] = roles[role].toString(); + } + return await coll.updateOne({ _id: domainId }, { $set: { roles: current.roles } }); + } + + static async addRole(domainId: string, name: string, permission: bigint) { + const current = await DomainModel.get(domainId); + current.roles[name] = permission.toString(); + return await coll.updateOne({ _id: domainId }, { $set: { roles: current.roles } }); + } + + static async deleteRoles(domainId: string, roles: string[]) { + const current = await DomainModel.get(domainId); + for (const role of roles) delete current.roles[role]; + await Promise.all([ + coll.updateOne({ _id: domainId }, { $set: current }), + collUser.updateMany({ domainId, role: { $in: roles } }, { $set: { role: 'default' } }), + ]); + } + + static async getDomainUser(domainId: string, udoc: DomainUserArg) { + let dudoc = await collUser.findOne({ domainId, uid: udoc._id }); + dudoc = dudoc || {}; + if (!(udoc.priv & PRIV.PRIV_USER_PROFILE)) dudoc.role = 'guest'; + if (udoc.priv & PRIV.PRIV_MANAGE_ALL_DOMAIN) dudoc.role = 'admin'; + dudoc.role = dudoc.role || 'default'; + const ddoc = await DomainModel.get(domainId); + if (ddoc.owner === udoc._id) dudoc.role = 'admin'; + dudoc.perm = ddoc.roles[dudoc.role] + ? BigInt(ddoc.roles[dudoc.role]) + : BUILTIN_ROLES[dudoc.role]; + return dudoc; + } + + static setMultiUserInDomain(domainId: string, query: any, params: any) { + return collUser.updateMany({ domainId, ...query }, { $set: params }); + } + + static getMultiUserInDomain(domainId: string, query: any = {}) { + return collUser.find({ domainId, ...query }); + } + + static setUserInDomain(domainId: string, uid: number, params: any) { + return collUser.updateOne({ domainId, uid }, { $set: params }); + } + + @ArgMethod + static async incUserInDomain(domainId: string, uid: number, field: string, n: number = 1) { + // @ts-ignore + const dudoc = await getDomainUser(domainId, { _id: uid }); + dudoc[field] = dudoc[field] + n || n; + await DomainModel.setUserInDomain(domainId, uid, { [field]: dudoc[field] }); + return dudoc; + } + + @ArgMethod + static async getDictUserByDomainId(uid: number) { + const [dudocs, ddocs] = await Promise.all([ + collUser.find({ uid }).toArray(), + coll.find({ owner: uid }).toArray(), + ]); + const dudict = {}; + for (const dudoc of dudocs) { + // eslint-disable-next-line no-await-in-loop + dudict[dudoc.domainId] = await DomainModel.get(dudoc.domainId); + dudict[dudoc.domainId].role = dudoc.role; + } + for (const ddoc of ddocs) dudict[ddoc._id] = 'admin'; + return dudict; + } + + static getJoinSettings(ddoc: DomainDoc, roles: string[]) { + if (!ddoc.join) return null; + const joinSettings = ddoc.join; + if (joinSettings.method === DomainModel.JOIN_METHOD_NONE) return null; + if (!roles.includes(joinSettings.role)) return null; + if (joinSettings.expire && joinSettings.expire < new Date()) return null; + return joinSettings; + } + + @ArgMethod + static async getPrefixSearch(prefix: string, limit: number = 50) { + const $regex = new RegExp(prefix, 'mi'); + const ddocs = await coll.find({ + $or: [{ _id: { $regex } }, { name: { $regex } }], + }).limit(limit).toArray(); + return ddocs; } - for (const ddoc of ddocs) dudict[ddoc._id] = 'admin'; - return dudict; } -export function getJoinSettings(ddoc: DomainDoc, roles: string[]) { - if (!ddoc.join) return null; - const joinSettings = ddoc.join; - if (joinSettings.method === JOIN_METHOD_NONE) return null; - if (!roles.includes(joinSettings.role)) return null; - if (joinSettings.expire && joinSettings.expire < new Date()) return null; - return joinSettings; -} - -export async function getPrefixSearch(prefix: string, limit = 50) { - const $regex = new RegExp(prefix, 'mi'); - const ddocs = await coll.find({ - $or: [{ _id: { $regex } }, { name: { $regex } }], - }).limit(limit).toArray(); - return ddocs; -} - -global.Hydro.model.domain = { - JOIN_METHOD_NONE, - JOIN_METHOD_ALL, - JOIN_METHOD_CODE, - JOIN_METHOD_RANGE, - JOIN_EXPIRATION_KEEP_CURRENT, - JOIN_EXPIRATION_UNLIMITED, - JOIN_EXPIRATION_RANGE, - - getRoles, - add, - inc, - get, - getByHost, - edit, - getMulti, - getList, - setRoles, - addRole, - deleteRoles, - setUserRole, - getDomainUser, - setMultiUserInDomain, - setUserInDomain, - incUserInDomain, - getMultiUserInDomain, - getDictUserByDomainId, - getJoinSettings, - getPrefixSearch, -}; +export = DomainModel; +global.Hydro.model.domain = DomainModel; diff --git a/packages/hydrooj/src/model/message.ts b/packages/hydrooj/src/model/message.ts index 2168d9447..76df8fc42 100644 --- a/packages/hydrooj/src/model/message.ts +++ b/packages/hydrooj/src/model/message.ts @@ -1,65 +1,71 @@ import { FilterQuery, ObjectID } from 'mongodb'; import user from './user'; import { Mdoc } from '../interface'; +import { ArgMethod } from '../utils'; import db from '../service/db'; import * as bus from '../service/bus'; const coll = db.collection('message'); -export const FLAG_UNREAD = 1; -export const FLAG_ALERT = 2; +class MessageModel { + static FLAG_UNREAD = 1; + static FLAG_ALERT = 2; -export async function send( - from: number, to: number, - content: string, flag: number = FLAG_UNREAD, -): Promise { - const res = await coll.insertOne({ - from, to, content, flag, - }); - const mdoc = { - from, to, content, _id: res.insertedId, flag, - }; - if (from !== to) { - // ENHANCE domainId? - const udoc = await user.getById('system', to); - bus.boardcast('user/message', to, mdoc, udoc); + @ArgMethod + static async send( + from: number, to: number, + content: string, flag: number = MessageModel.FLAG_UNREAD, + ): Promise { + const res = await coll.insertOne({ + from, to, content, flag, + }); + const mdoc = { + from, to, content, _id: res.insertedId, flag, + }; + if (from !== to) { + // ENHANCE domainId? + const udoc = await user.getById('system', to); + bus.boardcast('user/message', to, mdoc, udoc); + } + return mdoc; } - return mdoc; -} -export async function get(_id: ObjectID): Promise { - return await coll.findOne({ _id }); -} + static async get(_id: ObjectID): Promise { + return await coll.findOne({ _id }); + } -export async function getByUser(uid: number): Promise { - return await coll.find({ $or: [{ from: uid }, { to: uid }] }).sort('_id', 1).toArray(); -} + @ArgMethod + static async getByUser(uid: number): Promise { + return await coll.find({ $or: [{ from: uid }, { to: uid }] }).sort('_id', 1).toArray(); + } -export async function getMany(query: FilterQuery, sort: any, page: number, limit: number): Promise { - return await coll.find(query).sort(sort) - .skip((page - 1) * limit).limit(limit) - .toArray(); -} + static async getMany(query: FilterQuery, sort: any, page: number, limit: number): Promise { + return await coll.find(query).sort(sort) + .skip((page - 1) * limit).limit(limit) + .toArray(); + } -export async function setFlag(messageId: ObjectID, flag: number): Promise { - const result = await coll.findOneAndUpdate( - { _id: messageId }, - { $bit: { flag: { xor: flag } } }, - { returnOriginal: false }, - ); - return result.value; -} + static async setFlag(messageId: ObjectID, flag: number): Promise { + const result = await coll.findOneAndUpdate( + { _id: messageId }, + { $bit: { flag: { xor: flag } } }, + { returnOriginal: false }, + ); + return result.value; + } -export async function del(_id: ObjectID) { - return await coll.deleteOne({ _id }); -} + static async del(_id: ObjectID) { + return await coll.deleteOne({ _id }); + } -export function count(query: FilterQuery = {}) { - return coll.find(query).count(); -} + @ArgMethod + static count(query: FilterQuery = {}) { + return coll.find(query).count(); + } -export function getMulti(uid: number) { - return coll.find({ $or: [{ from: uid }, { to: uid }] }); + static getMulti(uid: number) { + return coll.find({ $or: [{ from: uid }, { to: uid }] }); + } } function ensureIndexes() { @@ -70,16 +76,5 @@ function ensureIndexes() { } bus.once('app/started', ensureIndexes); -global.Hydro.model.message = { - FLAG_UNREAD, - FLAG_ALERT, - - count, - get, - getByUser, - del, - setFlag, - getMany, - getMulti, - send, -}; +export = MessageModel; +global.Hydro.model.message = MessageModel; diff --git a/packages/hydrooj/src/model/oauth.ts b/packages/hydrooj/src/model/oauth.ts index 2734f0b51..3d8db6ba9 100644 --- a/packages/hydrooj/src/model/oauth.ts +++ b/packages/hydrooj/src/model/oauth.ts @@ -12,25 +12,25 @@ const cache = new Lru({ maxAge: 5000, }); -export async function get(_id: string) { - const res = cache.get(_id); - if (res !== undefined) return res; - const doc = await coll.findOne({ _id }); - if (doc) return doc.uid; - return null; +class OauthModel { + static async get(_id: string) { + const res = cache.get(_id); + if (res !== undefined) return res; + const doc = await coll.findOne({ _id }); + if (doc) return doc.uid; + return null; + } + + static async set(_id: string, value: number) { + cache.set(_id, value); + const res = await coll.findOneAndUpdate( + { _id }, + { $set: { value } }, + { upsert: true, returnOriginal: false }, + ); + return res.value.uid; + } } -export async function set(_id: string, value: number) { - cache.set(_id, value); - const res = await coll.findOneAndUpdate( - { _id }, - { $set: { value } }, - { upsert: true, returnOriginal: false }, - ); - return res.value.uid; -} - -global.Hydro.model.oauth = { - get, - set, -}; +export = OauthModel; +global.Hydro.model.oauth = OauthModel; diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index d6a014802..4c4134d66 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -3,7 +3,7 @@ import { ObjectID, FilterQuery } from 'mongodb'; import { Dictionary, pick } from 'lodash'; import { STATUS } from './builtin'; import * as document from './document'; -import * as domain from './domain'; +import domain from './domain'; import { buildProjection } from '../utils'; import type { ProblemStatusDoc, Pdict, Document } from '../interface'; import { Content } from '../loader'; diff --git a/packages/hydrooj/src/model/record.ts b/packages/hydrooj/src/model/record.ts index 2bc44df7c..35a6d6c0e 100644 --- a/packages/hydrooj/src/model/record.ts +++ b/packages/hydrooj/src/model/record.ts @@ -4,213 +4,167 @@ import { import { Dictionary } from 'lodash'; import moment from 'moment'; import { STATUS } from './builtin'; -import * as task from './task'; +import task from './task'; import * as problem from './problem'; -import { - Rdoc, TestCase, ContestInfo, ProblemConfig, -} from '../interface'; -import { Time } from '../utils'; +import { Rdoc, ContestInfo, ProblemConfig } from '../interface'; +import { ArgMethod, Time } from '../utils'; import * as bus from '../service/bus'; import db from '../service/db'; -export const coll: Collection = db.collection('record'); +class RecordModel { + static coll: Collection = db.collection('record'); -export interface RdocBase { - _id?: ObjectID, - domainId?: string, - pid: number - uid: number, - lang: string, - code: string, - score?: number, - memory?: number, - time?: number, - judgeTexts?: string[], - compilerTexts?: string[], - testCases?: TestCase[], - rejudged?: boolean, - judger?: string, - judgeAt?: Date, - status?: number, - type?: string, - hidden?: boolean, - input?: string, - tid?: ObjectID, - ttype?: number, -} - -export interface JudgeTask { - _id: ObjectID, - rid: ObjectID, - domainId: string, - pid: number - lang: string, - code: string, - data?: ObjectID, - config: string, - type?: string, -} - -export async function get(domainId: string, _id: ObjectID): Promise { - const res = await coll.findOne({ _id }); - if (res && res.domainId === domainId) return res; - return null; -} - -export async function stat(domainId?: string) { - const [d5min, d1h, day, week, month, year, total] = await Promise.all([ - coll.find({ _id: { $gte: Time.getObjectID(moment().add(-5, 'minutes')) }, ...domainId ? { domainId } : {} }).count(), - coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'hour')) }, ...domainId ? { domainId } : {} }).count(), - coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'day')) }, ...domainId ? { domainId } : {} }).count(), - coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'week')) }, ...domainId ? { domainId } : {} }).count(), - coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'month')) }, ...domainId ? { domainId } : {} }).count(), - coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'year')) }, ...domainId ? { domainId } : {} }).count(), - coll.find(domainId ? { domainId } : {}).count(), - ]); - return { - d5min, d1h, day, week, month, year, total, - }; -} - -export async function judge(domainId: string, rid: ObjectID, priority = 1, config: ProblemConfig = {}) { - const rdoc = await get(domainId, rid); - let data = []; - if (rdoc.pid) { - const pdoc = await problem.get(domainId, rdoc.pid); - data = pdoc.data; + static async get(domainId: string, _id: ObjectID): Promise { + const res = await RecordModel.coll.findOne({ _id }); + if (res && res.domainId === domainId) return res; + return null; } - delete rdoc._id; - await task.add({ - ...rdoc, - priority, - type: 'judge', - rid, - domainId, - config, - data, - }); -} -export async function add( - domainId: string, pid: number, uid: number, - lang: string, code: string, addTask: boolean, contestOrConfig?: ContestInfo | string, -) { - const data: Rdoc = { - status: STATUS.STATUS_WAITING, - _id: new ObjectID(), - uid, - code, - lang, - pid, - domainId, - score: 0, - time: 0, - memory: 0, - hidden: false, - judgeTexts: [], - compilerTexts: [], - testCases: [], - judger: null, - judgeAt: null, - rejudged: false, - }; - if (typeof contestOrConfig === 'string') { - // is Run - data.input = contestOrConfig; - data.hidden = true; - } else data.contest = contestOrConfig; - const res = await coll.insertOne(data); - if (addTask) await judge(domainId, res.insertedId); - return res.insertedId; -} - -export function getMulti(domainId: string, query: any) { - return coll.find({ ...query, domainId }); -} - -export async function update( - domainId: string, _id: ObjectID, - $set?: MatchKeysAndValues, - $push?: PushOperator, - $unset?: OnlyFieldsOfType, -): Promise { - const $update: UpdateQuery = {}; - if ($set && Object.keys($set).length) $update.$set = $set; - if ($push && Object.keys($push).length) $update.$push = $push; - if ($unset && Object.keys($unset).length) $update.$unset = $unset; - if (Object.keys($update).length) { - const res = await coll.findOneAndUpdate( - { _id, domainId }, - $update, - { returnOriginal: false }, - ); - return res.value; + @ArgMethod + static async stat(domainId?: string) { + const [d5min, d1h, day, week, month, year, total] = await Promise.all([ + RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-5, 'minutes')) }, ...domainId ? { domainId } : {} }).count(), + RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'hour')) }, ...domainId ? { domainId } : {} }).count(), + RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'day')) }, ...domainId ? { domainId } : {} }).count(), + RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'week')) }, ...domainId ? { domainId } : {} }).count(), + RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'month')) }, ...domainId ? { domainId } : {} }).count(), + RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'year')) }, ...domainId ? { domainId } : {} }).count(), + RecordModel.coll.find(domainId ? { domainId } : {}).count(), + ]); + return { + d5min, d1h, day, week, month, year, total, + }; } - return await get(domainId, _id); -} -export function reset(domainId: string, rid: ObjectID, isRejudge: boolean) { - const upd: any = { - score: 0, - status: STATUS.STATUS_WAITING, - time: 0, - memory: 0, - testCases: [], - judgeTexts: [], - compilerTexts: [], - judgeAt: null, - judger: null, - }; - if (isRejudge) upd.rejudged = true; - return update(domainId, rid, upd); -} - -export function count(domainId: string, query: any) { - return coll.find({ domainId, ...query }).count(); -} - -export async function getList( - domainId: string, rids: ObjectID[], showHidden: boolean, -): Promise> { - const r = {}; - rids = Array.from(new Set(rids)); - const rdocs = await coll.find({ domainId, _id: { $in: rids } }).toArray(); - for (const rdoc of rdocs) { - if (rdoc.hidden && !showHidden) r[rdoc._id.toHexString()] = null; - else r[rdoc._id.toHexString()] = rdoc; - } - return r; -} - -export function getUserInProblemMulti( - domainId: string, uid: number, pid: number, - getHidden = false, -) { - if (!getHidden) { - return coll.find({ - domainId, uid, pid, hidden: false, + static async judge(domainId: string, rid: ObjectID, priority = 1, config: ProblemConfig = {}) { + const rdoc = await RecordModel.get(domainId, rid); + let data = []; + if (rdoc.pid) { + const pdoc = await problem.get(domainId, rdoc.pid); + data = pdoc.data; + } + delete rdoc._id; + await task.add({ + ...rdoc, + priority, + type: 'judge', + rid, + domainId, + config, + data, }); } - return coll.find({ domainId, uid, pid }); + + static async add( + domainId: string, pid: number, uid: number, + lang: string, code: string, addTask: boolean, contestOrConfig?: ContestInfo | string, + ) { + const data: Rdoc = { + status: STATUS.STATUS_WAITING, + _id: new ObjectID(), + uid, + code, + lang, + pid, + domainId, + score: 0, + time: 0, + memory: 0, + hidden: false, + judgeTexts: [], + compilerTexts: [], + testCases: [], + judger: null, + judgeAt: null, + rejudged: false, + }; + if (typeof contestOrConfig === 'string') { + // is Run + data.input = contestOrConfig; + data.hidden = true; + } else data.contest = contestOrConfig; + const res = await RecordModel.coll.insertOne(data); + if (addTask) await RecordModel.judge(domainId, res.insertedId); + return res.insertedId; + } + + static getMulti(domainId: string, query: any) { + return RecordModel.coll.find({ ...query, domainId }); + } + + static async update( + domainId: string, _id: ObjectID, + $set?: MatchKeysAndValues, + $push?: PushOperator, + $unset?: OnlyFieldsOfType, + ): Promise { + const $update: UpdateQuery = {}; + if ($set && Object.keys($set).length) $update.$set = $set; + if ($push && Object.keys($push).length) $update.$push = $push; + if ($unset && Object.keys($unset).length) $update.$unset = $unset; + if (Object.keys($update).length) { + const res = await RecordModel.coll.findOneAndUpdate( + { _id, domainId }, + $update, + { returnOriginal: false }, + ); + return res.value; + } + return await RecordModel.get(domainId, _id); + } + + static reset(domainId: string, rid: ObjectID, isRejudge: boolean) { + const upd: any = { + score: 0, + status: STATUS.STATUS_WAITING, + time: 0, + memory: 0, + testCases: [], + judgeTexts: [], + compilerTexts: [], + judgeAt: null, + judger: null, + }; + if (isRejudge) upd.rejudged = true; + return RecordModel.update(domainId, rid, upd); + } + + static count(domainId: string, query: any) { + return RecordModel.coll.find({ domainId, ...query }).count(); + } + + static async getList( + domainId: string, rids: ObjectID[], showHidden: boolean, + ): Promise> { + const r = {}; + rids = Array.from(new Set(rids)); + const rdocs = await RecordModel.coll.find({ domainId, _id: { $in: rids } }).toArray(); + for (const rdoc of rdocs) { + if (rdoc.hidden && !showHidden) r[rdoc._id.toHexString()] = null; + else r[rdoc._id.toHexString()] = rdoc; + } + return r; + } + + @ArgMethod + static getUserInProblemMulti( + domainId: string, uid: number, pid: number, + getHidden: boolean = false, + ) { + if (!getHidden) { + return RecordModel.coll.find({ + domainId, uid, pid, hidden: false, + }); + } + return RecordModel.coll.find({ domainId, uid, pid }); + } + + @ArgMethod + static getByUid(domainId: string, uid: number, limit: number): Promise { + return RecordModel.coll.find({ domainId, uid }).limit(limit).toArray(); + } } -export function getByUid(domainId: string, uid: number, limit: number): Promise { - return coll.find({ domainId, uid }).limit(limit).toArray(); -} - -bus.on('problem/delete', (domainId, docId) => coll.deleteMany({ domainId, pid: docId })); - -global.Hydro.model.record = { - coll, - add, - get, - getMulti, - stat, - update, - count, - reset, - getList, - getUserInProblemMulti, - getByUid, - judge, -}; +bus.on('problem/delete', (domainId, docId) => RecordModel.coll.deleteMany({ domainId, pid: docId })); +export = RecordModel; +global.Hydro.model.record = RecordModel; diff --git a/packages/hydrooj/src/model/solution.ts b/packages/hydrooj/src/model/solution.ts index d4bb726af..a4fc516c6 100644 --- a/packages/hydrooj/src/model/solution.ts +++ b/packages/hydrooj/src/model/solution.ts @@ -3,80 +3,82 @@ import * as document from './document'; import { SolutionNotFoundError } from '../error'; import * as bus from '../service/bus'; -export function add(domainId: string, pid: number, owner: number, content: string) { - return document.add( - domainId, content, owner, document.TYPE_PROBLEM_SOLUTION, - null, document.TYPE_PROBLEM, pid, { reply: [], vote: 0 }, - ); -} +class SolutionModel { + static add(domainId: string, pid: number, owner: number, content: string) { + return document.add( + domainId, content, owner, document.TYPE_PROBLEM_SOLUTION, + null, document.TYPE_PROBLEM, pid, { reply: [], vote: 0 }, + ); + } -export async function get(domainId: string, psid: ObjectID) { - const psdoc = await document.get(domainId, document.TYPE_PROBLEM_SOLUTION, psid); - if (!psdoc) throw new SolutionNotFoundError(); - return psdoc; -} + static async get(domainId: string, psid: ObjectID) { + const psdoc = await document.get(domainId, document.TYPE_PROBLEM_SOLUTION, psid); + if (!psdoc) throw new SolutionNotFoundError(); + return psdoc; + } -export function getMany(domainId: string, query: any, sort: any, page: number, limit: number) { - return document.getMulti(domainId, document.TYPE_PROBLEM_SOLUTION, query) - .sort(sort) - .skip((page - 1) * limit).limit(limit) - .toArray(); -} + static getMany(domainId: string, query: any, sort: any, page: number, limit: number) { + return document.getMulti(domainId, document.TYPE_PROBLEM_SOLUTION, query) + .sort(sort) + .skip((page - 1) * limit).limit(limit) + .toArray(); + } -export function edit(domainId: string, psid: ObjectID, content: string) { - return document.set(domainId, document.TYPE_PROBLEM_SOLUTION, psid, { content }); -} + static edit(domainId: string, psid: ObjectID, content: string) { + return document.set(domainId, document.TYPE_PROBLEM_SOLUTION, psid, { content }); + } -export async function del(domainId: string, psid: ObjectID) { - return await Promise.all([ - document.deleteOne(domainId, document.TYPE_PROBLEM_SOLUTION, psid), - document.deleteMultiStatus(domainId, document.TYPE_PROBLEM_SOLUTION, { docId: psid }), - ]); -} + static async del(domainId: string, psid: ObjectID) { + return await Promise.all([ + document.deleteOne(domainId, document.TYPE_PROBLEM_SOLUTION, psid), + document.deleteMultiStatus(domainId, document.TYPE_PROBLEM_SOLUTION, { docId: psid }), + ]); + } -export function count(domainId: string, query: any) { - return document.count(domainId, document.TYPE_PROBLEM_SOLUTION, query); -} + static count(domainId: string, query: any) { + return document.count(domainId, document.TYPE_PROBLEM_SOLUTION, query); + } -export function getMulti(domainId: string, pid: number) { - return document.getMulti( - domainId, document.TYPE_PROBLEM_SOLUTION, - { parentType: document.TYPE_PROBLEM, parentId: pid }, - ).sort({ vote: -1 }); -} + static getMulti(domainId: string, pid: number) { + return document.getMulti( + domainId, document.TYPE_PROBLEM_SOLUTION, + { parentType: document.TYPE_PROBLEM, parentId: pid }, + ).sort({ vote: -1 }); + } -export function reply(domainId: string, psid: ObjectID, owner: number, content: string) { - return document.push(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', content, owner); -} + static reply(domainId: string, psid: ObjectID, owner: number, content: string) { + return document.push(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', content, owner); + } -export function getReply(domainId: string, psid: ObjectID, psrid: ObjectID) { - return document.getSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid); -} + static getReply(domainId: string, psid: ObjectID, psrid: ObjectID) { + return document.getSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid); + } -export function editReply(domainId: string, psid: ObjectID, psrid: ObjectID, content: string) { - return document.setSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid, { content }); -} + static editReply(domainId: string, psid: ObjectID, psrid: ObjectID, content: string) { + return document.setSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid, { content }); + } -export function delReply(domainId: string, psid: ObjectID, psrid: ObjectID) { - return document.deleteSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid); -} + static delReply(domainId: string, psid: ObjectID, psrid: ObjectID) { + return document.deleteSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid); + } -export async function vote(domainId: string, psid: ObjectID, uid: number, value: number) { - 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; - 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]; -} + static async vote(domainId: string, psid: ObjectID, uid: number, value: number) { + 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; + 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]; + } -export async function getListStatus(domainId: string, psids: ObjectID[], uid: number) { - const result = {}; - const res = await document.getMultiStatus( - domainId, document.TYPE_PROBLEM_SOLUTION, { uid, psid: { $in: psids } }, - ).toArray(); - for (const i of res) result[i.psid] = i; - return result; + static async getListStatus(domainId: string, psids: ObjectID[], uid: number) { + const result = {}; + const res = await document.getMultiStatus( + domainId, document.TYPE_PROBLEM_SOLUTION, { uid, psid: { $in: psids } }, + ).toArray(); + for (const i of res) result[i.psid] = i; + return result; + } } bus.on('problem/delete', async (domainId, docId) => { @@ -90,18 +92,5 @@ bus.on('problem/delete', async (domainId, docId) => { ]); }); -global.Hydro.model.solution = { - count, - add, - get, - edit, - del, - getMany, - getMulti, - reply, - getReply, - editReply, - delReply, - vote, - getListStatus, -}; +export = SolutionModel; +global.Hydro.model.solution = SolutionModel; diff --git a/packages/hydrooj/src/model/task.ts b/packages/hydrooj/src/model/task.ts index bdbcc111c..eecefeed6 100644 --- a/packages/hydrooj/src/model/task.ts +++ b/packages/hydrooj/src/model/task.ts @@ -7,98 +7,95 @@ import db from '../service/db'; const logger = new Logger('model/task'); const coll = db.collection('task'); -export async function add(task: Partial & { type: string }) { - const t: Task = { - ...task, - count: task.count ?? 1, - priority: task.priority ?? 1, - executeAfter: task.executeAfter || new Date(), - _id: new ObjectID(), - }; - const res = await coll.insertOne(t); - return res.insertedId; -} - -export function get(_id: ObjectID) { - return coll.findOne({ _id }); -} - -export function count(query: FilterQuery) { - return coll.find(query).count(); -} - -export function del(_id: ObjectID) { - return coll.deleteOne({ _id }); -} - -export async function getFirst(query: FilterQuery) { - const q = { ...query }; - q.executeAfter = q.executeAfter || { $lt: new Date() }; - const res = await coll.findOneAndDelete(q, { sort: { priority: -1 } }); - if (res.value) { - logger.debug('%o', res.value); - if (res.value.interval) { - await coll.insertOne({ - ...res.value, executeAfter: moment().add(...res.value.interval).toDate(), - }); - } - return res.value; +class TaskModel { + static async add(task: Partial & { type: string }) { + const t: Task = { + ...task, + count: task.count ?? 1, + priority: task.priority ?? 1, + executeAfter: task.executeAfter || new Date(), + _id: new ObjectID(), + }; + const res = await coll.insertOne(t); + return res.insertedId; } - return null; -} -export async function consume(query: any, cb: Function) { - let isRunning = false; - const interval = setInterval(async () => { - if (isRunning) return; - isRunning = true; - const res = await getFirst(query); - if (res) { - try { - await cb(res); - } catch (e) { - clearInterval(interval); + static get(_id: ObjectID) { + return coll.findOne({ _id }); + } + + static count(query: FilterQuery) { + return coll.find(query).count(); + } + + static del(_id: ObjectID) { + return coll.deleteOne({ _id }); + } + + static async getFirst(query: FilterQuery) { + const q = { ...query }; + q.executeAfter = q.executeAfter || { $lt: new Date() }; + const res = await coll.findOneAndDelete(q, { sort: { priority: -1 } }); + if (res.value) { + logger.debug('%o', res.value); + if (res.value.interval) { + await coll.insertOne({ + ...res.value, executeAfter: moment().add(...res.value.interval).toDate(), + }); + } + return res.value; + } + return null; + } + + static async consume(query: any, cb: Function) { + let isRunning = false; + const interval = setInterval(async () => { + if (isRunning) return; + isRunning = true; + const res = await TaskModel.getFirst(query); + if (res) { + try { + await cb(res); + } catch (e) { + clearInterval(interval); + } + } + isRunning = false; + }, 100); + } + + Consumer = class Consumer { + consuming: boolean; + running?: Promise; + interval: NodeJS.Timeout; + filter: any; + func: Function; + + constructor(filter: any, func: Function) { + this.consuming = true; + this.filter = filter; + this.func = func; + this.interval = setInterval(this.consume.bind(this), 100); + } + + async consume() { + if (this.running || !this.consuming) return; + const res = await TaskModel.getFirst(this.filter); + if (res) { + this.running = this.func(res); + if (this.running instanceof Promise) await this.running; + this.running = null; } } - isRunning = false; - }, 100); -} -export class Consumer { - consuming: boolean; - - running?: Promise; - - interval: NodeJS.Timeout; - - filter: any; - - func: Function; - - constructor(filter: any, func: Function) { - this.consuming = true; - this.filter = filter; - this.func = func; - this.interval = setInterval(this.consume.bind(this), 100); - } - - async consume() { - if (this.running || !this.consuming) return; - const res = await getFirst(this.filter); - if (res) { - this.running = this.func(res); - if (this.running instanceof Promise) await this.running; - this.running = null; + async destory() { + this.consuming = false; + clearInterval(this.interval); + if (this.running) await this.running; } - } - - async destory() { - this.consuming = false; - clearInterval(this.interval); - if (this.running) await this.running; - } + }; } -global.Hydro.model.task = { - Consumer, add, get, del, count, getFirst, consume, -}; +export = TaskModel; +global.Hydro.model.task = TaskModel; diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index 55b1f2dc9..f0d4d2d26 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -2,7 +2,7 @@ import { Collection } from 'mongodb'; import * as system from './system'; import token from './token'; import * as setting from './setting'; -import * as domain from './domain'; +import domain from './domain'; import { BUILTIN_USERS, PERM, PRIV } from './builtin'; import { ArgMethod } from '../utils'; import { UserNotFoundError, UserAlreadyExistError, LoginError } from '../error'; diff --git a/packages/hydrooj/src/script/rating.ts b/packages/hydrooj/src/script/rating.ts index c701b0799..8c3f46c0e 100644 --- a/packages/hydrooj/src/script/rating.ts +++ b/packages/hydrooj/src/script/rating.ts @@ -2,10 +2,10 @@ import { NumericDictionary } from 'lodash'; import { ObjectID, FilterQuery } from 'mongodb'; import { Tdoc, Pdoc, Udoc } from '../interface'; -import * as domain from '../model/domain'; +import domain from '../model/domain'; import * as contest from '../model/contest'; import * as problem from '../model/problem'; -import * as record from '../model/record'; +import record from '../model/record'; import { STATUS } from '../model/builtin'; import rating from '../lib/rating'; import paginate from '../lib/paginate'; diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index a39d834d8..bb7958f5c 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -27,7 +27,7 @@ import { import hash from '../lib/hash.hydro'; import * as misc from '../lib/misc'; import user from '../model/user'; -import * as domain from '../model/domain'; +import domain from '../model/domain'; import * as system from '../model/system'; import blacklist from '../model/blacklist'; import token from '../model/token'; diff --git a/packages/hydrooj/src/upgrade.ts b/packages/hydrooj/src/upgrade.ts index 55c31c62d..0e45baa3f 100644 --- a/packages/hydrooj/src/upgrade.ts +++ b/packages/hydrooj/src/upgrade.ts @@ -12,7 +12,7 @@ import gridfs from './service/gridfs'; import storage from './service/storage'; import db from './service/db'; import * as problem from './model/problem'; -import * as domain from './model/domain'; +import domain from './model/domain'; import * as document from './model/document'; import * as system from './model/system'; import difficultyAlgorithm from './lib/difficulty'; diff --git a/packages/syzoj-ng-api/handlers/problem.ts b/packages/syzoj-ng-api/handlers/problem.ts index 5bad2c2ef..566739f04 100644 --- a/packages/syzoj-ng-api/handlers/problem.ts +++ b/packages/syzoj-ng-api/handlers/problem.ts @@ -1,7 +1,7 @@ import { FilterQuery } from 'mongodb'; import { Pdoc } from 'hydrooj/dist/interface'; import * as problem from 'hydrooj/dist/model/problem'; -import * as record from 'hydrooj/dist/model/record'; +import record from 'hydrooj/dist/model/record'; import user from 'hydrooj/dist/model/user'; import { PERM, PRIV } from 'hydrooj/dist/model/builtin'; import { diff --git a/packages/syzoj-ng-api/package.json b/packages/syzoj-ng-api/package.json index e3b0c3eb8..8dd9f936b 100644 --- a/packages/syzoj-ng-api/package.json +++ b/packages/syzoj-ng-api/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/syzoj-ng-api", - "version": "0.0.1", + "version": "0.0.2", "description": "A syzoj-ng API interface", "main": "package.json", "repository": "https://github.com/hydro-dev/Hydro.git",