移除可以与同一用户创建多个会话的特性

This commit is contained in:
masnn committed 2020-05-15 13:10:41 +08:00
1 parent d04628148f
commit 3c5fdb9032
16 files changed
+111 -109

No files matched your search

+15 -19
View File
@@ -1,6 +1,6 @@
const { const {
VerifyPasswordError, UserAlreadyExistError, InvalidTokenError, VerifyPasswordError, UserAlreadyExistError, InvalidTokenError,
NotFoundError, MessageNotFoundError, NotFoundError,
} = require('../error'); } = require('../error');
const bus = require('../service/bus'); const bus = require('../service/bus');
const { const {
@@ -200,38 +200,34 @@ class HomeMessagesHandler extends Handler {
this.udoc(udict, mdoc.from); this.udoc(udict, mdoc.from);
this.udoc(udict, mdoc.to); this.udoc(udict, mdoc.to);
} }
this.response.body = { messages: mdocs, udict }; const path = [
['Hydro', '/'],
['home_messages', null],
];
this.response.body = { messages: mdocs, udict, path };
this.response.template = 'home_messages.html'; this.response.template = 'home_messages.html';
} }
async postSendMessage({ uid, content }) { async postSend({ uid, content, type = 'full' }) {
const udoc = await user.getById(uid); const udoc = await user.getById(uid);
const mdoc = await message.add(this.user._id, udoc._id, content); let mdoc = await message.send(this.user._id, uid, content);
if (type === 'single') {
mdoc = mdoc.reply[mdoc.reply.length - 1];
}
// TODO(twd2): improve here: // TODO(twd2): improve here:
// projection // projection
mdoc.from_udoc = this.user; mdoc.from_udoc = this.user;
this.udoc(mdoc, 'from');
mdoc.to_udoc = udoc; mdoc.to_udoc = udoc;
this.udoc(mdoc, 'from');
this.udoc(mdoc, 'to'); this.udoc(mdoc, 'to');
if (this.user._id !== uid) { if (this.user._id !== uid) {
await bus.publish(`user_message-${uid}`, { type: 'new', data: mdoc }); await bus.publish(`user_message-${uid}`, { type: 'new', data: mdoc });
} }
this.back({ mdoc }); this.back(type === 'full' ? { mdoc } : { reply: mdoc });
} }
async postReplyMessage({ message_id, content }) { async postDeleteMessage({ messageId }) {
const [mdoc, reply] = await message.addReply(message_id, this.user._id, content); await message.delete(messageId, this.user._id);
if (!mdoc) throw new MessageNotFoundError(message_id);
if (mdoc.from !== mdoc.to) {
const other = mdoc.from === this.user._id ? mdoc.from : mdoc.to;
await bus.publish(`user_message-${other}`, { type: 'reply', data: mdoc });
}
mdoc.reply = [reply];
this.back({ reply });
}
async postDeleteMessage({ message_id }) {
await message.delete(message_id, this.user._id);
this.back(); this.back();
} }
} }
+2 -2
View File
@@ -22,10 +22,10 @@ class ProblemImportHandler extends Handler {
async post({ async post({
url, pid, hidden, remoteType, url, pid, hidden, remoteType,
}) { }) {
if (typeof this[`from_${remoteType}`] !== 'function') { if (typeof global.Hydro.lib[`import.${remoteType}`] !== 'function') {
throw new ValidationError('remoteType'); throw new ValidationError('remoteType');
} }
const [pdoc, testdata] = await this[`from_${remoteType}`](url); const [pdoc, testdata] = await global.Hydro.lib[`import.${remoteType}`](url, this);
if (pid) pdoc.pid = pid; if (pid) pdoc.pid = pid;
if (hidden) pdoc.hidden = true; if (hidden) pdoc.hidden = true;
const _id = await problem.add(pdoc); const _id = await problem.add(pdoc);
+4 -2
View File
@@ -118,8 +118,10 @@ class ProblemSubmitHandler extends ProblemDetailHandler {
const rid = await record.add({ const rid = await record.add({
uid: this.uid, lang, code, pid: this.pdoc._id, uid: this.uid, lang, code, pid: this.pdoc._id,
}); });
await record.judge(rid); await Promise.all([
this.user.nSubmit++; record.judge(rid),
user.inc(this.user._id, 'nSubmit'),
]);
this.response.body = { rid }; this.response.body = { rid };
this.response.redirect = `/r/${rid}`; this.response.redirect = `/r/${rid}`;
} }
+4 -16
View File
@@ -17,22 +17,10 @@ class RecordListHandler extends Handler {
this.response.template = 'record_main.html'; this.response.template = 'record_main.html';
const q = {}; const q = {};
const rdocs = await record.getMany(q, { _id: -1 }, page, await system.get('RECORD_PER_PAGE')); const rdocs = await record.getMany(q, { _id: -1 }, page, await system.get('RECORD_PER_PAGE'));
const pdict = {}; const [udict, pdict] = await Promise.all([
const udict = {}; user.getList(rdocs.map((rdoc) => rdoc.uid)),
const ulist = []; problem.getList(rdocs.map((rdoc) => rdoc.pid)),
const plist = []; ]);
for (const rdoc of rdocs) {
ulist[rdoc.uid] = user.getById(rdoc.uid);
plist[rdoc.pid] = problem.get(rdoc.pid, this.user._id);
}
await Promise.all(ulist);
await Promise.all(plist);
for (const udoc of ulist) {
udict[udoc._id] = udoc;
}
for (const pdoc of plist) {
pdict[pdoc._id] = pdoc;
}
const path = [ const path = [
['Hydro', '/'], ['Hydro', '/'],
['record_main', null], ['record_main', null],
+22 -28
View File
@@ -1,26 +1,39 @@
const { ObjectID } = require('bson');
const { MessageNotFoundError } = require('../error'); const { MessageNotFoundError } = require('../error');
const db = require('../service/db.js'); const db = require('../service/db.js');
const coll = db.collection('message'); const coll = db.collection('message');
async function add(from, to, content) { async function send(from, to, content) {
const res = await coll.insertOne({ await coll.updateOne({
from: Math.min(from, to),
to: Math.max(from, to),
}, {
$push: {
reply: {
_id: new ObjectID(),
from, from,
to, content,
unread: true,
at: new Date(),
},
},
$setOnInsert: {
from: Math.min(from, to),
to: Math.max(from, to),
fromUnread: false, fromUnread: false,
toUnread: true, toUnread: true,
reply: [
{
from, unread: true, content, at: new Date(),
}, },
], }, { upsert: true });
return await coll.findOne({ // eslint-disable-line no-return-await
from: Math.min(from, to),
to: Math.max(from, to),
}); });
return await coll.findOne({ _id: res.insertedId }); // eslint-disable-line no-return-await
} }
async function get(_id) { async function get(_id) {
const doc = await coll.findOne({ _id }); const doc = await coll.findOne({ _id });
if (!doc) throw new MessageNotFoundError(); if (!doc) throw new MessageNotFoundError(_id);
return doc; return doc;
} }
@@ -42,23 +55,6 @@ function getMulti(uid) {
return coll.find({ $or: [{ from: uid }, { to: uid }] }); return coll.find({ $or: [{ from: uid }, { to: uid }] });
} }
async function addReply(_id, from, content) {
const reply = {
from,
content,
unread: true,
at: new Date(),
};
await coll.updateOne({ _id }, { $push: { reply } });
return await coll.findOne({ _id }); // eslint-disable-line no-return-await
}
async function send(from, to, content) {
const sdoc = await coll.findOne({ $or: [{ from, to }, { from: to, to: from }] });
await addReply(sdoc._id, from, content);
return sdoc._id;
}
function index() { function index() {
return Promise.all([ return Promise.all([
coll.createIndex({ to: 1, _id: -1 }), coll.createIndex({ to: 1, _id: -1 }),
@@ -68,12 +64,10 @@ function index() {
global.Hydro.model.message = module.exports = { global.Hydro.model.message = module.exports = {
count, count,
add,
get, get,
del, del,
getMany, getMany,
getMulti, getMulti,
addReply,
send, send,
index, index,
}; };
+1 -1
View File
@@ -63,9 +63,9 @@ module.exports = {
const now = new Date(); const now = new Date();
const res = await coll.findOneAndUpdate({ _id: tokenId, tokenType }, { const res = await coll.findOneAndUpdate({ _id: tokenId, tokenType }, {
$set: { $set: {
...data,
updateAt: now, updateAt: now,
expireAt: new Date(now.getTime() + expireSeconds * 1000), expireAt: new Date(now.getTime() + expireSeconds * 1000),
...data,
tokenType, tokenType,
}, },
}); });
+4 -3
View File
@@ -96,9 +96,10 @@ async function changePassword(uid, currentPassword, newPassword) {
}); });
} }
async function inc(_id, field, n) { async function inc(_id, field, n = 1) {
await coll.findOneAndUpdate({ _id }, { $inc: { [field]: n } }); const udoc = await coll.findOne({ _id });
const udoc = await getById(_id); udoc[field] = udoc[field] + n || n;
await coll.updateOne({ _id }, { $set: { [field]: udoc[field] } });
return udoc; return udoc;
} }
+5 -5
View File
@@ -151,7 +151,10 @@ class Handler {
if (bdoc) throw new BlacklistedError(this.request.ip); if (bdoc) throw new BlacklistedError(this.request.ip);
this.user = await user.getById(this.session.uid); this.user = await user.getById(this.session.uid);
if (!this.user) throw new UserNotFoundError(this.session.uid); if (!this.user) throw new UserNotFoundError(this.session.uid);
[this.csrfToken] = await token.add(token.TYPE_CSRF_TOKEN, 600, { path: this.request.path }); [this.csrfToken] = await token.add(token.TYPE_CSRF_TOKEN, 600, {
path: this.request.path,
uid: this.session.uid,
});
this.preferJson = (this.request.headers.accept || '').includes('application/json'); this.preferJson = (this.request.headers.accept || '').includes('application/json');
} }
@@ -321,7 +324,6 @@ class ConnectionHandler {
_: (str) => (str ? str.toString().translate(this.user.language) : ''), _: (str) => (str ? str.toString().translate(this.user.language) : ''),
user: this.user, user: this.user,
})); }));
console.timeEnd(name);
return res; return res;
} }
@@ -335,9 +337,7 @@ class ConnectionHandler {
async ___prepare() { async ___prepare() {
try { try {
this.session = { this.session = await token.get(this.request.params.token, token.TYPE_CSRF_TOKEN);
uid: await token.get(this.request.params.token, token.TYPE_CSRF_TOKEN),
};
await token.delete(this.request.params.token, token.TYPE_CSRF_TOKEN); await token.delete(this.request.params.token, token.TYPE_CSRF_TOKEN);
} catch (e) { } catch (e) {
this.session = { uid: 1 }; this.session = { uid: 1 };
+1 -1
View File
@@ -22,7 +22,7 @@ class MigrateVijosHandler extends Handler {
}) { }) {
migrate({ migrate({
host, port, name, username, password, host, port, name, username, password,
}, (data) => message.send(0, 0, data)); }, (data) => message.send(1, 1, data));
this.response.redirect = '/manage/log'; this.response.redirect = '/manage/log';
} }
} }
@@ -4,7 +4,7 @@ const { download } = global.Hydro.lib;
const { axios } = global.Hydro.lib; const { axios } = global.Hydro.lib;
const { ValidationError, RemoteOnlineJudgeError } = global.Hydro.error; const { ValidationError, RemoteOnlineJudgeError } = global.Hydro.error;
async function syzoj(url) { async function syzoj(url, handler) {
const RE_SYZOJ = /https?:\/\/([a-zA-Z0-9.]+)\/problem\/([0-9]+)\/?/i; const RE_SYZOJ = /https?:\/\/([a-zA-Z0-9.]+)\/problem\/([0-9]+)\/?/i;
assert(url.match(RE_SYZOJ), new ValidationError('url')); assert(url.match(RE_SYZOJ), new ValidationError('url'));
if (!url.endsWith('/')) url += '/'; if (!url.endsWith('/')) url += '/';
@@ -13,40 +13,59 @@ async function syzoj(url) {
assert(res.status === 200, new RemoteOnlineJudgeError('Cannot connect to target server')); assert(res.status === 200, new RemoteOnlineJudgeError('Cannot connect to target server'));
assert(res.data.success, new RemoteOnlineJudgeError((res.data.error || {}).message)); assert(res.data.success, new RemoteOnlineJudgeError((res.data.error || {}).message));
const p = res.data.obj; const p = res.data.obj;
const content = [ const content = [];
this.translate('problem.import.problem_description'), if (p.description) {
content.push(
handler.translate('problem.import.problem_description'),
p.description, p.description,
this.translate('problem.import.input_format'), );
}
if (p.input_format) {
content.push(
handler.translate('problem.import.input_format'),
p.input_format, p.input_format,
this.translate('problem.import.output_format'), );
}
if (p.output_format) {
content.push(
handler.translate('problem.import.output_format'),
p.output_format, p.output_format,
this.translate('problem.import.hint'), );
}
if (p.hint) {
content.push(
handler.translate('problem.import.hint'),
p.hint, p.hint,
this.translate('problem.import.limit_and_hint'), );
}
if (p.limit_and_hint) {
content.push(
handler.translate('problem.import.limit_and_hint'),
p.limit_and_hint, p.limit_and_hint,
]; );
}
if (p.have_additional_file) { if (p.have_additional_file) {
content.push( content.push(
this.translate('problem.import.additional_file'), handler.translate('problem.import.additional_file'),
`${url}download/additional_file`, `${url}download/additional_file`,
); );
} }
const pdoc = { const pdoc = {
title: p.title, title: p.title,
content: content.join(' \n'), content: content.join(' \n'),
owner: this.user._id, owner: handler.user._id,
from: url, from: url,
pid: `${host}_${pid}`, pid: `${host}_${pid}`,
tags: p.tags,
config: { config: {
time: p.time_limit, time: p.time_limit,
memory: p.memory_limit * 1024, memory: p.memory_limit * 1024,
filename: p.file_io_input_name, filename: p.file_io_input_name,
type: p.type, type: p.type,
tags: p.tags,
}, },
}; };
const r = await download(`${url}testdata/download`); const r = await download(`${url}testdata/download`);
return [pdoc, r]; return [pdoc, r];
} }
global.Hydro.handler.import.ProblemImportHandler.from_syzoj = syzoj; global.Hydro.lib['import.syzoj'] = syzoj;
@@ -1 +1,8 @@
Import problem from syzoj.: 从运行 syzoj 系统的 OJ 导入题目。 Import problem from syzoj.: 从运行 syzoj 系统的 OJ 导入题目。
problem.import.problem_description: '## 题目描述'
problem.import.input_format: '## 输入格式'
problem.import.output_format: '## 输出格式'
problem.import.hint: '## 提示'
problem.import.limit_and_hint: '## 限制'
problem.import.additional_file: '## 附加文件'
import: 导入
@@ -56,7 +56,7 @@ export default class MessagePadDialogueContentContainer extends React.PureCompon
return _.map(this.props.item.reply, (reply, idx) => ( return _.map(this.props.item.reply, (reply, idx) => (
<Message <Message
key={idx} key={idx}
isSelf={reply.from === UserContext.uid} isSelf={reply.from === UserContext._id}
faceUrl={ faceUrl={
reply.from === this.props.item.from reply.from === this.props.item.from
? this.props.item.from_udoc.gravatar_url ? this.props.item.from_udoc.gravatar_url
@@ -39,7 +39,7 @@ export default class MessagePadDialogueListContainer extends React.PureComponent
<ListItem <ListItem
key={dialogue._id} key={dialogue._id}
userName={ userName={
dialogue.from === UserContext.uid dialogue.from === UserContext._id
? dialogue.to_udoc.uname ? dialogue.to_udoc.uname
: dialogue.from_udoc.uname : dialogue.from_udoc.uname
} }
@@ -49,7 +49,7 @@ export default class MessagePadDialogueListContainer extends React.PureComponent
: _.last(dialogue.reply).content : _.last(dialogue.reply).content
} }
faceUrl={ faceUrl={
dialogue.from === UserContext.uid dialogue.from === UserContext._id
? dialogue.to_udoc.gravatar_url ? dialogue.to_udoc.gravatar_url
: dialogue.from_udoc.gravatar_url : dialogue.from_udoc.gravatar_url
} }
@@ -18,9 +18,7 @@ const mapStateToProps = (state) => ({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
handleChange(id, value) { handleChange(id, value) {
if (id === null) { if (id === null) return;
return;
}
dispatch({ dispatch({
type: 'DIALOGUES_INPUT_CHANGED', type: 'DIALOGUES_INPUT_CHANGED',
payload: value, payload: value,
@@ -30,11 +28,9 @@ const mapDispatchToProps = (dispatch) => ({
}); });
}, },
postSend(placeholderId, uid, value) { postSend(placeholderId, uid, value) {
if (placeholderId === null) { if (placeholderId === null) return;
return;
}
const req = request.post('', { const req = request.post('', {
operation: 'send_message', operation: 'send',
uid, uid,
content: value, content: value,
}); });
@@ -46,20 +42,19 @@ const mapDispatchToProps = (dispatch) => ({
}, },
}); });
}, },
postReply(id, value) { postReply(_id, uid, value) {
if (id === null) { if (_id === null) return;
return;
}
const req = request.post('', { const req = request.post('', {
operation: 'reply_message', operation: 'send',
message_id: id, type: 'single',
uid,
content: value, content: value,
}); });
dispatch({ dispatch({
type: 'DIALOGUES_POST_REPLY', type: 'DIALOGUES_POST_REPLY',
payload: req, payload: req,
meta: { meta: {
dialogueId: id, dialogueId: _id,
}, },
}); });
}, },
@@ -103,6 +98,7 @@ export default class MessagePadInputContainer extends React.PureComponent {
} else { } else {
this.props.postReply( this.props.postReply(
this.props.activeId, this.props.activeId,
state.dialogues[this.props.activeId].to,
this.props.inputValue, this.props.inputValue,
); );
} }
@@ -22,7 +22,7 @@ export default function reducer(state = {}, action) {
...state, ...state,
[id]: { [id]: {
_id: id, _id: id,
from: UserContext.uid, from: UserContext._id,
to: user._id, to: user._id,
to_udoc: { ...user }, to_udoc: { ...user },
reply: [], reply: [],
+1 -2
View File
@@ -15,8 +15,7 @@ export default function reducer(state = {
} }
case 'SCRATCHPAD_RECORDS_PUSH': { case 'SCRATCHPAD_RECORDS_PUSH': {
const { rdoc } = action.payload; const { rdoc } = action.payload;
if (rdoc.uid !== UserContext.uid if (rdoc.uid !== UserContext._id
|| rdoc.domain_id !== UserContext.domain
|| rdoc.pid !== Context.problemId || rdoc.pid !== Context.problemId
) { ) {
return state; return state;