diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aad54be69..233cfd787 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,10 @@ jobs: with: name: development.js path: .build/development.js + - uses: actions/upload-artifact@v1 + with: + name: full.js + path: .build/full.js - uses: actions/upload-artifact@v1 with: name: module diff --git a/build/index.js b/build/index.js index 91d99ee5d..4606d633c 100644 --- a/build/index.js +++ b/build/index.js @@ -25,18 +25,15 @@ async function build(type) { ignoreFailure(fs.mkdirSync, root('.build')); ignoreFailure(fs.mkdirSync, root('.build/module')); const langs = fs.readdirSync(root('locales')); - const lang = {}; + const locale = {}; for (const i of langs) { const content = fs.readFileSync(root(`locales/${i}`)).toString(); - lang[i.split('.')[0]] = yaml.safeLoad(content); + locale[i.split('.')[0]] = yaml.safeLoad(content); } - const exclude = [ - 'bsod.html', - ]; const builtin = { id: 'builtin', - locale: lang, - template: template('templates', exclude), + locale, + template: template('templates'), file: {}, }; const files = getFiles('.uibuild'); @@ -72,10 +69,9 @@ async function build(type) { console.error(`Module pack failed: ${m}`); } } - await fsp.writeFile(root('.build/module.json'), JSON.stringify(j)); - console.log('Build::Full'); - await require('./webpack')(type, '-full'); - await fsp.unlink(root('.build/module.json')); + const f = fs.readFileSync(root('.build/development.js')).toString(); + const file = `global._hydroModule=${JSON.stringify(j)};${f}`; + fs.writeFileSync(root('.build/full.js'), file); } module.exports = build; diff --git a/build/template.js b/build/template.js index 30324f2a2..14515a134 100644 --- a/build/template.js +++ b/build/template.js @@ -15,7 +15,7 @@ function getFiles(folder) { return files; } -const build = (dirOrObject, exclude = []) => { +const build = (dirOrObject) => { let templates = {}; if (typeof dirOrObject === 'string') { const files = getFiles(dirOrObject); @@ -25,7 +25,7 @@ const build = (dirOrObject, exclude = []) => { } } else templates = dirOrObject; for (const i in templates) { - if (!exclude.includes(i) && i.endsWith('.html')) { + if (i.endsWith('.html') && !templates[i].includes('No Compression')) { templates[i] = templates[i] .trim() .replace(/ *\n */gmi, ' ') diff --git a/build/webpack.js b/build/webpack.js index e07e315b3..684f1b1c7 100644 --- a/build/webpack.js +++ b/build/webpack.js @@ -4,7 +4,7 @@ const webpack = require('webpack'); const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); const { root } = require('./utils'); -const build = async (type, tag = '') => { +const build = async (type) => { function hackNodeModuleFormidable() { const tasks = ['incoming_form', 'file', 'json_parser', 'querystring_parser']; for (const task of tasks) { @@ -25,7 +25,7 @@ const build = async (type, tag = '') => { development: root('hydro/development.js'), }, output: { - filename: `[name]${tag}.js`, + filename: '[name].js', path: root('.build'), }, target: 'node', diff --git a/extensions/homework/hydro.json b/extensions/homework/hydro.json deleted file mode 100644 index 3a5c14838..000000000 --- a/extensions/homework/hydro.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "homework", - "version": "1.0.0", - "description": "Homework support" -} \ No newline at end of file diff --git a/hydro/handler/home.js b/hydro/handler/home.js index aaeb548eb..0abfc1e95 100644 --- a/hydro/handler/home.js +++ b/hydro/handler/home.js @@ -10,6 +10,7 @@ const misc = require('../lib/misc'); const md5 = require('../lib/md5'); const contest = require('../model/contest'); const message = require('../model/message'); +const document = require('../model/document'); const system = require('../model/system'); const user = require('../model/user'); const setting = require('../model/setting'); @@ -18,12 +19,26 @@ const token = require('../model/token'); const training = require('../model/training'); const { PERM_VIEW_TRAINING, PERM_VIEW_CONTEST, PERM_VIEW_DISCUSSION, - PERM_LOGGEDIN, + PERM_LOGGEDIN, PERM_VIEW_HOMEWORK, } = require('../permission'); const { geoip, useragent } = global.Hydro.lib; class HomeHandler extends Handler { + async homework(domainId) { + if (this.user.hasPerm(PERM_VIEW_HOMEWORK)) { + const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK) + .limit(await system.get('HOMEWORK_ON_MAIN')) + .toArray(); + const tsdict = await contest.getListStatus( + domainId, this.user._id, + tdocs.map((tdoc) => tdoc.docId), document.TYPE_HOMEWORK, + ); + return [tdocs, tsdict]; + } + return [[], {}]; + } + async contest(domainId) { if (this.user.hasPerm(PERM_VIEW_CONTEST)) { const tdocs = await contest.getMulti(domainId) @@ -63,13 +78,17 @@ class HomeHandler extends Handler { } async get({ domainId }) { - const [[tdocs, tsdict], [trdocs, trsdict], [ddocs, vndict]] = await Promise.all([ - this.contest(domainId), this.training(domainId), this.discussion(domainId), + const [ + [htdocs, htsdict], [tdocs, tsdict], + [trdocs, trsdict], [ddocs, vndict], + ] = await Promise.all([ + this.homework(domainId), this.contest(domainId), + this.training(domainId), this.discussion(domainId), ]); const udict = await user.getList(domainId, ddocs.map((ddoc) => ddoc.owner)); this.response.template = 'main.html'; this.response.body = { - tdocs, tsdict, trdocs, trsdict, ddocs, vndict, udict, + htdocs, htsdict, tdocs, tsdict, trdocs, trsdict, ddocs, vndict, udict, }; } } @@ -190,21 +209,37 @@ class HomeMessagesHandler extends Handler { async get() { // TODO(iceboy): projection, pagination. - const mdocs = await message.getMulti(this.user._id).sort('_id', -1).limit(50).toArray(); + const messages = await message.getByUser(this.user._id); const udict = await user.getList([ - ...mdocs.map((mdoc) => mdoc.from), - ...mdocs.map((mdoc) => mdoc.to), + ...messages.map((mdoc) => mdoc.from), + ...messages.map((mdoc) => mdoc.to), ]); // TODO(twd2): improve here: - for (const mdoc of mdocs) { - this.udoc(udict, mdoc.from); - this.udoc(udict, mdoc.to); + const parsed = {}; + for (const m of messages) { + if (m.from === this.user._id) { + if (!parsed[m.to]) { + parsed[m.to] = { + udoc: { ...udict[m.to], gravatar: misc.gravatar(udict[m.to].gravatar) }, + messages: [], + }; + } + parsed[m.to].messages.push(m); + } else { + if (!parsed[m.from]) { + parsed[m.from] = { + udoc: { ...udict[m.from], gravatar: misc.gravatar(udict[m.from].gravatar) }, + messages: [], + }; + } + parsed[m.from].messages.push(m); + } } const path = [ ['Hydro', '/'], ['home_messages', null], ]; - this.response.body = { messages: mdocs, udict, path }; + this.response.body = { messages, udict, path }; this.response.template = 'home_messages.html'; } diff --git a/hydro/handler/homework.js b/hydro/handler/homework.js index 19307d534..1723a4ab4 100644 --- a/hydro/handler/homework.js +++ b/hydro/handler/homework.js @@ -27,8 +27,7 @@ class HomeworkMainHandler extends HomeworkHandler { const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK).toArray(); const calendar = []; for (const tdoc of tdocs) { - console.log(tdoc); - const cal = { ...tdoc }; + const cal = { ...tdoc, url: `/homework/${tdoc.docId}` }; if (contest.isHomeworkExtended(tdoc) || contest.isDone(tdoc)) { cal.endAt = tdoc.endAt; cal.penaltySince = tdoc.penaltySince; @@ -37,7 +36,11 @@ class HomeworkMainHandler extends HomeworkHandler { } calendar.push(cal); } - this.response.body = { tdocs, calendar }; + const path = [ + ['Hydro', '/'], + ['homework_main', null], + ]; + this.response.body = { tdocs, calendar, path }; this.response.template = 'homework_main.html'; } } @@ -76,6 +79,7 @@ class HomeworkDetailHandler extends HomeworkHandler { uids.push(tdoc.owner); const udict = await user.getList(domainId, uids); const path = [ + ['Hydro', '/'], ['homework_main', '/homework'], [tdoc.title, null, true], ]; @@ -113,6 +117,7 @@ class HomeworkDetailProblemHandler extends HomeworkHandler { } if (!this.tdoc.pids.includes(pid)) throw new ProblemNotFoundError(domainId, pid, tid); const path = [ + ['Hydro', '/'], ['homework_main', '/homework'], [this.tdoc.title, `/homework/${tid}`, true], [this.pdoc.title, null, true], @@ -136,6 +141,7 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler { .sort('_id', -1).limit(10).toArray() : []; const path = [ + ['Hydro', '/'], ['homework_main', '/homework'], [this.tdoc.title, `/homework/${tid}`, true], [this.pdoc.title, `/homework/${tid}/p/${pid}`, true], @@ -162,6 +168,7 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler { rid, pid, false, 0, document.TYPE_HOMEWORK); this.response.body = { tid, rid }; if (this.canShowRecord(this.tdoc)) this.response.redirect = `/record/${rid}`; + else this.response.redirect = `/homework/${tid}`; } } @@ -170,6 +177,8 @@ class HomeworkCreateHandler extends HomeworkHandler { const beginAt = new Date().delta({ day: 1 }); const penaltySince = beginAt.delta({ day: 7 }); const path = [ + ['Hydro', '/'], + ['homework_main', '/homework'], ['homework_create', null], ]; this.response.template = 'homework_edit.html'; @@ -221,6 +230,7 @@ class HomeworkEditHandler extends HomeworkHandler { (tdoc.endAt.getTime() - tdoc.penaltySince.getTime()) / 36000 / 24, ) / 100; const path = [ + ['Hydro', '/'], ['homework_main', '/homework'], [tdoc.title, `/homework/${tid}`, true], ['homework_edit', null], @@ -353,6 +363,7 @@ async function apply() { { type: 'total_time', value: _('Total Time (Seconds)') }, ); } + columns.push({ type: 'total_time_str', value: _('Total Time') }); for (const index in tdoc.pids) { const pid = tdoc.pids[index]; if (isExport) { @@ -383,14 +394,13 @@ async function apply() { } } const rows = [columns]; - for (const rank in rankedTsdocs) { - const tsdoc = rankedTsdocs[rank]; + for (const [rank, tsdoc] of rankedTsdocs) { const tsddict = {}; for (const item of tsdoc.journal || []) { tsddict[item.pid] = item; } const row = [ - { type: 'string', value: rank + 1 }, + { type: 'string', value: rank }, { type: 'user', value: udict[tsdoc.uid].uname, @@ -411,7 +421,7 @@ async function apply() { const colScore = (tsddict[pid] || {}).penaltyScore || '-'; const colOriginalScore = (tsddict[pid] || {}).score || '-'; const colTime = (tsddict[pid] || {}).time || '-'; - const colTimeStr = colTime !== '-' ? misc.formatSeconds(colTime) : '-'; + const colTimeStr = colTime !== '-' ? misc.formatSeconds(colTime || 0) : '-'; if (isExport) { row.push( { type: 'string', value: colScore }, @@ -431,7 +441,7 @@ async function apply() { } return rows; }, - rank: (tdocs) => ranked(tdocs, (a, b) => a.score === b.score), + rank: (tsdocs) => ranked(tsdocs, (a, b) => a.score === b.score), }; Route('/homework', HomeworkMainHandler, PERM_VIEW_HOMEWORK); Route('/homework/create', HomeworkCreateHandler, PERM_CREATE_HOMEWORK); diff --git a/hydro/handler/record.js b/hydro/handler/record.js index 02a21e9d4..60142483c 100644 --- a/hydro/handler/record.js +++ b/hydro/handler/record.js @@ -1,7 +1,5 @@ -const { - PERM_READ_RECORD_CODE, PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD, - PERM_REJUDGE, PERM_VIEW_PROBLEM_HIDDEN, -} = require('../permission'); +const { PERM_READ_RECORD_CODE, PERM_REJUDGE, PERM_VIEW_PROBLEM_HIDDEN } = require('../permission'); +const { PermissionError } = require('../error'); const problem = require('../model/problem'); const record = require('../model/record'); const contest = require('../model/contest'); @@ -20,7 +18,12 @@ class RecordListHandler extends RecordHandler { }) { this.response.template = 'record_main.html'; const q = { tid }; - if (uname_or_uid) q.$or = [{ unameLower: uname_or_uid.toLowerCase() }, { _id: parseInt(uname_or_uid) }]; + if (uname_or_uid) { + q.$or = [ + { unameLower: uname_or_uid.toLowerCase() }, + { _id: parseInt(uname_or_uid) }, + ]; + } const rdocs = await record.getMany(domainId, q, { _id: -1 }, page, await system.get('RECORD_PER_PAGE')); const [udict, pdict] = await Promise.all([ user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)), @@ -73,16 +76,30 @@ class RecordRejudgeHandler extends Handler { } } -class RecordConnectionHandler extends ConnectionHandler { - async prepare({ domainId }) { +const RecordConnectionHandler = contest.ContestHandlerMixin(ConnectionHandler); + +class RecordMainConnectionHandler extends RecordConnectionHandler { + async prepare({ domainId, tid }) { this.domainId = domainId; + if (tid) { + const tdoc = await contest.get(domainId, tid, -1); + if (this.canShowRecord(tdoc)) this.tid = tid; + else { + this.close(); + return; + } + } bus.subscribe(['record_change'], this.onRecordChange); } async message(msg) { - for (const rid of msg.rids) { - const rdoc = await record.get(this.domainId, rid); // eslint-disable-line no-await-in-loop - await this.onRecordChange({ value: rdoc }); // eslint-disable-line no-await-in-loop + const rdocs = await record.getMany( + this.domainId, + { _id: { $in: msg.rids } }, + { _id: -1 }, 1, 100, + ); + for (const rdoc of rdocs) { + this.onRecordChange({ value: rdoc }); } } @@ -107,7 +124,7 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti async prepare({ domainId, rid }) { const rdoc = await record.get(domainId, rid); if (rdoc.tid) { - const tdoc = await contest.get(domainId, rdoc.tid); + const tdoc = await contest.get(domainId, rdoc.tid, -1); if (!this.canShowRecord(tdoc)) { this.close(); return; @@ -133,21 +150,21 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti } async function apply() { - Route('/r', module.exports.RecordListHandler); - Route('/r/:rid', module.exports.RecordDetailHandler); - Route('/r/:rid/rejudge', module.exports.RecordRejudgeHandler); - Route('/record', module.exports.RecordListHandler); - Route('/record/:rid', module.exports.RecordDetailHandler); - Route('/record/:rid/rejudge', module.exports.RecordRejudgeHandler); - Connection('/record-conn', module.exports.RecordConnectionHandler); - Connection('/record-detail-conn', module.exports.RecordDetailConnectionHandler); + Route('/r', RecordListHandler); + Route('/r/:rid', RecordDetailHandler); + Route('/r/:rid/rejudge', RecordRejudgeHandler); + Route('/record', RecordListHandler); + Route('/record/:rid', RecordDetailHandler); + Route('/record/:rid/rejudge', RecordRejudgeHandler); + Connection('/record-conn', RecordMainConnectionHandler); + Connection('/record-detail-conn', RecordDetailConnectionHandler); } global.Hydro.handler.record = module.exports = { RecordListHandler, RecordDetailHandler, RecordRejudgeHandler, - RecordConnectionHandler, + RecordMainConnectionHandler, RecordDetailConnectionHandler, apply, }; diff --git a/hydro/lib/misc.js b/hydro/lib/misc.js index 9792f665d..8fedd9a92 100644 --- a/hydro/lib/misc.js +++ b/hydro/lib/misc.js @@ -49,9 +49,18 @@ function size(s, base = 1) { return '{0} {1}'.format(Math.round(s * unit), unitNames[unitNames.length - 1]); } -function formatSeconds(seconds) { +function _digit2(number) { + if (number < 10) return `0${number}`; + return number.toString(); +} + +function formatSeconds(seconds = 0) { seconds = parseInt(seconds); - return '{:02}:{:02}:{:02}'.format(Math.floor(seconds / 3600), Math.floor((seconds % 3600) / 60), seconds % 60); + return '{0}:{1}:{2}'.format( + _digit2(Math.floor(seconds / 3600)), + _digit2(Math.floor((seconds % 3600) / 60)), + _digit2(seconds % 60), + ); } global.Hydro.lib.misc = module.exports = { diff --git a/hydro/loader.js b/hydro/loader.js index 9656c3804..1591550e4 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -20,20 +20,15 @@ let pending = []; const active = []; const fail = []; -try { - // Let webpack pack builtin module together. - // eslint-disable-next-line import/no-unresolved - const f = require('../.build/module.json'); - for (const filename in f) { +if (global._hydroModule) { + for (const filename in global._hydroModule) { const m = { - ...yaml.safeLoad(zlib.gunzipSync(Buffer.from(f[filename], 'base64'))), + ...yaml.safeLoad(zlib.gunzipSync(Buffer.from(global._hydroModule[filename], 'base64'))), filename, isBuiltin: true, }; pending.push(m); } -} catch (e) { - // Ignore Missing } async function preload() { diff --git a/hydro/model/contest.js b/hydro/model/contest.js index 58e6a53ca..a83526d20 100644 --- a/hydro/model/contest.js +++ b/hydro/model/contest.js @@ -245,7 +245,11 @@ async function edit(domainId, tid, $set, type = document.TYPE_CONTEST) { * @returns {Tdoc} */ async function get(domainId, tid, type = document.TYPE_CONTEST) { - const tdoc = await document.get(domainId, type, tid); + let tdoc; + if (type === -1) { + tdoc = await document.get(domainId, document.TYPE_CONTEST, tid); + if (!tdoc) tdoc = await document.get(domainId, document.TYPE_HOMEWORK, tid); + } else tdoc = await document.get(domainId, type, tid); if (!tdoc) throw new ContestNotFoundError(tid); return tdoc; } @@ -265,10 +269,11 @@ function getStatus(domainId, tid, uid, type = document.TYPE_CONTEST) { return document.getStatus(domainId, type, tid, uid); } -async function getListStatus(domainId, uid, tids) { +async function getListStatus(domainId, uid, tids, type = document.TYPE_CONTEST) { const r = {}; + console.log(tids); // eslint-disable-next-line no-await-in-loop - for (const tid of tids) r[tid] = await getStatus(domainId, tid, uid); + for (const tid of tids) r[tid] = await getStatus(domainId, tid, uid, type); return r; } @@ -327,7 +332,7 @@ const ContestHandlerMixin = (c) => class extends c { async getScoreboard(domainId, tid, isExport = false, docType = document.TYPE_CONTEST) { const tdoc = await get(domainId, tid, docType); if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid); - const tsdocs = await getMultiStatus(domainId, tid, docType) + const tsdocs = await getMultiStatus(domainId, { docId: tid }, docType) .sort(RULES[tdoc.rule].statusSort).toArray(); const uids = []; for (const tsdoc of tsdocs) uids.push(tsdoc.uid); diff --git a/hydro/model/message.js b/hydro/model/message.js index 21c65a38e..ce490ba80 100644 --- a/hydro/model/message.js +++ b/hydro/model/message.js @@ -5,29 +5,8 @@ const db = require('../service/db.js'); const coll = db.collection('message'); async function send(from, to, content) { - await coll.updateOne({ - from: Math.min(from, to), - to: Math.max(from, to), - }, { - $push: { - reply: { - _id: new ObjectID(), - from, - content, - unread: true, - at: new Date(), - }, - }, - $setOnInsert: { - from: Math.min(from, to), - to: Math.max(from, to), - fromUnread: from > to, - toUnread: from < to, - }, - }, { upsert: true }); - return await coll.findOne({ // eslint-disable-line no-return-await - from: Math.min(from, to), - to: Math.max(from, to), + await coll.insertOne({ + from, to, content, unread: true, }); } @@ -37,6 +16,10 @@ async function get(_id) { return doc; } +function getByUser(uid) { + return coll.find({ $or: [{ from: uid }, { to: uid }] }).toArray(); +} + function getMany(query, sort, page, limit) { return coll.find(query).sort(sort) .skip((page - 1) * limit).limit(limit) @@ -65,6 +48,7 @@ function ensureIndexes() { global.Hydro.model.message = module.exports = { count, get, + getByUser, del, getMany, getMulti, diff --git a/hydro/model/task.js b/hydro/model/task.js index 95ccc40bb..65c902151 100644 --- a/hydro/model/task.js +++ b/hydro/model/task.js @@ -3,7 +3,6 @@ const db = require('../service/db'); const coll = db.collection('task'); async function add(task) { - console.log(task); const res = await coll.insertOne(task); return res.insertedId; } diff --git a/hydro/script/install.js b/hydro/script/install.js index 18bd4bd2d..3dc4d1daa 100644 --- a/hydro/script/install.js +++ b/hydro/script/install.js @@ -28,6 +28,7 @@ async function run(username, password) { TRAINING_PER_PAGE: 10, DISCUSSION_PER_PAGE: 50, REPLY_PER_PAGE: 50, + HOMEWORK_ON_MAIN: 5, CONTESTS_ON_MAIN: 5, TRAININGS_ON_MAIN: 5, DISCUSSIONS_ON_MAIN: 20, diff --git a/extensions/homework/handler.js b/module/migrate-vijos/lib.js similarity index 100% rename from extensions/homework/handler.js rename to module/migrate-vijos/lib.js diff --git a/module/migrate-vijos/script.js b/module/migrate-vijos/script.js index 32a61976d..1c7681feb 100644 --- a/module/migrate-vijos/script.js +++ b/module/migrate-vijos/script.js @@ -43,7 +43,7 @@ const tasks = { doc_type: 'docType', num_submit: 'nSubmit', num_accept: 'nAccept', - difficulty_admin: 'difficulty', + difficulty: 'difficulty', pname: 'pid', title: 'title', content: 'content', @@ -64,6 +64,17 @@ const tasks = { update_at: 'updateAt', begin_at: 'beginAt', end_at: 'endAt', + penalty_since: 'penaltySince', + penalty_rules: { + field: 'penaltyRules', + processer: (rule) => { + const n = {}; + for (const key in rule) { + n[key / 3600] = rule; + } + return n; + }, + }, rule: { field: 'rule', processer: (rule) => { @@ -176,7 +187,7 @@ const tasks = { score: c.score, time: c.time_ms || c.time, memory: c.memory_kb || c.memory, - judge_text: (c.judge_text || '') + (c.message || ''), + judgeText: (c.judge_text || '') + (c.message || ''), }); } return { @@ -194,7 +205,7 @@ const tasks = { judger: doc.judge_uid, judgeAt: doc.judge_at, judgeTexts: doc.judge_texts, - compileTexts: doc.compile_texts, + compilerTexts: doc.compiler_texts, rejudged: doc.rejudged, testCases, }; @@ -242,25 +253,42 @@ async function task(name, src, report) { const count = await cursor[name](src).count(); await report({ progress: 1, message: `${name}: ${count}` }); const total = Math.floor(count / 50); + let lastProgress = -1; for (let i = 0; i <= total; i++) { const docs = await cursor[name](src).skip(i * 50).limit(50).toArray(); const res = []; for (const doc of docs) { const d = await tasks[name](doc); if (d) { - if (d.docId && d.docType) { - res.push(dst.collection(name).updateOne({ - docId: d.docId, docType: d.docType, - }, { $set: d }, { upsert: true })); + const docWithoutId = {}; + const docWithoutDid = {}; + for (const key in d) { + if (key !== '_id') { + if (key !== 'domainId' && key !== 'docId' && key !== 'docType' && key !== 'uid') { + docWithoutDid[key] = d[key]; + } + docWithoutId[key] = d[key]; + } + } + if (d.domainId && d.docId && d.docType) { + const query = { domainId: d.domainId, docId: d.docId, docType: d.docType }; + if (d.uid) query.uid = d.uid; + res.push(dst.collection(name).updateOne( + query, { $set: docWithoutDid }, { upsert: true }, + )); } else if (d._id) { res.push(dst.collection(name).updateOne({ _id: d._id, - }, { $set: d }, { upsert: true })); + }, { $set: docWithoutId }, { upsert: true })); } else res.push(dst.collection(name).insertOne(d)); } } await Promise.all(res); - await report({ progress: Math.round(100 * ((i + 1) / (total + 1))) }); + const progress = Math.round(100 * ((i + 1) / (total + 1))); + if (progress > lastProgress) { + await report({ progress }); + lastProgress = progress; + } } await fixPid(report); } @@ -286,7 +314,6 @@ async function migrateVijos({ { upsert: true }, ); await report({ progress: 1, message: 'Collection:system done.' }); - await dst.collection('user').deleteMany({ _id: { $nin: [0, 1] } }); if (!await dst.collection('system').findOne({ _id: 'migrateVijosFs' })) { const f = ['fs.files', 'fs.chunks']; for (const i of f) { @@ -295,8 +322,10 @@ async function migrateVijos({ } await dst.collection('system').insertOne({ _id: 'migrateVijosFs', value: true }); } + await dst.collection('user').deleteMany({ _id: { $nin: [0, 1] } }); const d = ['user', 'document', 'document.status', 'record', 'file']; for (const i of d) await task(i, src, report); + return true; } global.Hydro.script.migrateVijos = module.exports = { run: migrateVijos }; diff --git a/templates/bsod.html b/templates/bsod.html index 3d4654118..e2331eeab 100644 --- a/templates/bsod.html +++ b/templates/bsod.html @@ -1,3 +1,4 @@ +{# No Compression #} diff --git a/templates/main.html b/templates/main.html index bc48f6136..ae0e92a28 100644 --- a/templates/main.html +++ b/templates/main.html @@ -56,6 +56,50 @@ {% endif %} + {% if htdocs.length %} +
+
+

{{ _('Homework') }}

+
+
    + {% for tdoc in htdocs %} +
  1. +
    +
    +
    +
    {{ datetimeSpan(tdoc['penaltySince'], false, '%d')|safe }}
    +
    {{ datetimeSpan(tdoc['penaltySince'], false, '%Y-%m')|safe }}
    +
    +
    +
    +

    {{ tdoc['title'] }}

    +
      +
    • + {{ _('Status') }}: {{ _(model.contest.statusText(tdoc)) }} +
    • +
    • + {{ _('Open Since') }}: {{ datetimeSpan(tdoc['beginAt'])|safe }} +
    • +
    • + {% if model.contest.isHomeworkExtended(tdoc) or model.contest.isDone(tdoc) %} + {{ _('Hard Deadline') }}: {{ datetimeSpan(tdoc['endAt'])|safe }} + {% else %} + {{ _('Deadline') }}: {{ datetimeSpan(tdoc['penaltySince'])|safe }} + {% endif %} +
    • + {% if htsdict[tdoc['docId']]['attend'] == 1 %} +
    • + {{ _('Claimed') }} +
    • + {% endif %} +
    +
    +
    +
  2. + {% endfor %} +
+
+ {% endif %} {% if trdocs.length %}
diff --git a/templates/partials/login_dialog.html b/templates/partials/login_dialog.html index efdbeb679..7429c50e9 100644 --- a/templates/partials/login_dialog.html +++ b/templates/partials/login_dialog.html @@ -1,3 +1,4 @@ +{# No Compression #}