diff --git a/docs/uaw.md b/docs/uaw.md index 1b18579..52f58c9 100644 --- a/docs/uaw.md +++ b/docs/uaw.md @@ -24,6 +24,9 @@ 1. 探索状态(12), 移除signCfg, 增加seqStat, 用于标识连续签到奖励领取状态 1. 增加领取连续签到奖励(25)的接口 +#### 20240408 +1. 增加宝箱助力状态查询(26), 用于助力上链前查询是否符合条件 + ### 1. 钱包预登录 #### Request @@ -690,7 +693,7 @@ body: ```js { - "days": 3 // 领取的累计签到天数 + "code": 3 // 领取的累计签到天数 } ``` @@ -703,4 +706,37 @@ body: } ``` +### + +### 26.\* 宝箱助力状态查询 + +#### Request + +- URL:`/api/chest/enhance/state` +- 方法:`POST` +- 头部: + - Authorization: Bearer JWT_token + +body: + +```js +{ + "code": "1123", // 宝箱的分享code, code和id选择传一个, 如果两个都传, 优先使用id来查询箱子 + "id": "123123" +} + +``` + +#### Response + +```js +{ + userCurrent: 1, // 用户当日已助力次数 + userMax: 10, // 用户当日最大可助力次数 + chestCurrent: 1, // 宝箱当前助力次数 + chestMax: 10, // 宝箱最大可助力次数 + enhanced: 0, // 用户是否已经为当前宝箱助力 +} +``` + ### \ No newline at end of file diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 3f0e4ed..b87c319 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -49,3 +49,8 @@ export const SCORE_INVITE_REBATE = 'invite_rebate' // google reCaptcha最小分数 export const RECAPTCHA_MIN_SCORE = 0.5 + +// 基础用户每日可助力次数 +export const MAX_ENHANCE_COUNT_BASE = 1 +// 高级用户每日可助力次数 +export const MAX_ENHANCE_COUNT_ADV = 10 diff --git a/src/controllers/chest.controller.ts b/src/controllers/chest.controller.ts index cbe9117..7e0ef1d 100644 --- a/src/controllers/chest.controller.ts +++ b/src/controllers/chest.controller.ts @@ -6,10 +6,12 @@ import { formatDate } from 'zutils/utils/date.util' import { ScoreRecord } from 'models/ScoreRecord' import { ChestRecord } from 'models/chain/ChestRecord' import { generateNewChest } from 'services/game.svr' -import { SCORE_OPEN_CHEST } from 'common/Constants' +import { MAX_ENHANCE_COUNT_ADV, MAX_ENHANCE_COUNT_BASE, SCORE_OPEN_CHEST } from 'common/Constants' import { formatAddress } from 'zutils/utils/chain.util' import { isObjectIdString, isValidShareCode } from 'common/Utils' import { checkReCaptcha } from 'services/google.svr' +import { GeneralScription } from 'models/chain/GeneralScription' +import { ChestEnhanceRecord } from 'models/ChestEnhanceRecord' /** * 宝箱相关接口 @@ -89,6 +91,50 @@ class BoxController extends BaseController { } return result } + /** + * 宝箱助力状态查询 + */ + @router('post /api/chest/enhance/state') + async enhanceState(req) { + const { code, id } = req.params + const user = req.user + if (code && !isValidShareCode(code)) { + throw new ZError(11, 'invalid share code') + } + if (id && !isObjectIdString(id)) { + throw new ZError(11, 'invalid chest id') + } + if (!code && !id) { + throw new ZError(11, 'must provide share code or chest id') + } + let chest: any + if (id) { + chest = await ActivityChest.findById(id) + } else { + chest = await ActivityChest.findOne({ shareCode: code, activity: user.activity }) + } + if (!chest) { + throw new ZError(12, 'chest not found') + } + if (chest.status === ChestStatusEnum.OPENED) { + throw new ZError(14, 'chest already opened') + } + if (chest.status === ChestStatusEnum.LOCKED) { + throw new ZError(15, 'chest is locked') + } + const enhanced = chest.bonusUsers.includes(user.id) ? 1 : 0 + const userMax = user.twitterId && user.discordId ? MAX_ENHANCE_COUNT_ADV : MAX_ENHANCE_COUNT_BASE + const dateTag = formatDate(new Date()) + const userCurrent = await ChestEnhanceRecord.countDocuments({ user: user.id, activity: user.activity, dateTag }) + + return { + userCurrent, + userMax, + enhanced, + chestCurrent: chest.bonusUsers.length, + chestMax: chest.maxBounsCount, + } + } /** * 宝箱助力 */ @@ -102,6 +148,14 @@ class BoxController extends BaseController { if (!isValidShareCode(code)) { throw new ZError(11, 'invalid share code') } + const chainRecord = await GeneralScription.findOne({ + from: user.address.toLowerCase(), + op: 'chest_enhance', + data: code, + }) + if (!chainRecord) { + throw new ZError(13, 'waiting for chain confirm') + } // TODO:: 待规则确定后, 检查用户是否符合助力条件 const chest = await ActivityChest.findOne({ shareCode: code, activity: user.activity }) @@ -124,6 +178,12 @@ class BoxController extends BaseController { if (process.env.NODE_ENV === 'production' && chest.user === uid) { throw new ZError(15, 'can not enhance self') } + const userMax = user.twitterId && user.discordId ? MAX_ENHANCE_COUNT_ADV : MAX_ENHANCE_COUNT_BASE + const dateTag = formatDate(new Date()) + const userCurrent = await ChestEnhanceRecord.countDocuments({ user: user.id, activity: user.activity, dateTag }) + if (userCurrent >= userMax) { + throw new ZError(16, 'user enhance times exceed') + } const score = Math.floor(Math.random() * (chest.bounsCfg[1] - chest.bounsCfg[0] + 1) + chest.bounsCfg[0]) await ActivityChest.updateOne( { _id: chest.id }, @@ -132,20 +192,31 @@ class BoxController extends BaseController { $push: { bonusUsers: uid, bonusScores: score }, }, ) - const chestsForUser = await ActivityChest.find({ user: uid, activity: user.activity }) - // 如果用户没有宝箱, 则说明用户是新用户, 生成一个宝箱 - if (chestsForUser.length === 0) { - const newChest = generateNewChest(uid, user.activity, 1, ChestStatusEnum.NORMAL) - await newChest.save() - return { - score: 0, - chests: [newChest.toJson()], - } - } else { - return { - score: 0, - } + const enhanceRecord = new ChestEnhanceRecord({ + user: uid, + activity: user.activity, + dateTag, + chest: chest.id, + score, + }) + await enhanceRecord.save() + return { + score, } + // const chestsForUser = await ActivityChest.find({ user: uid, activity: user.activity }) + // 如果用户没有宝箱, 则说明用户是新用户, 生成一个宝箱 + // if (chestsForUser.length === 0) { + // const newChest = generateNewChest(uid, user.activity, 1, ChestStatusEnum.NORMAL) + // await newChest.save() + // return { + // score: 0, + // chests: [newChest.toJson()], + // } + // } else { + // return { + // score: 0, + // } + // } } /** diff --git a/src/models/ChestEnhanceRecord.ts b/src/models/ChestEnhanceRecord.ts new file mode 100644 index 0000000..a5812cd --- /dev/null +++ b/src/models/ChestEnhanceRecord.ts @@ -0,0 +1,43 @@ +import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose' +import { dbconn } from 'decorators/dbconn' +import { BaseModule } from './Base' + +@dbconn() +@index({ user: 1, activity: 1, dateTag: 1 }, { unique: false }) +@index({ user: 1, activity: 1, chest: 1 }, { unique: false }) +@index({ activity: 1, chest: 1 }, { unique: false }) +@modelOptions({ + schemaOptions: { collection: 'chest_enhance_record', timestamps: true }, +}) +class ChestEnhanceRecordClass extends BaseModule { + @prop({ required: true }) + public user: string + + @prop({ required: true }) + public activity: string + + @prop() + public chest: string + + @prop() + public dateTag: string + + @prop() + public score: number + + public toJson() { + return { + user: this.user, + activity: this.activity, + chest: this.chest, + score: this.score, + dateTag: this.dateTag, + //@ts-ignore + time: this.createdAt.getTime(), + } + } +} + +export const ChestEnhanceRecord = getModelForClass(ChestEnhanceRecordClass, { + existingConnection: ChestEnhanceRecordClass['db'], +})