task-svr/src/controllers/sign.controller.ts
2024-03-29 11:18:52 +08:00

159 lines
5.5 KiB
TypeScript

import { ZError, SyncLocker, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils'
import { BOOST_CFG } from 'configs/boost'
import { ActivityUser } from 'models/ActivityUser'
import { DEFAULT_EXPIRED, NonceRecord } from 'models/NonceRecord'
import { ScoreRecord } from 'models/ScoreRecord'
import { LoginRecordQueue } from 'queue/loginrecord.queue'
import { rankKey } from 'services/rank.svr'
import { SiweMessage } from 'siwe'
import { nextday } from 'zutils/utils/date.util'
import { checkParamsNeeded } from 'zutils/utils/net.util'
import { aesDecrypt } from 'zutils/utils/security.util'
import { base58ToHex } from 'zutils/utils/string.util'
import { ActivityGame } from 'models/ActivityGame'
import { SCORE_INVITE_USER, SCORE_SOCIAL_TASK } from 'common/Constants'
import { isObjectIdString } from 'common/Utils'
const LOGIN_TIP = 'This signature is just to verify your identity'
const ROUND = 1000000
const generateBoost = (rewards: { probability: number }[]) => {
let total = 0
let random = Math.floor(Math.random() * ROUND)
let reward = null
for (let r of rewards) {
total += r.probability
if (random < total) {
reward = r
break
}
}
return reward.value
}
class SignController extends BaseController {
@role(ROLE_ANON)
@router('get /api/wallet/nonce')
async walletNonce(req, res) {
const { address } = req.params
let record = new NonceRecord({ expired: Date.now() + DEFAULT_EXPIRED, address })
await record.save()
return { nonce: record.id, tips: LOGIN_TIP }
}
@role(ROLE_ANON)
@router('post /api/wallet/login')
async walletVerify(req, res) {
const { signature, message, activity } = req.params
checkParamsNeeded(signature, message)
if (!message.nonce) {
throw new ZError(11, 'Invalid nonce')
}
let nonce = message.nonce
let source = 'unknow'
if (nonce.length > 24) {
nonce = base58ToHex(nonce)
let nonceStr = aesDecrypt(nonce, activity)
if (nonceStr.indexOf('|') >= 0) {
const split = nonceStr.split('|')
nonce = split[0]
source = split[1]
} else {
nonce = nonceStr
}
}
if (!isObjectIdString(nonce)) {
throw new ZError(11, 'nonce invalid')
}
let record = await NonceRecord.findById(nonce)
if (!record || record.status !== 0) {
throw new ZError(12, 'nonce invalid')
}
if (record.expired < Date.now()) {
throw new ZError(13, 'nonce expired')
}
if (record.address.toLowerCase() !== message.address.toLowerCase()) {
throw new ZError(14, 'address not match')
}
record.status = 1
await record.save()
const msgSign = new SiweMessage(message)
try {
await msgSign.verify({ signature, nonce: message.nonce })
} catch (e) {
throw new ZError(15, 'signature invalid')
}
let accountData = await ActivityUser.insertOrUpdate({ address: message.address, activity }, {})
if (!accountData) {
throw new ZError(11, 'user not found')
}
if (accountData.locked) {
throw new ZError(12, 'user locked')
}
accountData.lastLogin = new Date()
await accountData.save()
new LoginRecordQueue().addLog(req, accountData.id, activity, source)
const token = await res.jwtSign({
id: accountData.id,
address: accountData.address,
activity: accountData.activity,
})
return { token }
}
@router('get /api/user/state')
async userInfo(req) {
const user = req.user
const todayKey = rankKey(user.activity, new Date())
const todayScore = await new ZRedisClient().zscore(todayKey, user.id)
const totalKey = rankKey(user.activity)
const totalScore = await new ZRedisClient().zscore(totalKey, user.id)
const totalRank = await new ZRedisClient().zrevrank(totalKey, user.id)
let invite = ''
if (user.inviteUser) {
const inviteUser = await ActivityUser.findById(user.inviteUser)
if (inviteUser) {
invite = inviteUser.address.toLowerCase()
}
}
const records = await ScoreRecord.find({ user: user.id, activity: user.activity, type: SCORE_INVITE_USER })
const gameRecord = await ActivityGame.insertOrUpdate({ user: user.id, activity: user.activity }, {})
let inviteScore = records.reduce((acc, record) => acc + record.score, 0)
const recordSocial = await ScoreRecord.find({ user: user.id, activity: user.activity, type: SCORE_SOCIAL_TASK })
let scoreSocial = recordSocial.reduce((acc, record) => acc + record.score, 0)
let result = {
address: user.address.toLowerCase(),
boost: user.boost || 1,
boostExpire: user.boostExpire,
twitterId: user.twitterId,
twitterName: user.twitterName,
twitterAvatar: user.twitterAvatar,
discordId: user.discordId,
discordName: user.discordName,
scoreToday: todayScore ? parseInt(todayScore + '') : 0,
scoreTotal: totalScore ? parseInt(totalScore + '') : 0,
rankTotal: totalRank ? totalRank : '-',
invite,
inviteCount: records.length,
inviteScore,
scoreSocial,
code: user.inviteCode,
mapopen: gameRecord.status,
}
return result
}
// @router('get /api/user/state/boost')
async boost(req) {
new SyncLocker().checkLock(req)
const user = req.user
if (user.boost > 1 && user.boostExpire && user.boostExpire > Date.now()) {
throw new ZError(11, 'already boosted')
}
user.boost = generateBoost(BOOST_CFG)
user.boostExpire = nextday()
await user.save()
return { boost: user.boost, boostExpire: user.boostExpire }
}
}