diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index f8e6a369f..786854976 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -114,12 +114,11 @@ class DomainUserHandler extends ManageHandler { // TODO: switch to getListForRender for better performance const udict = await user.getList(domainId, dudocs.map((dudoc) => dudoc.uid)); const users = dudocs.filter((dudoc) => udict[dudoc.uid].priv & PRIV.PRIV_USER_PROFILE); - for (const role of roles) { - rudocs[role._id] = users.filter((dudoc) => dudoc.role === role._id).map((i) => udict[i.uid]); - } + const joined = Object.fromEntries(dudocs.map((dudoc) => [dudoc.uid, dudoc.join])); + 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.body = { - roles, rudocs, udict, domain: this.domain, + roles, rudocs, domain: this.domain, joined, }; } @@ -215,7 +214,7 @@ class DomainJoinApplicationsHandler extends ManageHandler { async get() { const r = await domain.getRoles(this.domain); const roles = r.map((role) => role._id).sort(); - this.response.body.rolesWithText = roles.filter((i) => !['default', 'guest'].includes(i)).map((role) => [role, role]); + this.response.body.rolesWithText = roles.filter((i) => i !== 'guest').map((role) => [role, role]); this.response.body.joinSettings = domain.getJoinSettings(this.domain, roles); this.response.body.expirations = { ...domain.JOIN_EXPIRATION_RANGE }; if (!this.response.body.joinSettings) { @@ -279,33 +278,58 @@ class DomainJoinHandler extends Handler { joinSettings: any; noCheckPermView = true; - async prepare() { - const r = await domain.getRoles(this.domain); + @param('target', Types.DomainId, true) + async prepare({ domainId }, target: string = domainId) { + const [ddoc, dudoc] = await Promise.all([ + domain.get(target), + domain.getDomainUser(target, this.user), + ]); + if (dudoc.join) throw new DomainJoinAlreadyMemberError(target, this.user._id); + const r = await domain.getRoles(ddoc); const roles = r.map((role) => role._id); - this.joinSettings = domain.getJoinSettings(this.domain, roles); - if (!this.joinSettings) throw new DomainJoinForbiddenError(this.domain._id); - if (this.user.role !== 'default') throw new DomainJoinAlreadyMemberError(this.domain._id, this.user._id); + this.joinSettings = domain.getJoinSettings(ddoc, roles); + if (dudoc.role !== 'default') delete this.joinSettings; + else if (!this.joinSettings) throw new DomainJoinForbiddenError(target); } @param('code', Types.Content, true) - async get(domainId: string, code: string) { + @param('target', Types.DomainId, true) + @param('redirect', Types.Content, true) + async get({ domainId }, code: string, target: string = domainId, redirect: string = '') { this.response.template = 'domain_join.html'; - this.response.body.joinSettings = this.joinSettings; - this.response.body.code = code; + const ddoc = await domain.get(target); + const domainInfo = { + name: ddoc.name, + owner: await user.getById(domainId, ddoc.owner), + avatar: ddoc.avatar, + bulletin: ddoc.showBulletin ? ddoc.bulletin : '', + }; + this.response.body = { + joinSettings: this.joinSettings, + code, + redirect, + target, + domainInfo, + }; } @param('code', Types.Content, true) - async post(domainId: string, code: string) { - if (this.joinSettings.method === domain.JOIN_METHOD_CODE) { + @param('target', Types.DomainId, true) + @param('redirect', Types.Content, true) + async post({ domainId }, code: string, target: string = domainId, redirect: string = '') { + if (this.joinSettings?.method === domain.JOIN_METHOD_CODE) { if (this.joinSettings.code !== code) { - throw new InvalidJoinInvitationCodeError(this.domain._id); + throw new InvalidJoinInvitationCodeError(target); } } await Promise.all([ - domain.setUserRole(this.domain._id, this.user._id, this.joinSettings.role), + domain.setUserInDomain(target, this.user._id, { + join: true, + ...(this.joinSettings ? { role: this.joinSettings.role } : {}), + }), oplog.log(this, 'domain.join', {}), ]); - this.response.redirect = this.url('homepage', { query: { notification: 'Successfully joined domain.' } }); + this.response.redirect = redirect || this.url('homepage', { domainId: target, query: { notification: 'Successfully joined domain.' } }); } } diff --git a/packages/hydrooj/src/model/domain.ts b/packages/hydrooj/src/model/domain.ts index a69265063..be74c8d85 100644 --- a/packages/hydrooj/src/model/domain.ts +++ b/packages/hydrooj/src/model/domain.ts @@ -207,6 +207,7 @@ class DomainModel { let dudoc = await collUser.findOne({ domainId, uid: udoc._id }); dudoc ||= { domainId, uid: udoc._id }; if (!(udoc.priv & PRIV.PRIV_USER_PROFILE)) dudoc.role = 'guest'; + if (!dudoc.join) dudoc.role = 'guest'; if (udoc.priv & PRIV.PRIV_MANAGE_ALL_DOMAIN) dudoc.role = 'root'; dudoc.role ||= 'default'; const ddoc = await DomainModel.get(domainId); diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index cd1bed188..3c24663f1 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -212,6 +212,14 @@ export async function apply(ctx: Context) { redirect: (this.context.originalPath || this.request.path) + this.context.search, }, }); + } else if (!this.user._dudoc.join && error instanceof PermissionError) { + this.response.redirect = this.url('domain_join', { + domainId: 'system', + query: { + redirect: (this.context.originalPath || this.request.path) + this.context.search, + target: this.domain._id, + }, + }); } else { this.response.status = error instanceof UserFacingError ? error.code : 500; this.response.template = error instanceof UserFacingError ? 'error.html' : 'bsod.html'; diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index f80ee093f..6d8d5fb9b 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -489,6 +489,8 @@ IOI(Strict): IOI(严格) Is rated: 计入排名 It works!: 它成了! Italic: 斜体 +Join Domain to Create a Discussion: 加入域后创建讨论 +Join Domain to Submit: 加入域后递交 Join: 加入 Judge Config: 评测设置 Judge Status: 评测状态 @@ -603,6 +605,7 @@ Not Attended: 未参加 Not available: 不可用 Not Claimed: 未认领 Not Enrolled: 未参加 +Not joined yet: 暂未加入 Not started with a number: 不以数字开头 Not wrapped with space: 两侧不含空格 Numeric PID: 数字题号 @@ -698,9 +701,9 @@ problem_import: 导入题目 problem_main: 题库 problem_solution: 题解 problem_submit: 递交代码 +problem_type.communication: 通信题 problem_type.default: 传统题 problem_type.interactive: 交互题 -problem_type.communication: 通信题 problem_type.objective: 客观题 problem_type.remote_judge: 远端评测题 problem_type.submit_answer: 提交答案题 diff --git a/packages/ui-default/templates/domain_join.html b/packages/ui-default/templates/domain_join.html index 91f4e686e..249bd027e 100644 --- a/packages/ui-default/templates/domain_join.html +++ b/packages/ui-default/templates/domain_join.html @@ -5,12 +5,25 @@
{{ _('By clicking the button, you will become a member of the domain {0}.').format(handler.domain['name']) }}
+{{ _('By clicking the button, you will become a member of the domain {0}.').format(domainInfo['name']) }}
{{ _('The domain owner:') }}
+ {{ user.render_inline(domainInfo['owner']) }} +--{{ _("The 'default' role applies to ALL REGISTERED USER.")|safe }}
-{{ _("You may want to create a new role in 'Manage Role' page.")|safe }}
-