core: domain-join: bug fixes & add i18n

Co-authored-by: panda <panda_dtdyy@outlook.com>
This commit is contained in:
undefinedandpanda committed 2025-03-20 17:55:10 +00:00
1 parent b9dbdc39ca
commit e9c5bf5900
6 files changed
+69 -19

No files matched your search

+56 -12
View File
@@ -101,24 +101,68 @@ class DomainUserHandler extends ManageHandler {
@requireSudo @requireSudo
@param('format', Types.Range(['default', 'raw']), true) @param('format', Types.Range(['default', 'raw']), true)
async get({ domainId }, format = 'default') { async get({ domainId }, format = 'default') {
const rudocs = {};
const [dudocs, roles] = await Promise.all([ const [dudocs, roles] = await Promise.all([
domain.getMultiUserInDomain(domainId, { domain.collUser.aggregate([
$and: [ {
{ role: { $nin: ['default', 'guest'] } }, $match: {
{ role: { $ne: null } }, $or: [{
join: true,
}, {
role: {
$nin: ['default', 'guest'],
$ne: null,
},
}],
domainId,
},
},
{
$lookup: {
from: 'user',
let: { uid: '$uid' },
pipeline: [
{
$match: {
$expr: { $eq: ['$_id', '$$uid'] },
priv: { $bitsAllSet: PRIV.PRIV_USER_PROFILE },
},
},
{
$project: {
_id: 1,
uname: 1,
avatar: 1,
},
},
], ],
}).toArray(), as: 'user',
},
},
{ $unwind: '$user' },
{
$project: {
user: 1,
role: 1,
join: 1,
displayName: this.user.hasPerm(PERM.PERM_VIEW_DISPLAYNAME),
},
},
]).toArray(),
domain.getRoles(domainId), domain.getRoles(domainId),
]); ]);
// TODO: switch to getListForRender for better performance const users = dudocs.map((dudoc) => {
const udict = await user.getList(domainId, dudocs.map((dudoc) => dudoc.uid)); const u = {
const users = dudocs.filter((dudoc) => udict[dudoc.uid].priv & PRIV.PRIV_USER_PROFILE); ...dudoc,
const joined = Object.fromEntries(dudocs.map((dudoc) => [dudoc.uid, dudoc.join])); ...dudoc.user,
};
delete u.user;
return u;
});
const rudocs = {};
for (const role of roles) rudocs[role._id] = users.filter((udoc) => udoc.role === role._id); for (const role of roles) rudocs[role._id] = users.filter((udoc) => udoc.role === role._id);
this.response.template = format === 'raw' ? 'domain_user_raw.html' : 'domain_user.html'; this.response.template = format === 'raw' ? 'domain_user_raw.html' : 'domain_user.html';
this.response.body = { this.response.body = {
roles, rudocs, domain: this.domain, joined, roles, rudocs, domain: this.domain,
}; };
} }
@@ -286,7 +330,7 @@ class DomainJoinHandler extends Handler {
]); ]);
const assignedRole = this.user.hasPriv(PRIV.PRIV_MANAGE_ALL_DOMAIN) const assignedRole = this.user.hasPriv(PRIV.PRIV_MANAGE_ALL_DOMAIN)
? 'root' ? 'root'
: dudoc?.role; : dudoc?.role || 'default';
if (dudoc?.join) throw new DomainJoinAlreadyMemberError(target, this.user._id); if (dudoc?.join) throw new DomainJoinAlreadyMemberError(target, this.user._id);
const r = await domain.getRoles(ddoc); const r = await domain.getRoles(ddoc);
const roles = r.map((role) => role._id); const roles = r.map((role) => role._id);
+5 -4
View File
@@ -59,7 +59,7 @@ class DomainModel {
}; };
await bus.parallel('domain/create', ddoc); await bus.parallel('domain/create', ddoc);
await coll.insertOne(ddoc); await coll.insertOne(ddoc);
await DomainModel.setUserRole(domainId, owner, 'root'); await DomainModel.setUserRole(domainId, owner, 'root', true);
return domainId; return domainId;
} }
@@ -133,9 +133,10 @@ class DomainModel {
} }
@ArgMethod @ArgMethod
static async setUserRole(domainId: string, uid: MaybeArray<number>, role: string) { static async setUserRole(domainId: string, uid: MaybeArray<number>, role: string, autojoin = false) {
const update = { $set: { role, ...(autojoin ? { join: true } : {}) } };
if (!(uid instanceof Array)) { if (!(uid instanceof Array)) {
const res = await collUser.findOneAndUpdate({ domainId, uid }, { $set: { role } }, { upsert: true, returnDocument: 'after' }); const res = await collUser.findOneAndUpdate({ domainId, uid }, update, { upsert: true, returnDocument: 'after' });
const udoc = await UserModel.getById(domainId, uid); const udoc = await UserModel.getById(domainId, uid);
deleteUserCache(udoc); deleteUserCache(udoc);
return res; return res;
@@ -144,7 +145,7 @@ class DomainModel {
.project<{ _id: number, mail: string, uname: string }>({ mail: 1, uname: 1 }) .project<{ _id: number, mail: string, uname: string }>({ mail: 1, uname: 1 })
.toArray(); .toArray();
for (const udoc of affected) deleteUserCache(udoc); for (const udoc of affected) deleteUserCache(udoc);
return await collUser.updateMany({ domainId, uid: { $in: uid } }, { $set: { role } }, { upsert: true }); return await collUser.updateMany({ domainId, uid: { $in: uid } }, update, { upsert: true });
} }
static async getRoles(domainId: string, count?: boolean): Promise<any[]>; static async getRoles(domainId: string, count?: boolean): Promise<any[]>;
+1 -1
View File
@@ -707,7 +707,7 @@ export const coreScripts: MigrationScript[] = [
async function _91_92() { async function _91_92() {
await domain.collUser.updateMany({}, { $set: { join: true } }); await domain.collUser.updateMany({}, { $set: { join: true } });
await domain.coll.updateMany( await domain.coll.updateMany(
{ _join: { $or: [{ $exists: false }, { $eq: null }] } }, { $or: [{ _join: { $exists: false } }, { _join: { $eq: null } }] },
{ $set: { _join: { method: domain.JOIN_METHOD_ALL, role: 'default', expire: null }, _migratedJoin: true } }, { $set: { _join: { method: domain.JOIN_METHOD_ALL, role: 'default', expire: null }, _migratedJoin: true } },
); );
const domainUser = await domain.coll.find({ domainId: 'system', join: true }, onPrimary) const domainUser = await domain.coll.find({ domainId: 'system', join: true }, onPrimary)
+2
View File
@@ -906,6 +906,7 @@ Testdata: 测试数据
Text: 文本 Text: 文本
The 'default' role applies to ALL REGISTERED USER.: default 角色作用于<b>所有已注册用户</b> The 'default' role applies to ALL REGISTERED USER.: default 角色作用于<b>所有已注册用户</b>
The contest is a flexible time contest. You need to complete the contest within a specified time after you attended.: 本场比赛采用灵活时间模式,你需要在参加后的指定时间内完成比赛。 The contest is a flexible time contest. You need to complete the contest within a specified time after you attended.: 本场比赛采用灵活时间模式,你需要在参加后的指定时间内完成比赛。
The domain owner: 域所有者:
The homework's deadline is due but in extension. You can still submit for this problem but your score will be penalized.: 作业已超过截止时间,但仍在延期时间内。您递交题目将无法获得全部分数。 The homework's deadline is due but in extension. You can still submit for this problem but your score will be penalized.: 作业已超过截止时间,但仍在延期时间内。您递交题目将无法获得全部分数。
The invitation code to enter to successfully join the domain. You can only use letters and numbers in the code and it should not be longer than 64 characters.: 加入此域的邀请码。您只能使用字母和数字,并且不能长于 64 个字符。 The invitation code to enter to successfully join the domain. You can only use letters and numbers in the code and it should not be longer than 64 characters.: 加入此域的邀请码。您只能使用字母和数字,并且不能长于 64 个字符。
The invitation code you provided is invalid.: 您提供的邀请码无效。 The invitation code you provided is invalid.: 您提供的邀请码无效。
@@ -1057,6 +1058,7 @@ Write Your Comment: 发表您的评论
Write Your Solution: 发表您的题解 Write Your Solution: 发表您的题解
Year: 年 Year: 年
You are currently in legacy mode. Some additional features will be unavailable. We strongly recommend switching to standard mode on a modern browser.: 您当前处于兼容模式。某些功能在此模式下不可用。我们强烈建议在现代浏览器上切换为标准模式以获得更好的体验。 You are currently in legacy mode. Some additional features will be unavailable. We strongly recommend switching to standard mode on a modern browser.: 您当前处于兼容模式。某些功能在此模式下不可用。我们强烈建议在现代浏览器上切换为标准模式以获得更好的体验。
You are invited to join {0}: '您被邀请加入 {0}'
You are not allowed to join the domain. The link is either invalid or expired.: 您无法加入该域,链接无效或已过期。 You are not allowed to join the domain. The link is either invalid or expired.: 您无法加入该域,链接无效或已过期。
You can also upload your avatar to Gravatar and it will be automatically updated here.: 你也可以将头像上传到 <a href="https://gravatar.com/">Gravatar</a>,本站的头像将自动更新。 You can also upload your avatar to Gravatar and it will be automatically updated here.: 你也可以将头像上传到 <a href="https://gravatar.com/">Gravatar</a>,本站的头像将自动更新。
You can create your own training plans and share them with others.: 您可以创建您自己的训练计划并且与他人分享。 You can create your own training plans and share them with others.: 您可以创建您自己的训练计划并且与他人分享。
@@ -82,7 +82,9 @@
{%- for udoc in udocs -%} {%- for udoc in udocs -%}
{% set is_disabled=(rudoc._id == handler.user._id) %} {% set is_disabled=(rudoc._id == handler.user._id) %}
<input type="checkbox" data-uid="{{udoc._id}}" data-checkbox-group="user" {% if is_disabled %}disabled{% else %}data-checkbox-range{% endif %}> <input type="checkbox" data-uid="{{udoc._id}}" data-checkbox-group="user" {% if is_disabled %}disabled{% else %}data-checkbox-range{% endif %}>
[{{udoc._id}}]{{ user.render_inline(udoc, avatar=false, badge=false) }}&nbsp;&nbsp;&nbsp; [{{udoc._id}}]{{ user.render_inline(udoc, avatar=false, badge=false) }}{% if not udoc.join %}
<span class="text-orange">({{ _('Not joined yet') }})</span>
{% endif %}&nbsp;&nbsp;&nbsp;
{%- endfor -%} {%- endfor -%}
</div></td> </div></td>
<td>{{ role }}</td> <td>{{ role }}</td>
@@ -101,7 +103,7 @@
</td> </td>
<td class="col--user"> <td class="col--user">
{{ user.render_inline(rudoc, badge=false) }} {{ user.render_inline(rudoc, badge=false) }}
{% if not joined[rudoc._id] %}<span class="text-orange">({{ _('Not joined yet') }})</span>{% endif %} {% if not rudoc.join %}<span class="text-orange">({{ _('Not joined yet') }})</span>{% endif %}
</td> </td>
<td class="col--role"> <td class="col--role">
{{ form.select({ {{ form.select({
@@ -21,6 +21,7 @@
<li class="menu__item"><a class="menu__link" href="javascript:joinDomain();"> <li class="menu__item"><a class="menu__link" href="javascript:joinDomain();">
<span class="icon icon-add"></span> {{ _('Join Domain to Create a Discussion') }}</a></li> <span class="icon icon-add"></span> {{ _('Join Domain to Create a Discussion') }}</a></li>
{% else %} {% else %}
<li class="menu__item"><a class="menu__link disabled" href="javascript:;">
<span class="icon icon-add"></span> {{ _('No Permission to Create a Discussion') }}</a></li> <span class="icon icon-add"></span> {{ _('No Permission to Create a Discussion') }}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}