177 lines
5.4 KiB
TypeScript
177 lines
5.4 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 { queryCheckInList } from 'services/chain.svr'
|
|
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, base58ToHex } from 'zutils/utils/security.util'
|
|
|
|
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
|
|
}
|
|
}
|
|
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
|
|
}
|
|
}
|
|
const records = await ScoreRecord.find({ user: user.id, activity: user.activity, scoreType: 'invite' })
|
|
let score = 0
|
|
for (let record of records) {
|
|
score += record.score
|
|
}
|
|
let result = {
|
|
address: user.address,
|
|
boost: user.boost || 1,
|
|
boostExpire: user.boostExpire,
|
|
twitterId: user.twitterId,
|
|
twitterName: user.twitterName,
|
|
discordId: user.discordId,
|
|
discordName: user.discordName,
|
|
scoreToday: todayScore ? parseInt(todayScore + '') : 0,
|
|
scoreTotal: totalScore ? parseInt(totalScore + '') : 0,
|
|
rankTotal: totalRank ? totalRank : '-',
|
|
invite,
|
|
inviteCount: records.length,
|
|
inviteScore: score,
|
|
code: user.inviteCode,
|
|
}
|
|
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 }
|
|
}
|
|
|
|
@router('get /api/user/checkin/list/:tag')
|
|
async checkInList(req) {
|
|
const user = req.user
|
|
const { tag } = req.params
|
|
let days: any = 1
|
|
if (tag === '1month') {
|
|
days = '1month'
|
|
} else if (tag === 'last') {
|
|
days = '1'
|
|
}
|
|
const res = await queryCheckInList(user.address, days, 0)
|
|
return res.data
|
|
}
|
|
|
|
/**
|
|
* regist user by token from wallet-svr
|
|
* TODO::
|
|
* @param req
|
|
* @param res
|
|
* @returns
|
|
*/
|
|
@role(ROLE_ANON)
|
|
@router('post /api/wallet/verify_token')
|
|
async verifyToken(req, res) {
|
|
const { wallet_token } = req.params
|
|
return {}
|
|
}
|
|
}
|