add api for leaderboard
This commit is contained in:
parent
5d2a3af490
commit
bc17ea9f3a
@ -9,15 +9,6 @@
|
|||||||
}, {
|
}, {
|
||||||
"task": "TwitterFollow",
|
"task": "TwitterFollow",
|
||||||
"params": {"time": 6, "failRate": 60}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
|
||||||
"task": "TwitterRetweet",
|
|
||||||
"params": {"time": 6, "failRate": 60}
|
|
||||||
}, {
|
|
||||||
"task": "DiscordJoin",
|
|
||||||
"params": {}
|
|
||||||
}, {
|
|
||||||
"task": "DiscordRole",
|
|
||||||
"params": {"time": 6, "failRate": 60}
|
|
||||||
}, {
|
}, {
|
||||||
"task": "UpdateScore",
|
"task": "UpdateScore",
|
||||||
"params": {"score": [100, 20]}
|
"params": {"score": [100, 20]}
|
||||||
|
@ -3,6 +3,7 @@ import BaseController, { ROLE_ANON } from "common/base.controller";
|
|||||||
import { role, router } from "decorators/router";
|
import { role, router } from "decorators/router";
|
||||||
import { ActivityInfo } from "models/ActivityInfo";
|
import { ActivityInfo } from "models/ActivityInfo";
|
||||||
import { ActivityUser } from "models/ActivityUser";
|
import { ActivityUser } from "models/ActivityUser";
|
||||||
|
import { RedisClient } from "redis/RedisClient";
|
||||||
|
|
||||||
export default class ActivityController extends BaseController {
|
export default class ActivityController extends BaseController {
|
||||||
|
|
||||||
@ -37,9 +38,24 @@ export default class ActivityController extends BaseController {
|
|||||||
throw new ZError(12, 'invalid invite code')
|
throw new ZError(12, 'invalid invite code')
|
||||||
}
|
}
|
||||||
user.inviteUser = inviteUser.id
|
user.inviteUser = inviteUser.id
|
||||||
|
await user.save()
|
||||||
return {}
|
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 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) => {
|
const parseCurrentTask = (user: typeof ActivityUser, task: string) => {
|
||||||
if (!allTasks.has(task)) {
|
if (!allTasks.has(task)) {
|
||||||
throw new ZError(11, 'invalid task')
|
throw new ZError(11, 'invalid task')
|
||||||
}
|
}
|
||||||
let preEnd = true;
|
let preEnd = true;
|
||||||
let currentTask = null;
|
let currentTask = null;
|
||||||
let nextTask = null;
|
|
||||||
for (let taskData of user.taskProgress) {
|
for (let taskData of user.taskProgress) {
|
||||||
if (taskData.id === task) {
|
if (taskData.id === task) {
|
||||||
currentTask = taskData;
|
currentTask = taskData;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (taskData.status !== 2 && !currentTask) {
|
if (taskData.status !== TaskStatusEnum.SUCCESS && !currentTask) {
|
||||||
preEnd = false;
|
preEnd = false;
|
||||||
}
|
}
|
||||||
if (currentTask && !nextTask) {
|
|
||||||
nextTask = taskData;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!preEnd) {
|
if (!preEnd) {
|
||||||
throw new ZError(12, 'task previous not end')
|
throw new ZError(12, 'task previous not end')
|
||||||
@ -49,19 +60,24 @@ const parseCurrentTask = (user: typeof ActivityUser, task: string) => {
|
|||||||
if (!currentTask) {
|
if (!currentTask) {
|
||||||
throw new ZError(13, 'task not found')
|
throw new ZError(13, 'task not found')
|
||||||
}
|
}
|
||||||
return {preEnd, currentTask, nextTask}
|
return {preEnd, currentTask}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseNextTask = async (
|
const parseNextTask = async (
|
||||||
user: typeof ActivityUser,
|
user: typeof ActivityUser,
|
||||||
activity: typeof ActivityInfo,
|
activity: typeof ActivityInfo,
|
||||||
task: TaskStatus) => {
|
task: string) => {
|
||||||
let Task = require('../tasks/' + task.id);
|
let nextTask = findNextTask(user, task);
|
||||||
|
if (!nextTask) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let Task = require('../tasks/' + nextTask.id);
|
||||||
if (!Task.default.auto) {
|
if (!Task.default.auto) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
let taskInstance = new Task.default({user, activity});
|
let taskInstance = new Task.default({user, activity});
|
||||||
let result = await taskInstance.execute({});
|
let result = await taskInstance.execute({});
|
||||||
|
await parseNextTask(user, activity, nextTask.id);
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
export default class TasksController extends BaseController {
|
export default class TasksController extends BaseController {
|
||||||
@ -110,7 +126,10 @@ export default class TasksController extends BaseController {
|
|||||||
let user = req.user;
|
let user = req.user;
|
||||||
let activity = req.activity;
|
let activity = req.activity;
|
||||||
let { task } = req.params;
|
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) {
|
if (currentTask.status !== TaskStatusEnum.RUNNING) {
|
||||||
throw new ZError(11, 'task not begin');
|
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 taskInstance = new Task.default({user, activity});
|
||||||
let result = await taskInstance.execute({});
|
let result = await taskInstance.execute({});
|
||||||
if (result) {
|
if (result) {
|
||||||
await parseNextTask(user, activity, nextTask);
|
await parseNextTask(user, activity, task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return currentTask
|
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 redis from 'redis'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { singleton } from '../decorators/singleton'
|
import { singleton } from '../decorators/singleton'
|
||||||
@ -180,6 +179,13 @@ export class RedisClient {
|
|||||||
this.pub.zadd(key, value, member, resolve)
|
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) {
|
public async zrangebyscore(key: string, min: number, max: number) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.pub.zrangebyscore(key, min, max, 'withscores', (err, data) => {
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.pub.zrevrange(key, start, end, 'withscores', (err, data) => {
|
this.pub.zrevrange(key, start, end, 'withscores', (err, data) => {
|
||||||
if (err) {
|
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 { ITask } from "./base/ITask";
|
||||||
import { TaskCfg } from "models/ActivityInfo";
|
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 {
|
export default class UpdateScore extends ITask {
|
||||||
static desc = 'update invite score'
|
static desc = 'update invite score'
|
||||||
@ -9,15 +36,13 @@ export default class UpdateScore extends ITask {
|
|||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
let task = this.params.user.taskProgress.find((t: TaskStatus) => t.id === this.constructor.name)
|
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 cfg = this.params.activity.tasks.find((t: TaskCfg) => t.task === this.constructor.name)
|
||||||
let scores = cfg.params.scores;
|
let scores = cfg.params.score;
|
||||||
for (let i = 0, l = scores.length; i < l; i++) {
|
task.status = TaskStatusEnum.SUCCESS
|
||||||
let score = scores[i]
|
task.timeFinish = Date.now()
|
||||||
let user = await ActivityUser.findById(this.params.user.inviteUser)
|
task.data = {}
|
||||||
if (user) {
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user