From e54956101c8584fbafbbe93b2ed422db15f60c26 Mon Sep 17 00:00:00 2001 From: KiritoKing Date: Sun, 18 Aug 2024 14:41:00 +0000 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=BF=80?= =?UTF-8?q?=E6=B4=BB=E7=A0=81=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/codemate-plugin/api.ts | 1 + .../plugins/invite-code/index.ts | 16 ++++++++++++++++ .../plugins/invite-code/model.ts | 18 ++++++++++++++++++ .../plugins/sms-register/index.ts | 14 +++++++++++++- packages/hydrooj/src/interface.ts | 1 + packages/hydrooj/src/model/document.ts | 5 ++++- 6 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 packages/codemate-plugin/plugins/invite-code/index.ts create mode 100644 packages/codemate-plugin/plugins/invite-code/model.ts diff --git a/packages/codemate-plugin/api.ts b/packages/codemate-plugin/api.ts index 70b5b374ec..c4535d46aa 100644 --- a/packages/codemate-plugin/api.ts +++ b/packages/codemate-plugin/api.ts @@ -3,5 +3,6 @@ import { Logger } from 'hydrooj'; export * from './plugins/privilege-group/model'; export * as plist from './plugins/assign-problem-list/model'; export * as bulletin from './plugins/bulletin/model'; +export * as invitation from './plugins/invite-code/model'; export const logger = new Logger('codemate'); diff --git a/packages/codemate-plugin/plugins/invite-code/index.ts b/packages/codemate-plugin/plugins/invite-code/index.ts new file mode 100644 index 0000000000..615faaf37d --- /dev/null +++ b/packages/codemate-plugin/plugins/invite-code/index.ts @@ -0,0 +1,16 @@ +import { Context, Handler, PERM } from 'hydrooj'; +import { InvitatonModel } from './model'; + +class InvitationCreateHandler extends Handler { + async post() { + const code = await InvitatonModel.add(this.domain._id, this.user._id); + this.response.body = { + success: true, + code, + }; + } +} + +export function apply(ctx: Context) { + ctx.Route('invitation_create', '/invite_code/create', InvitationCreateHandler, PERM.PERM_EDIT_DOMAIN); +} diff --git a/packages/codemate-plugin/plugins/invite-code/model.ts b/packages/codemate-plugin/plugins/invite-code/model.ts new file mode 100644 index 0000000000..76c953bf0a --- /dev/null +++ b/packages/codemate-plugin/plugins/invite-code/model.ts @@ -0,0 +1,18 @@ +import { Document, DocumentModel, nanoid } from 'hydrooj'; + +export interface InvitationDoc extends Document { + docId: string; + docType: typeof DocumentModel.TYPE_INVITATION; + content: string; +} + +export class InvitatonModel { + static get(domainId: string, code: string) { + return DocumentModel.get(domainId, DocumentModel.TYPE_INVITATION, code); + } + + static add(domainId: string, owner: number) { + const code = nanoid(8); + return DocumentModel.add(domainId, code, owner, DocumentModel.TYPE_INVITATION, code); + } +} diff --git a/packages/codemate-plugin/plugins/sms-register/index.ts b/packages/codemate-plugin/plugins/sms-register/index.ts index 0417e38c98..89b46c096b 100644 --- a/packages/codemate-plugin/plugins/sms-register/index.ts +++ b/packages/codemate-plugin/plugins/sms-register/index.ts @@ -117,7 +117,18 @@ export class RegisterBaseHandler extends Handler { @param('regionCode', Types.String, true) // 国内行政区划代码(国外用000000代替) @param('userRole', Types.Int, true) // 用户角色(如机构老师、学生等) @param('age', Types.PositiveInt, true) // 年龄 - async post(_, uname: string, password: string, nickname: string, nationality: string, regionCode: string, userRole: number, age: number) { + @param('inviteCode', Types.String, true) // 机构邀请码 + async post( + _, + uname: string, + password: string, + nickname?: string, + nationality?: string, + regionCode?: string, + userRole?: number, + age?: number, + inviteCode?: string, + ) { const uid = await UserModel.create(this.email, uname, password, undefined, this.request.ip); await UserModel.setById(uid, { nationality, @@ -125,6 +136,7 @@ export class RegisterBaseHandler extends Handler { userRole, age, nickname, + inviteCode, ...(this.token.phoneNumber ? { phoneNumber: this.token.phoneNumber } : {}), }); await TokenModel.del(this.token._id, TokenModel.TYPE_REGISTRATION); diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index e54cff33cc..f5f20ec063 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -97,6 +97,7 @@ export interface Udoc extends Record { regionCode?: string; // 行政区代码 userRole?: number; // 枚举值 age?: number; + inviteCode?: string; } /** diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index c7977d6b39..2d6a8a16f3 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -1,6 +1,6 @@ /* eslint-disable object-curly-newline */ import assert from 'assert'; -import { bulletin, plist } from 'codemate-plugin'; +import { bulletin, invitation, plist } from 'codemate-plugin'; import { Filter, FindCursor, ObjectId, OnlyFieldsOfType, PushOperator, SetFields, UpdateFilter } from 'mongodb'; import { Context } from '../context'; import { Content, ContestClarificationDoc, DiscussionDoc, DiscussionReplyDoc, ProblemDoc, ProblemStatusDoc, Tdoc, TrainingDoc } from '../interface'; @@ -29,6 +29,7 @@ export const TYPE_TRAINING: 40 = 40; /** @deprecated use `TYPE_CONTEST` with rule `homework` instead. */ export const TYPE_HOMEWORK: 60 = 60; export const TYPE_BULLETIN: 80 = 80; +export const TYPE_INVITATION: 90 = 90; export interface DocType { [TYPE_PROBLEM]: ProblemDoc; @@ -42,6 +43,7 @@ export interface DocType { [TYPE_TRAINING]: TrainingDoc; [TYPE_SYSTEM_PLIST]: plist.ProblemList; [TYPE_BULLETIN]: bulletin.BulletinDoc; + [TYPE_INVITATION]: invitation.InvitationDoc; } export interface DocStatusType { @@ -552,4 +554,5 @@ global.Hydro.model.document = { TYPE_PROBLEM_SOLUTION, TYPE_TRAINING, TYPE_BULLETIN, + TYPE_INVITATION, }; From 84c0fc57bfbfbb043418c28ae21d586503bf2545 Mon Sep 17 00:00:00 2001 From: KiritoKing Date: Sun, 18 Aug 2024 16:28:19 +0000 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E9=82=80?= =?UTF-8?q?=E8=AF=B7=E7=A0=81=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/assign-problem-list/model.ts | 11 ++++++-- .../plugins/invite-code/model.ts | 27 ++++++++++++++++--- .../plugins/sms-register/index.ts | 15 +++++++++-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/codemate-plugin/plugins/assign-problem-list/model.ts b/packages/codemate-plugin/plugins/assign-problem-list/model.ts index d0b2210af7..523bd276cc 100644 --- a/packages/codemate-plugin/plugins/assign-problem-list/model.ts +++ b/packages/codemate-plugin/plugins/assign-problem-list/model.ts @@ -64,11 +64,18 @@ export async function add( export async function edit(domainId: string, tid: ObjectId, $set: Partial) { const tdoc = await document.get(domainId, TYPE_PROBLEM_LIST, tid); if (!tdoc) throw new ProblemListNotFountError(tid); - const res = await document.set(domainId, TYPE_PROBLEM_LIST, tid, $set); if ($set.parent) { + // 先删除之前的parent.children + if (tdoc.parent) { + const ptdoc = await get(domainId, tdoc.parent); + await document.set(domainId, TYPE_PROBLEM_LIST, ptdoc._id, { + children: ptdoc.children.filter((id) => !tdoc.docId.equals(id)), + }); + } // 若有parent则更新parent.children - await document.set(domainId, TYPE_PROBLEM_LIST, $set.parent, undefined, undefined, undefined, { children: res._id }); + await document.set(domainId, TYPE_PROBLEM_LIST, $set.parent, undefined, undefined, undefined, { children: tdoc._id }); } + const res = await document.set(domainId, TYPE_PROBLEM_LIST, tid, $set); return res; } diff --git a/packages/codemate-plugin/plugins/invite-code/model.ts b/packages/codemate-plugin/plugins/invite-code/model.ts index 76c953bf0a..b98ec4e895 100644 --- a/packages/codemate-plugin/plugins/invite-code/model.ts +++ b/packages/codemate-plugin/plugins/invite-code/model.ts @@ -1,18 +1,39 @@ -import { Document, DocumentModel, nanoid } from 'hydrooj'; +import { Document, DocumentModel, Err, nanoid, NotFoundError, UserModel, UserNotFoundError } from 'hydrooj'; export interface InvitationDoc extends Document { docId: string; docType: typeof DocumentModel.TYPE_INVITATION; content: string; + users: number[]; } +export const InvitationNotFoundError = Err('InvitationNotFoundError', NotFoundError, 'Invitation code {0} not found.'); + export class InvitatonModel { - static get(domainId: string, code: string) { + static get(domainId: string, code: string): Promise { return DocumentModel.get(domainId, DocumentModel.TYPE_INVITATION, code); } static add(domainId: string, owner: number) { const code = nanoid(8); - return DocumentModel.add(domainId, code, owner, DocumentModel.TYPE_INVITATION, code); + return DocumentModel.add(domainId, code, owner, DocumentModel.TYPE_INVITATION, code, null, null, { users: [] }); + } + + static async registerCode(domainId: string, code: string, uid: number) { + const cdoc = await this.get(domainId, code); + if (!cdoc) { + throw new InvitationNotFoundError(code); + } + const user = await UserModel.getById(domainId, uid); + if (!user) { + throw new UserNotFoundError(uid); + } + // 更新邀请码记录 + DocumentModel.set(domainId, DocumentModel.TYPE_INVITATION, cdoc.docId, undefined, undefined, undefined, { + users: user._id, + }); + // 更新用户记录 + UserModel.setById(user._id, { inviteCode: code }); + return cdoc; } } diff --git a/packages/codemate-plugin/plugins/sms-register/index.ts b/packages/codemate-plugin/plugins/sms-register/index.ts index 89b46c096b..0f74cccc33 100644 --- a/packages/codemate-plugin/plugins/sms-register/index.ts +++ b/packages/codemate-plugin/plugins/sms-register/index.ts @@ -20,6 +20,7 @@ import { ValidationError, } from 'hydrooj'; import { logger, SendSMSFailedError } from './lib'; +import { InvitatonModel } from '../invite-code/model'; declare module 'hydrooj' { interface Lib { @@ -119,7 +120,7 @@ export class RegisterBaseHandler extends Handler { @param('age', Types.PositiveInt, true) // 年龄 @param('inviteCode', Types.String, true) // 机构邀请码 async post( - _, + domainId: string, uname: string, password: string, nickname?: string, @@ -136,11 +137,21 @@ export class RegisterBaseHandler extends Handler { userRole, age, nickname, - inviteCode, ...(this.token.phoneNumber ? { phoneNumber: this.token.phoneNumber } : {}), }); await TokenModel.del(this.token._id, TokenModel.TYPE_REGISTRATION); + // 邀请码注册 + if (inviteCode) { + // 邀请码不应阻塞注册 + try { + await InvitatonModel.registerCode(domainId, inviteCode, uid); + } catch (e) { + console.error(e); + logger.error('inviteCode register fail: ', e.message); + } + } + this.response.body = { success: true, code: 0, From 0a0d61e9006441e9ce39f142bf80d0c68118ad1d Mon Sep 17 00:00:00 2001 From: KiritoKing Date: Sun, 18 Aug 2024 16:33:02 +0000 Subject: [PATCH 3/7] chore: add logging --- packages/codemate-plugin/plugins/sms-register/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/codemate-plugin/plugins/sms-register/index.ts b/packages/codemate-plugin/plugins/sms-register/index.ts index 0f74cccc33..ea15fe0ec3 100644 --- a/packages/codemate-plugin/plugins/sms-register/index.ts +++ b/packages/codemate-plugin/plugins/sms-register/index.ts @@ -146,6 +146,7 @@ export class RegisterBaseHandler extends Handler { // 邀请码不应阻塞注册 try { await InvitatonModel.registerCode(domainId, inviteCode, uid); + logger.info('inviteCode register success: ', inviteCode); } catch (e) { console.error(e); logger.error('inviteCode register fail: ', e.message); From 37e6af1e566beec75b255b09d2ee2bee4efb0cbd Mon Sep 17 00:00:00 2001 From: KiritoKing Date: Mon, 26 Aug 2024 14:13:55 +0000 Subject: [PATCH 4/7] chore: modify git & settings --- .gitignore | 1 + .vscode/settings.json | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9570913c43..3b24dcf79d 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ pnpm-lock.yaml tsconfig.json tsconfig.*.json .idea/ +.vscode/* # Project Rules ## Core diff --git a/.vscode/settings.json b/.vscode/settings.json index e3bb0585c6..4b230f31de 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,5 @@ }, "files.associations": { "*.html": "nunjucks" - }, - "peacock.remoteColor": "#215732" + } } From 3c4ecd533d71747dfa44176ae1eb7cdc5c0e014a Mon Sep 17 00:00:00 2001 From: KiritoKing Date: Mon, 26 Aug 2024 14:25:41 +0000 Subject: [PATCH 5/7] =?UTF-8?q?chore:=20=E4=BF=AE=E6=94=B9devcontainer?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/devcontainer.json | 11 +++++++++-- .devcontainer/setup.sh | 28 ++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0c68fc9932..d041315b43 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,10 +16,17 @@ "forwardPorts": [8888, 2333, 8000], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "bash .devcontainer/setup.sh && yarn install" + "postCreateCommand": "bash .devcontainer/setup.sh", // Configure tool-specific properties. - // "customizations": {}, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/zsh" + }, + "extensions": ["ms-vscode-remote.remote-containers"] + } + } // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 2307ee0c71..186aac24dc 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -1,5 +1,25 @@ -mkdir ~/.hydro +#!/bin/bash + +# 创建必要的目录和配置文件 +mkdir -p ~/.hydro echo '["@hydrooj/ui-default"]' > ~/.hydro/addon.json -# the following config refers to a local mongodb instance. -# before you run this application, please ensure you have a mongo instance read at this port -echo '{"uri": "mongodb://admin:admin@localhost:27017/hydro?authSource=admin"}' > ~/.hydro/config.json \ No newline at end of file +echo '{"uri": "mongodb://admin:admin@localhost:27017/hydro?authSource=admin"}' > ~/.hydro/config.json + +# 安装 zsh 和 oh-my-zsh +sudo apt-get update && sudo apt-get install -y zsh curl git +sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" + +# 安装 zsh-autosuggestions 插件 +git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions + +# 安装 zsh-syntax-highlighting 插件 +git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting + +# 更新 .zshrc 文件以启用插件 +sed -i 's/plugins=(git)/plugins=(git zsh-autosuggestions zsh-syntax-highlighting)/' ~/.zshrc + +# 确保 zsh 是默认shell +chsh -s $(which zsh) + +# 安装 yarn 依赖 +yarn install \ No newline at end of file From e18b645519e8970134099bbe917e920f717616bc Mon Sep 17 00:00:00 2001 From: KiritoKing Date: Mon, 26 Aug 2024 14:33:00 +0000 Subject: [PATCH 6/7] chore: modify devcontainer adding zsh --- .devcontainer/devcontainer.json | 1 + .devcontainer/setup.sh | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d041315b43..bfd088a667 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,6 +17,7 @@ // Use 'postCreateCommand' to run commands after the container is created. "postCreateCommand": "bash .devcontainer/setup.sh", + "postStartCommand": "zsh", // 每次容器启动时自动进入 zsh // Configure tool-specific properties. "customizations": { diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 186aac24dc..0d60467621 100644 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -18,8 +18,5 @@ git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM: # 更新 .zshrc 文件以启用插件 sed -i 's/plugins=(git)/plugins=(git zsh-autosuggestions zsh-syntax-highlighting)/' ~/.zshrc -# 确保 zsh 是默认shell -chsh -s $(which zsh) - # 安装 yarn 依赖 yarn install \ No newline at end of file From 5f3b730786a09930db353b2f56067faa95893c39 Mon Sep 17 00:00:00 2001 From: KiritoKing Date: Mon, 26 Aug 2024 14:40:30 +0000 Subject: [PATCH 7/7] feat: enable batch create invite codes --- .devcontainer/devcontainer.json | 6 +++++- packages/codemate-plugin/plugins/invite-code/index.ts | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bfd088a667..fe9243c077 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -25,7 +25,11 @@ "settings": { "terminal.integrated.shell.linux": "/bin/zsh" }, - "extensions": ["ms-vscode-remote.remote-containers"] + "extensions": [ + "ms-vscode-remote.remote-containers", + "Codeium.codeium", + "esbenp.prettier-vscode" + ] } } diff --git a/packages/codemate-plugin/plugins/invite-code/index.ts b/packages/codemate-plugin/plugins/invite-code/index.ts index 615faaf37d..f5cd0b8020 100644 --- a/packages/codemate-plugin/plugins/invite-code/index.ts +++ b/packages/codemate-plugin/plugins/invite-code/index.ts @@ -1,9 +1,10 @@ -import { Context, Handler, PERM } from 'hydrooj'; +import { Context, Handler, param, PERM, Types } from 'hydrooj'; import { InvitatonModel } from './model'; class InvitationCreateHandler extends Handler { - async post() { - const code = await InvitatonModel.add(this.domain._id, this.user._id); + @param('num', Types.PositiveInt, true) + async post(domainId: string, num = 1) { + const code = await Promise.all(Array.from({ length: num }, () => InvitatonModel.add(this.domain._id, this.user._id))); this.response.body = { success: true, code,