修改task完成机制
This commit is contained in:
parent
f1a7b1f7e9
commit
e9b0f802c1
40
docs/api.md
40
docs/api.md
@ -93,7 +93,12 @@ SiweMessage说明: https://docs.login.xyz/sign-in-with-ethereum/quickstart-guide
|
||||
{
|
||||
"id": "任务id",
|
||||
"title": "任务名",
|
||||
"desc": "任务描述"
|
||||
"desc": "任务描述",
|
||||
"type": 1, //任务类型, 1: 一次性任务, 2: 日常任务
|
||||
"pretasks": ["task id 1"], //前置任务
|
||||
"score": 0, // 完成任务可获得的积分
|
||||
"category": "", // 任务分类
|
||||
"autoclaim": false // 任务完成后是否自动获取奖励
|
||||
}
|
||||
],
|
||||
"startTime": 1702628292366, // 活动开始时间
|
||||
@ -188,7 +193,36 @@ body:
|
||||
}
|
||||
```
|
||||
|
||||
### 6.\* 提交邀请码
|
||||
###7.\* 获取任务奖励
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/tasks/claim`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
"task": "TwitterFollow" // 任务id
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 1, // 任务状态, 0: 未开始, 1: 进行中, 2: 成功, 9: 失败
|
||||
"id": "TwitterFollow", // 任务id
|
||||
"timeStart": 1703150294051, // 任务开始时间
|
||||
"timeFinish": 1703151338598
|
||||
}
|
||||
```
|
||||
|
||||
### 8.\* 提交邀请码
|
||||
|
||||
#### Request
|
||||
|
||||
@ -213,7 +247,7 @@ body:
|
||||
{}
|
||||
```
|
||||
|
||||
### 7. 积分排行榜
|
||||
### 9. 积分排行榜
|
||||
|
||||
#### Request
|
||||
|
||||
|
@ -6,16 +6,35 @@
|
||||
"tasks": [{
|
||||
"id": "TwitterConnect",
|
||||
"title": "Connect Twitter",
|
||||
"type": 1,
|
||||
"desc": "",
|
||||
"params": {}
|
||||
"category": "",
|
||||
"autoclaim": true,
|
||||
"params": {"score": 100}
|
||||
}, {
|
||||
"id": "TwitterFollow",
|
||||
"title": "Follow Twitter",
|
||||
"type": 1,
|
||||
"desc": "",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
"category": "",
|
||||
"autoclaim": false,
|
||||
"pretasks": ["TwitterConnect"],
|
||||
"params": {"score": 100, "time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "TwitterRetweet",
|
||||
"title": "ReTwitt",
|
||||
"type": 2,
|
||||
"desc": "",
|
||||
"category": "",
|
||||
"autoclaim": false,
|
||||
"pretasks": ["TwitterConnect"],
|
||||
"params": {"score": 100, "time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "UpdateScore",
|
||||
"type": 1,
|
||||
"show": false,
|
||||
"autoclaim": false,
|
||||
"pretasks": ["TwitterConnect", "TwitterFollow"],
|
||||
"params": {"score": [100, 20]}
|
||||
}],
|
||||
"startTime": 1702628292366,
|
||||
|
@ -19,15 +19,7 @@ export default class ActivityController extends BaseController {
|
||||
if (!activity) {
|
||||
throw new ZError(12, 'activity not found')
|
||||
}
|
||||
let tasks = []
|
||||
for (let task of activity.tasks) {
|
||||
if (task.show) {
|
||||
tasks.push({id: task.id, title: task.title, desc: task.desc})
|
||||
}
|
||||
}
|
||||
let result = activity.toJson()
|
||||
result.tasks = tasks
|
||||
return result;
|
||||
return activity.toJson()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { ZError } from "common/ZError";
|
||||
import BaseController, { ROLE_ANON } from "common/base.controller";
|
||||
import { role, router } from "decorators/router";
|
||||
import { all } from "deepmerge";
|
||||
import { ActivityInfo, TaskCfg } from "models/ActivityInfo";
|
||||
import { router } from "decorators/router";
|
||||
import { ActivityInfo, TaskCfg, TaskTypeEnum } from "models/ActivityInfo";
|
||||
import { ActivityUser, TaskStatus, TaskStatusEnum } from "models/ActivityUser";
|
||||
import { join } from 'path'
|
||||
import { formatDate } from "utils/date.util";
|
||||
const fs = require('fs')
|
||||
|
||||
const prod = process.env.NODE_ENV === 'production'
|
||||
@ -25,82 +25,46 @@ 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;
|
||||
for (let taskData of user.taskProgress) {
|
||||
if (taskData.id === task) {
|
||||
currentTask = taskData;
|
||||
break;
|
||||
}
|
||||
if (taskData.status !== TaskStatusEnum.SUCCESS && !currentTask) {
|
||||
preEnd = false;
|
||||
}
|
||||
}
|
||||
if (!preEnd) {
|
||||
throw new ZError(12, 'task previous not end')
|
||||
}
|
||||
if (!currentTask) {
|
||||
throw new ZError(13, 'task not found')
|
||||
}
|
||||
return {preEnd, currentTask}
|
||||
}
|
||||
|
||||
const parseNextTask = async (
|
||||
user: typeof ActivityUser,
|
||||
activity: typeof ActivityInfo,
|
||||
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 {
|
||||
|
||||
@router('post /api/tasks/progress')
|
||||
async taskProgress(req) {
|
||||
let user = req.user;
|
||||
let activity = req.activity;
|
||||
if (!user.taskProgress || user.taskProgress.length === 0) {
|
||||
for (let task of activity.tasks) {
|
||||
if (allTasks.has(task.id)) {
|
||||
const dateTag = formatDate(new Date());
|
||||
let taskAddedSet = new Set();
|
||||
for (let task of user.taskProgress) {
|
||||
if (task.dateTag) {
|
||||
taskAddedSet.add(task.id + ':' + task.dateTag)
|
||||
} else {
|
||||
taskAddedSet.add(task.id)
|
||||
}
|
||||
}
|
||||
let modified = false;
|
||||
let visibleTasks = new Set();
|
||||
for (let task of activity.tasks) {
|
||||
if (!allTasks.has(task.id)) {
|
||||
continue;
|
||||
}
|
||||
if (task.type === TaskTypeEnum.DAILY ) {
|
||||
let id = `${task.id}:${dateTag}`
|
||||
if (!taskAddedSet.has(id)) {
|
||||
modified = true;
|
||||
user.taskProgress.push({id, dateTag: dateTag, status: TaskStatusEnum.NOT_START})
|
||||
}
|
||||
if (task.show) visibleTasks.add(id);
|
||||
} else if (task.type === TaskTypeEnum.ONCE ) {
|
||||
if (!taskAddedSet.has(task.id)) {
|
||||
modified = true;
|
||||
user.taskProgress.push({id: task.id, status: TaskStatusEnum.NOT_START})
|
||||
}
|
||||
if (task.show) visibleTasks.add(task.id);
|
||||
}
|
||||
}
|
||||
if (modified) {
|
||||
await user.save();
|
||||
}
|
||||
let visibleTasks = new Set();
|
||||
activity.tasks.forEach((t:TaskCfg) => {
|
||||
if (t.show) {
|
||||
visibleTasks.add(t.id)
|
||||
}
|
||||
})
|
||||
return user.taskProgress.filter((t: TaskStatus) => visibleTasks.has(t.id));
|
||||
}
|
||||
|
||||
@ -108,35 +72,93 @@ export default class TasksController extends BaseController {
|
||||
@router('post /api/tasks/begin_task')
|
||||
async beginTask(req) {
|
||||
let user = req.user;
|
||||
let activity = req.activity;
|
||||
let { task } = req.params;
|
||||
let { currentTask } = parseCurrentTask(user, task);
|
||||
currentTask.timeStart = Date.now();
|
||||
currentTask.status = 1;
|
||||
const [taskId, dateTag] = task.split(':');
|
||||
const currentDateTag = formatDate(new Date());
|
||||
if (currentDateTag !== dateTag) {
|
||||
throw new ZError(11, 'task date not match')
|
||||
}
|
||||
let cfg = activity.tasks.find((t: TaskCfg) => t.id === taskId);
|
||||
if (!cfg) {
|
||||
throw new ZError(12, 'task not found')
|
||||
}
|
||||
if (dateTag && cfg.type !== TaskTypeEnum.DAILY) {
|
||||
throw new ZError(13, 'task is not daily task')
|
||||
}
|
||||
if (cfg.pretasks && cfg.pretasks.length > 0) {
|
||||
for (let preTask of cfg.pretasks) {
|
||||
let preTaskData = user.taskProgress.find((t: TaskStatus) => {
|
||||
if (preTask.type === TaskTypeEnum.DAILY) {
|
||||
return t.id === `${preTask}:${formatDate(new Date())}`
|
||||
}
|
||||
return t.id === preTask
|
||||
});
|
||||
if (!preTaskData || (preTaskData.status === TaskStatusEnum.NOT_START || preTaskData.status === TaskStatusEnum.RUNNING)) {
|
||||
throw new ZError(14, 'task previous not end')
|
||||
}
|
||||
}
|
||||
}
|
||||
let currentTask = user.taskProgress.find((t: TaskStatus) => t.id === task);
|
||||
if (currentTask.status === TaskStatusEnum.SUCCESS || currentTask.status === TaskStatusEnum.CLAIMED) {
|
||||
throw new ZError(15, 'task already end')
|
||||
}
|
||||
if (currentTask.status === TaskStatusEnum.NOT_START) {
|
||||
currentTask.timeStart = Date.now();
|
||||
currentTask.status = TaskStatusEnum.RUNNING;
|
||||
}
|
||||
await user.save();
|
||||
return currentTask
|
||||
}
|
||||
|
||||
@router('post /api/tasks/check_task')
|
||||
async checkTask(req) {
|
||||
let user = req.user;
|
||||
let activity = req.activity;
|
||||
let { task } = req.params;
|
||||
let { currentTask } = parseCurrentTask(user, task);
|
||||
if (currentTask.status === TaskStatusEnum.SUCCESS) {
|
||||
const user = req.user;
|
||||
const activity = req.activity;
|
||||
const { task } = req.params;
|
||||
const [taskId, dateTag] = task.split(':');
|
||||
const currentDateTag = formatDate(new Date());
|
||||
if (currentDateTag !== dateTag) {
|
||||
throw new ZError(11, 'task date not match')
|
||||
}
|
||||
let currentTask = user.taskProgress.find((t: TaskStatus) => t.id === task);
|
||||
if (!currentTask) {
|
||||
throw new ZError(11, 'task not found')
|
||||
}
|
||||
if (currentTask.status === TaskStatusEnum.SUCCESS || currentTask.status === TaskStatusEnum.CLAIMED) {
|
||||
return currentTask;
|
||||
}
|
||||
if (currentTask.status !== TaskStatusEnum.RUNNING) {
|
||||
if (currentTask.status === TaskStatusEnum.NOT_START) {
|
||||
throw new ZError(11, 'task not begin');
|
||||
}
|
||||
if (currentTask.status !== TaskStatusEnum.SUCCESS) {
|
||||
let Task = require('../tasks/' + task);
|
||||
if (currentTask.status === TaskStatusEnum.RUNNING) {
|
||||
let Task = require('../tasks/' + taskId);
|
||||
let taskInstance = new Task.default({user, activity});
|
||||
let result = await taskInstance.execute({});
|
||||
if (result) {
|
||||
await parseNextTask(user, activity, task);
|
||||
}
|
||||
await taskInstance.execute({task: currentTask});
|
||||
}
|
||||
return currentTask
|
||||
}
|
||||
|
||||
@router('post /api/tasks/claim')
|
||||
async claimTask(req) {
|
||||
const user = req.user;
|
||||
const activity = req.activity;
|
||||
const { task } = req.params;
|
||||
let currentTask = user.taskProgress.find((t: TaskStatus) => t.id === task);
|
||||
if (!currentTask) {
|
||||
throw new ZError(11, 'task not found')
|
||||
}
|
||||
if (currentTask.status === TaskStatusEnum.CLAIMED) {
|
||||
throw new ZError(12, 'task already claimed')
|
||||
}
|
||||
if (currentTask.status !== TaskStatusEnum.SUCCESS) {
|
||||
throw new ZError(13, 'task not end')
|
||||
}
|
||||
|
||||
const Task = require('../tasks/' + task);
|
||||
const taskInstance = new Task.default({user, activity});
|
||||
await taskInstance.claimReward(currentTask);
|
||||
return currentTask
|
||||
}
|
||||
|
||||
}
|
@ -4,15 +4,30 @@ import { dbconn } from 'decorators/dbconn'
|
||||
import findOrCreate from 'mongoose-findorcreate'
|
||||
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'
|
||||
import { BaseModule } from './Base'
|
||||
import exp from 'constants'
|
||||
|
||||
export enum TaskTypeEnum {
|
||||
ONCE = 1,
|
||||
DAILY = 2,
|
||||
}
|
||||
@modelOptions({ schemaOptions: { _id: false }, options: { allowMixed: Severity.ALLOW }, })
|
||||
export class TaskCfg {
|
||||
@prop()
|
||||
id: string
|
||||
@prop()
|
||||
title: string
|
||||
@prop({ enum: TaskTypeEnum, default: TaskTypeEnum.ONCE })
|
||||
type: TaskTypeEnum
|
||||
@prop()
|
||||
desc: string
|
||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||
category: any
|
||||
@prop({default: false})
|
||||
autoclaim: boolean
|
||||
@prop({ type: () => [String], default: [] })
|
||||
pretasks: string[]
|
||||
@prop()
|
||||
score: number
|
||||
@prop({default: true})
|
||||
show: boolean
|
||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||
@ -45,6 +60,27 @@ class ActivityInfoClass extends BaseModule {
|
||||
|
||||
@prop()
|
||||
public comment?: string
|
||||
|
||||
public toJson() {
|
||||
let result = super.toJson()
|
||||
let tasks = []
|
||||
for (let task of this.tasks) {
|
||||
if (task.show) {
|
||||
tasks.push({
|
||||
id: task.id,
|
||||
title: task.title,
|
||||
desc: task.desc,
|
||||
type: task.type,
|
||||
pretasks: task.pretasks,
|
||||
score: task.score,
|
||||
category: task.category,
|
||||
autoclaim: task.autoclaim,
|
||||
})
|
||||
}
|
||||
}
|
||||
result.tasks = tasks
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ export enum TaskStatusEnum {
|
||||
NOT_START = 0,
|
||||
RUNNING = 1,
|
||||
SUCCESS = 2,
|
||||
CLAIMED = 3,
|
||||
}
|
||||
|
||||
@modelOptions({ schemaOptions: { _id: false }, options: { allowMixed: Severity.ALLOW }})
|
||||
@ -22,9 +23,13 @@ export class TaskStatus {
|
||||
@prop({ enum: TaskStatusEnum, default: TaskStatusEnum.NOT_START })
|
||||
status: TaskStatusEnum
|
||||
@prop()
|
||||
dateTag: string
|
||||
@prop()
|
||||
timeStart: number
|
||||
@prop()
|
||||
timeFinish: number
|
||||
@prop()
|
||||
timeClaim: number
|
||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||
data: any
|
||||
}
|
||||
@ -73,7 +78,7 @@ class ActivityUserClass extends BaseModule {
|
||||
@prop()
|
||||
public twitterId?: string
|
||||
@prop()
|
||||
public discordId: string
|
||||
public discordId?: string
|
||||
|
||||
@prop()
|
||||
public lastLogin?: Date
|
||||
|
@ -2,9 +2,6 @@ import { Severity, getModelForClass, index, modelOptions, mongoose, prop } from
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import { BaseModule } from './Base'
|
||||
|
||||
export enum ScoreTypeEnum {
|
||||
INVITE = 1,
|
||||
}
|
||||
|
||||
@dbconn()
|
||||
@index({ user: 1 }, { unique: false })
|
||||
@ -20,8 +17,8 @@ class ScoreRecordClass extends BaseModule {
|
||||
@prop()
|
||||
public score: number
|
||||
|
||||
@prop({ enum: ScoreTypeEnum })
|
||||
public type: ScoreTypeEnum
|
||||
@prop()
|
||||
public type: string
|
||||
|
||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||
public data: any
|
||||
|
43
src/services/rank.svr.ts
Normal file
43
src/services/rank.svr.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { ScoreRecord } from "models/ScoreRecord";
|
||||
import { RedisClient } from "redis/RedisClient";
|
||||
|
||||
/**
|
||||
* 更新排行榜
|
||||
* @param param0
|
||||
* user: 用户id
|
||||
* score: 分数
|
||||
* activity: 活动id
|
||||
* scoreType: 分数类型
|
||||
* scoreParams: 额外的参数
|
||||
*/
|
||||
export const updateRankScore = async ({
|
||||
user,
|
||||
score,
|
||||
activity,
|
||||
scoreType,
|
||||
scoreParams
|
||||
}: {
|
||||
user: string,
|
||||
score: number,
|
||||
activity: string,
|
||||
scoreType: string,
|
||||
scoreParams: any
|
||||
}) => {
|
||||
let record = new ScoreRecord({
|
||||
user: user,
|
||||
activity: activity,
|
||||
score,
|
||||
type: scoreType,
|
||||
data: scoreParams
|
||||
})
|
||||
await record.save();
|
||||
const key = `${activity}:score`
|
||||
let scoreSaved = await new RedisClient().zscore(key, user) + '';
|
||||
if (scoreSaved) {
|
||||
scoreSaved = scoreSaved.substring(0, scoreSaved.indexOf('.'))
|
||||
}
|
||||
let scoreOld = parseInt(scoreSaved || '0');
|
||||
score = score + scoreOld;
|
||||
const scoreToSave = score + 1 - (Date.now() / 1000 / 10000000000)
|
||||
await new RedisClient().zadd(key, scoreToSave, user);
|
||||
}
|
@ -2,23 +2,24 @@ import { checkDiscord } from "services/oauth.svr";
|
||||
import { ITask } from "./base/ITask";
|
||||
import { ZError } from "common/ZError";
|
||||
import { TaskStatusEnum } from "models/ActivityUser";
|
||||
import { TaskCfg } from "models/ActivityInfo";
|
||||
|
||||
export default class DiscordJoin extends ITask {
|
||||
static desc = 'join discord'
|
||||
static show: boolean = true
|
||||
|
||||
async execute(data: any) {
|
||||
let { address } = this.params.user
|
||||
let res = await checkDiscord(address)
|
||||
console.log(res);
|
||||
const { address } = this.params.user
|
||||
const res = await checkDiscord(address)
|
||||
let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name)
|
||||
if (res.status !== 200) {
|
||||
throw new ZError(11, 'discord check failed')
|
||||
}
|
||||
if (res.data.errcode) {
|
||||
throw new ZError(res.data.errcode, res.data.errmsg)
|
||||
}
|
||||
let task = this.params.user.taskProgress.find(t => t.id === this.constructor.name)
|
||||
if (res.data.data.userid && task.status !== TaskStatusEnum.SUCCESS) {
|
||||
const { task } = data
|
||||
if (res.data.data.userid && task.status === TaskStatusEnum.RUNNING) {
|
||||
task.status = TaskStatusEnum.SUCCESS
|
||||
task.timeFinish = Date.now()
|
||||
task.data = res.data.data
|
||||
@ -28,7 +29,13 @@ export default class DiscordJoin extends ITask {
|
||||
} catch(err) {
|
||||
throw new ZError(100, 'discord already binded')
|
||||
}
|
||||
|
||||
if (cfg.autoclaim) {
|
||||
try {
|
||||
await this.claimReward(task);
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { ZError } from "common/ZError";
|
||||
import { ITask } from "./base/ITask";
|
||||
import { TaskStatusEnum } from "models/ActivityUser";
|
||||
import { TaskCfg } from "models/ActivityInfo";
|
||||
|
||||
export default class DiscordRole extends ITask {
|
||||
static desc = 'acquire discord role'
|
||||
static show: boolean = true
|
||||
async execute(data: any) {
|
||||
let task = this.params.user.taskProgress.find(t => t.id === this.constructor.name)
|
||||
let cfg = this.params.activity.tasks.find(t => t.id === this.constructor.name)
|
||||
const { task } = data
|
||||
let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name)
|
||||
let time = cfg.params.time;
|
||||
if (Date.now() - task.timeStart < time * 1000) {
|
||||
throw new ZError(11, 'check discord role failed')
|
||||
@ -19,7 +20,18 @@ export default class DiscordRole extends ITask {
|
||||
task.status = TaskStatusEnum.SUCCESS
|
||||
task.timeFinish = Date.now()
|
||||
task.data = {}
|
||||
await this.params.user.save()
|
||||
try {
|
||||
await this.params.user.save()
|
||||
} catch(err) {
|
||||
throw new ZError(100, 'already acquired discord role')
|
||||
}
|
||||
if (cfg.autoclaim) {
|
||||
try {
|
||||
await this.claimReward(task);
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
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";
|
||||
import { updateRankScore } from "services/rank.svr";
|
||||
|
||||
const updateInviteScore = async (user: typeof ActivityUser, scores: number[], level: number) => {
|
||||
const updateInviteScore = async (user: typeof ActivityUser, scores: number[], level: number, reason: string) => {
|
||||
if (!user.inviteUser || scores.length <= level) {
|
||||
return;
|
||||
}
|
||||
@ -12,27 +11,23 @@ const updateInviteScore = async (user: typeof ActivityUser, scores: number[], le
|
||||
if (!userSup) {
|
||||
return;
|
||||
}
|
||||
let record = new ScoreRecord({
|
||||
await updateRankScore({
|
||||
user: userSup.id,
|
||||
activity: user.activity,
|
||||
score: scores[level],
|
||||
type: ScoreTypeEnum.INVITE,
|
||||
data: {
|
||||
activity: user.activity,
|
||||
scoreType: reason,
|
||||
scoreParams: {
|
||||
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)
|
||||
await updateInviteScore(userSup, scores, level + 1, reason)
|
||||
}
|
||||
|
||||
export default class UpdateScore extends ITask {
|
||||
export default class ShareCode extends ITask {
|
||||
static desc = 'update invite score'
|
||||
static show: boolean = false
|
||||
static auto: boolean = true
|
||||
|
||||
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.id === this.constructor.name)
|
||||
@ -42,8 +37,19 @@ export default class UpdateScore extends ITask {
|
||||
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)
|
||||
if (cfg.autoclaim) {
|
||||
try {
|
||||
await updateInviteScore(this.params.user, scores, 0, this.constructor.name)
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public async claimReward(task: TaskStatus) {
|
||||
let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name)
|
||||
let scores = cfg.params.score;
|
||||
await updateInviteScore(this.params.user, scores, 0, this.constructor.name)
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import { checkTwitter } from "services/oauth.svr";
|
||||
import { ITask } from "./base/ITask";
|
||||
import { ActivityUser, TaskStatusEnum } from "models/ActivityUser";
|
||||
import { ZError } from "common/ZError";
|
||||
import { TaskCfg } from "models/ActivityInfo";
|
||||
|
||||
export default class TwitterConnect extends ITask {
|
||||
static desc = 'twitter connect'
|
||||
@ -10,15 +11,14 @@ export default class TwitterConnect extends ITask {
|
||||
async execute(data: any) {
|
||||
let { address } = this.params.user
|
||||
let res = await checkTwitter(address)
|
||||
console.log(res);
|
||||
if (res.status !== 200) {
|
||||
throw new ZError(11, 'twitter check failed')
|
||||
}
|
||||
if (res.data.errcode) {
|
||||
throw new ZError(res.data.errcode, res.data.errmsg)
|
||||
}
|
||||
let task = this.params.user.taskProgress.find(t => t.id === this.constructor.name)
|
||||
if (res.data.data.userid && task.status !== TaskStatusEnum.SUCCESS) {
|
||||
const { task } = data
|
||||
if (res.data.data.userid && task.status === TaskStatusEnum.RUNNING) {
|
||||
task.status = TaskStatusEnum.SUCCESS
|
||||
task.timeFinish = Date.now()
|
||||
task.data = res.data.data
|
||||
@ -28,6 +28,14 @@ export default class TwitterConnect extends ITask {
|
||||
} catch(err) {
|
||||
throw new ZError(100, 'twitter already binded')
|
||||
}
|
||||
let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name)
|
||||
if (cfg.autoclaim) {
|
||||
try {
|
||||
await this.claimReward(task);
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return true
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { ITask } from "./base/ITask";
|
||||
import { TaskStatusEnum } from "models/ActivityUser";
|
||||
import { ZError } from "common/ZError";
|
||||
import { TaskCfg } from "models/ActivityInfo";
|
||||
|
||||
export default class TwitterFollow extends ITask {
|
||||
static desc = 'twitter follow'
|
||||
static show: boolean = true
|
||||
async execute(data: any) {
|
||||
let task = this.params.user.taskProgress.find(t => t.id === this.constructor.name)
|
||||
let cfg = this.params.activity.tasks.find(t => t.id === this.constructor.name)
|
||||
const { task } = data
|
||||
let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name)
|
||||
let time = cfg.params.time;
|
||||
if (Date.now() - task.timeStart < time * 1000) {
|
||||
throw new ZError(11, 'follow failed')
|
||||
@ -19,7 +20,18 @@ export default class TwitterFollow extends ITask {
|
||||
task.status = TaskStatusEnum.SUCCESS
|
||||
task.timeFinish = Date.now()
|
||||
task.data = {}
|
||||
await this.params.user.save()
|
||||
try {
|
||||
await this.params.user.save()
|
||||
} catch(err) {
|
||||
throw new ZError(100, 'save failed')
|
||||
}
|
||||
if (cfg.autoclaim) {
|
||||
try {
|
||||
await this.claimReward(task);
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ export default class TwitterRetweet extends ITask {
|
||||
static desc = 'twitter retweet'
|
||||
static show: boolean = true
|
||||
async execute(data: any) {
|
||||
let task = this.params.user.taskProgress.find(t => t.id === this.constructor.name)
|
||||
const { task } = data
|
||||
let cfg = this.params.activity.tasks.find(t => t.id === this.constructor.name)
|
||||
let time = cfg.params.time;
|
||||
if (Date.now() - task.timeStart < time * 1000) {
|
||||
@ -19,7 +19,18 @@ export default class TwitterRetweet extends ITask {
|
||||
task.status = TaskStatusEnum.SUCCESS
|
||||
task.timeFinish = Date.now()
|
||||
task.data = {}
|
||||
await this.params.user.save()
|
||||
try {
|
||||
await this.params.user.save()
|
||||
} catch(err) {
|
||||
throw new ZError(100, 'save failed')
|
||||
}
|
||||
if (cfg.autoclaim) {
|
||||
try {
|
||||
await this.claimReward(task);
|
||||
} catch(err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,35 @@
|
||||
import { TaskCfg } from "models/ActivityInfo"
|
||||
import { TaskStatus, TaskStatusEnum } from "models/ActivityUser"
|
||||
import { updateRankScore } from "services/rank.svr"
|
||||
|
||||
export abstract class ITask {
|
||||
static desc: string
|
||||
static show: boolean = true
|
||||
static auto: boolean = false
|
||||
|
||||
params: any
|
||||
constructor(params: any) {
|
||||
// do nothing
|
||||
this.params = params
|
||||
}
|
||||
abstract execute(data: any): Promise<boolean>
|
||||
|
||||
public async claimReward(task: TaskStatus) {
|
||||
const user = this.params.user
|
||||
const [taskId, dateTag] = task.id.split(':');
|
||||
const cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === taskId)
|
||||
await updateRankScore({
|
||||
user: user.id,
|
||||
score: cfg.score,
|
||||
activity: user.activity,
|
||||
scoreType: taskId,
|
||||
scoreParams: {
|
||||
date: dateTag,
|
||||
taskId: task.id
|
||||
}
|
||||
})
|
||||
task.status = TaskStatusEnum.CLAIMED
|
||||
task.timeClaim = Date.now()
|
||||
await user.save()
|
||||
}
|
||||
}
|
7
src/utils/date.util.ts
Normal file
7
src/utils/date.util.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// format the date to the format we want
|
||||
export const formatDate = (date: Date): string => {
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
return `${year}${month}${day}`;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user