add api for leaderboard
This commit is contained in:
parent
5d2a3af490
commit
bc17ea9f3a
@ -9,15 +9,6 @@
|
||||
}, {
|
||||
"task": "TwitterFollow",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"task": "TwitterRetweet",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"task": "DiscordJoin",
|
||||
"params": {}
|
||||
}, {
|
||||
"task": "DiscordRole",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"task": "UpdateScore",
|
||||
"params": {"score": [100, 20]}
|
||||
|
@ -3,6 +3,7 @@ import BaseController, { ROLE_ANON } from "common/base.controller";
|
||||
import { role, router } from "decorators/router";
|
||||
import { ActivityInfo } from "models/ActivityInfo";
|
||||
import { ActivityUser } from "models/ActivityUser";
|
||||
import { RedisClient } from "redis/RedisClient";
|
||||
|
||||
export default class ActivityController extends BaseController {
|
||||
|
||||
@ -37,9 +38,24 @@ export default class ActivityController extends BaseController {
|
||||
throw new ZError(12, 'invalid invite code')
|
||||
}
|
||||
user.inviteUser = inviteUser.id
|
||||
await user.save()
|
||||
return {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@router('get /api/activity/leaderboard')
|
||||
async inviteCode(req) {
|
||||
let user = req.user;
|
||||
let records = await new RedisClient().zrevrange(`${user.activity}:score`, 0, 9)
|
||||
let results: any = []
|
||||
for (let i = 0; i < records.length; i+=2) {
|
||||
let id = records[i]
|
||||
let score = parseInt(records[i + 1])
|
||||
let user = await ActivityUser.findById(id)
|
||||
results.push({
|
||||
address: user.address,
|
||||
score
|
||||
})
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
@ -25,23 +25,34 @@ const initTasks = () => {
|
||||
}
|
||||
const allTasks = initTasks();
|
||||
|
||||
const findNextTask = (user: typeof ActivityUser, task: string) => {
|
||||
let currentTask = null;
|
||||
for (let taskData of user.taskProgress) {
|
||||
if (taskData.id === task) {
|
||||
currentTask = taskData;
|
||||
continue;
|
||||
}
|
||||
if (currentTask && currentTask.status === TaskStatusEnum.SUCCESS) {
|
||||
return taskData;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const parseCurrentTask = (user: typeof ActivityUser, task: string) => {
|
||||
if (!allTasks.has(task)) {
|
||||
throw new ZError(11, 'invalid task')
|
||||
}
|
||||
let preEnd = true;
|
||||
let currentTask = null;
|
||||
let nextTask = null;
|
||||
for (let taskData of user.taskProgress) {
|
||||
if (taskData.id === task) {
|
||||
currentTask = taskData;
|
||||
break;
|
||||
}
|
||||
if (taskData.status !== 2 && !currentTask) {
|
||||
if (taskData.status !== TaskStatusEnum.SUCCESS && !currentTask) {
|
||||
preEnd = false;
|
||||
}
|
||||
if (currentTask && !nextTask) {
|
||||
nextTask = taskData;
|
||||
}
|
||||
}
|
||||
if (!preEnd) {
|
||||
throw new ZError(12, 'task previous not end')
|
||||
@ -49,19 +60,24 @@ const parseCurrentTask = (user: typeof ActivityUser, task: string) => {
|
||||
if (!currentTask) {
|
||||
throw new ZError(13, 'task not found')
|
||||
}
|
||||
return {preEnd, currentTask, nextTask}
|
||||
return {preEnd, currentTask}
|
||||
}
|
||||
|
||||
const parseNextTask = async (
|
||||
user: typeof ActivityUser,
|
||||
activity: typeof ActivityInfo,
|
||||
task: TaskStatus) => {
|
||||
let Task = require('../tasks/' + task.id);
|
||||
task: string) => {
|
||||
let nextTask = findNextTask(user, task);
|
||||
if (!nextTask) {
|
||||
return true
|
||||
}
|
||||
let Task = require('../tasks/' + nextTask.id);
|
||||
if (!Task.default.auto) {
|
||||
return true
|
||||
}
|
||||
let taskInstance = new Task.default({user, activity});
|
||||
let result = await taskInstance.execute({});
|
||||
await parseNextTask(user, activity, nextTask.id);
|
||||
return result
|
||||
}
|
||||
export default class TasksController extends BaseController {
|
||||
@ -110,7 +126,10 @@ export default class TasksController extends BaseController {
|
||||
let user = req.user;
|
||||
let activity = req.activity;
|
||||
let { task } = req.params;
|
||||
let { currentTask, nextTask } = parseCurrentTask(user, task);
|
||||
let { currentTask } = parseCurrentTask(user, task);
|
||||
if (currentTask.status === TaskStatusEnum.SUCCESS) {
|
||||
return currentTask;
|
||||
}
|
||||
if (currentTask.status !== TaskStatusEnum.RUNNING) {
|
||||
throw new ZError(11, 'task not begin');
|
||||
}
|
||||
@ -119,7 +138,7 @@ export default class TasksController extends BaseController {
|
||||
let taskInstance = new Task.default({user, activity});
|
||||
let result = await taskInstance.execute({});
|
||||
if (result) {
|
||||
await parseNextTask(user, activity, nextTask);
|
||||
await parseNextTask(user, activity, task);
|
||||
}
|
||||
}
|
||||
return currentTask
|
||||
|
32
src/models/ScoreRecord.ts
Normal file
32
src/models/ScoreRecord.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Severity, getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose'
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import { BaseModule } from './Base'
|
||||
|
||||
export enum ScoreTypeEnum {
|
||||
INVITE = 1,
|
||||
}
|
||||
|
||||
@dbconn()
|
||||
@index({ user: 1 }, { unique: false })
|
||||
@index({ activity: 1 }, { unique: false })
|
||||
@modelOptions({ schemaOptions: { collection: 'score_record', timestamps: true }, options: { allowMixed: Severity.ALLOW } })
|
||||
class ScoreRecordClass extends BaseModule {
|
||||
@prop({ required: true})
|
||||
public user: string
|
||||
|
||||
@prop({ required: true})
|
||||
public activity: string
|
||||
|
||||
@prop()
|
||||
public score: number
|
||||
|
||||
@prop({ enum: ScoreTypeEnum })
|
||||
public type: ScoreTypeEnum
|
||||
|
||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||
public data: any
|
||||
|
||||
}
|
||||
|
||||
export const ScoreRecord = getModelForClass(ScoreRecordClass, { existingConnection: ScoreRecordClass['db'] })
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { resolveCname } from 'dns'
|
||||
import redis from 'redis'
|
||||
import { promisify } from 'util'
|
||||
import { singleton } from '../decorators/singleton'
|
||||
@ -180,6 +179,13 @@ export class RedisClient {
|
||||
this.pub.zadd(key, value, member, resolve)
|
||||
})
|
||||
}
|
||||
|
||||
public async zincrby(key: string, value: any, member: string) {
|
||||
return new Promise(resolve => {
|
||||
this.pub.zincrby(key, value, member, resolve)
|
||||
})
|
||||
}
|
||||
|
||||
public async zrangebyscore(key: string, min: number, max: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.zrangebyscore(key, min, max, 'withscores', (err, data) => {
|
||||
@ -235,7 +241,7 @@ export class RedisClient {
|
||||
})
|
||||
}
|
||||
|
||||
public async zrevrange(key: string, start: number, end: number) {
|
||||
public async zrevrange(key: string, start: number, end: number): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.zrevrange(key, start, end, 'withscores', (err, data) => {
|
||||
if (err) {
|
||||
|
@ -1,6 +1,33 @@
|
||||
import { ActivityUser, TaskStatus } from "models/ActivityUser";
|
||||
import { ActivityUser, TaskStatus, TaskStatusEnum } from "models/ActivityUser";
|
||||
import { ITask } from "./base/ITask";
|
||||
import { TaskCfg } from "models/ActivityInfo";
|
||||
import { ScoreRecord, ScoreTypeEnum } from "models/ScoreRecord";
|
||||
import { RedisClient } from "redis/RedisClient";
|
||||
|
||||
const updateInviteScore = async (user: typeof ActivityUser, scores: number[], level: number) => {
|
||||
if (!user.inviteUser || scores.length <= level) {
|
||||
return;
|
||||
}
|
||||
let userSup = await ActivityUser.findById(user.inviteUser)
|
||||
if (!userSup) {
|
||||
return;
|
||||
}
|
||||
let record = new ScoreRecord({
|
||||
user: userSup.id,
|
||||
activity: user.activity,
|
||||
score: scores[level],
|
||||
type: ScoreTypeEnum.INVITE,
|
||||
data: {
|
||||
user: user.id,
|
||||
level
|
||||
}
|
||||
})
|
||||
await record.save();
|
||||
const key = `${user.activity}:score`
|
||||
const score = scores[level] + 1 - (Date.now() / 1000 / 10000000000)
|
||||
await new RedisClient().zincrby(key, score, userSup.id);
|
||||
await updateInviteScore(userSup, scores, level + 1)
|
||||
}
|
||||
|
||||
export default class UpdateScore extends ITask {
|
||||
static desc = 'update invite score'
|
||||
@ -9,15 +36,13 @@ export default class UpdateScore extends ITask {
|
||||
async execute(data: any) {
|
||||
let task = this.params.user.taskProgress.find((t: TaskStatus) => t.id === this.constructor.name)
|
||||
let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.task === this.constructor.name)
|
||||
let scores = cfg.params.scores;
|
||||
for (let i = 0, l = scores.length; i < l; i++) {
|
||||
let score = scores[i]
|
||||
let user = await ActivityUser.findById(this.params.user.inviteUser)
|
||||
if (user) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
let scores = cfg.params.score;
|
||||
task.status = TaskStatusEnum.SUCCESS
|
||||
task.timeFinish = Date.now()
|
||||
task.data = {}
|
||||
await this.params.user.save();
|
||||
// According to configuration, add score to user who invite current user
|
||||
await updateInviteScore(this.params.user, scores, 0)
|
||||
return true
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user