修改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",
|
"id": "任务id",
|
||||||
"title": "任务名",
|
"title": "任务名",
|
||||||
"desc": "任务描述"
|
"desc": "任务描述",
|
||||||
|
"type": 1, //任务类型, 1: 一次性任务, 2: 日常任务
|
||||||
|
"pretasks": ["task id 1"], //前置任务
|
||||||
|
"score": 0, // 完成任务可获得的积分
|
||||||
|
"category": "", // 任务分类
|
||||||
|
"autoclaim": false // 任务完成后是否自动获取奖励
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"startTime": 1702628292366, // 活动开始时间
|
"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
|
#### Request
|
||||||
|
|
||||||
@ -213,7 +247,7 @@ body:
|
|||||||
{}
|
{}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7. 积分排行榜
|
### 9. 积分排行榜
|
||||||
|
|
||||||
#### Request
|
#### Request
|
||||||
|
|
||||||
|
@ -6,16 +6,35 @@
|
|||||||
"tasks": [{
|
"tasks": [{
|
||||||
"id": "TwitterConnect",
|
"id": "TwitterConnect",
|
||||||
"title": "Connect Twitter",
|
"title": "Connect Twitter",
|
||||||
|
"type": 1,
|
||||||
"desc": "",
|
"desc": "",
|
||||||
"params": {}
|
"category": "",
|
||||||
|
"autoclaim": true,
|
||||||
|
"params": {"score": 100}
|
||||||
}, {
|
}, {
|
||||||
"id": "TwitterFollow",
|
"id": "TwitterFollow",
|
||||||
"title": "Follow Twitter",
|
"title": "Follow Twitter",
|
||||||
|
"type": 1,
|
||||||
"desc": "",
|
"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",
|
"id": "UpdateScore",
|
||||||
|
"type": 1,
|
||||||
"show": false,
|
"show": false,
|
||||||
|
"autoclaim": false,
|
||||||
|
"pretasks": ["TwitterConnect", "TwitterFollow"],
|
||||||
"params": {"score": [100, 20]}
|
"params": {"score": [100, 20]}
|
||||||
}],
|
}],
|
||||||
"startTime": 1702628292366,
|
"startTime": 1702628292366,
|
||||||
|
@ -19,15 +19,7 @@ export default class ActivityController extends BaseController {
|
|||||||
if (!activity) {
|
if (!activity) {
|
||||||
throw new ZError(12, 'activity not found')
|
throw new ZError(12, 'activity not found')
|
||||||
}
|
}
|
||||||
let tasks = []
|
return activity.toJson()
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { ZError } from "common/ZError";
|
import { ZError } from "common/ZError";
|
||||||
import BaseController, { ROLE_ANON } from "common/base.controller";
|
import BaseController, { ROLE_ANON } from "common/base.controller";
|
||||||
import { role, router } from "decorators/router";
|
import { router } from "decorators/router";
|
||||||
import { all } from "deepmerge";
|
import { ActivityInfo, TaskCfg, TaskTypeEnum } from "models/ActivityInfo";
|
||||||
import { ActivityInfo, TaskCfg } from "models/ActivityInfo";
|
|
||||||
import { ActivityUser, TaskStatus, TaskStatusEnum } from "models/ActivityUser";
|
import { ActivityUser, TaskStatus, TaskStatusEnum } from "models/ActivityUser";
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import { formatDate } from "utils/date.util";
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
const prod = process.env.NODE_ENV === 'production'
|
const prod = process.env.NODE_ENV === 'production'
|
||||||
@ -25,82 +25,46 @@ 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) => {
|
|
||||||
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 {
|
export default class TasksController extends BaseController {
|
||||||
|
|
||||||
@router('post /api/tasks/progress')
|
@router('post /api/tasks/progress')
|
||||||
async taskProgress(req) {
|
async taskProgress(req) {
|
||||||
let user = req.user;
|
let user = req.user;
|
||||||
let activity = req.activity;
|
let activity = req.activity;
|
||||||
if (!user.taskProgress || user.taskProgress.length === 0) {
|
const dateTag = formatDate(new Date());
|
||||||
for (let task of activity.tasks) {
|
let taskAddedSet = new Set();
|
||||||
if (allTasks.has(task.id)) {
|
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})
|
user.taskProgress.push({id: task.id, status: TaskStatusEnum.NOT_START})
|
||||||
}
|
}
|
||||||
|
if (task.show) visibleTasks.add(task.id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (modified) {
|
||||||
await user.save();
|
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));
|
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')
|
@router('post /api/tasks/begin_task')
|
||||||
async beginTask(req) {
|
async beginTask(req) {
|
||||||
let user = req.user;
|
let user = req.user;
|
||||||
|
let activity = req.activity;
|
||||||
let { task } = req.params;
|
let { task } = req.params;
|
||||||
let { currentTask } = parseCurrentTask(user, task);
|
const [taskId, dateTag] = task.split(':');
|
||||||
currentTask.timeStart = Date.now();
|
const currentDateTag = formatDate(new Date());
|
||||||
currentTask.status = 1;
|
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();
|
await user.save();
|
||||||
return currentTask
|
return currentTask
|
||||||
}
|
}
|
||||||
|
|
||||||
@router('post /api/tasks/check_task')
|
@router('post /api/tasks/check_task')
|
||||||
async checkTask(req) {
|
async checkTask(req) {
|
||||||
let user = req.user;
|
const user = req.user;
|
||||||
let activity = req.activity;
|
const activity = req.activity;
|
||||||
let { task } = req.params;
|
const { task } = req.params;
|
||||||
let { currentTask } = parseCurrentTask(user, task);
|
const [taskId, dateTag] = task.split(':');
|
||||||
if (currentTask.status === TaskStatusEnum.SUCCESS) {
|
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;
|
return currentTask;
|
||||||
}
|
}
|
||||||
if (currentTask.status !== TaskStatusEnum.RUNNING) {
|
if (currentTask.status === TaskStatusEnum.NOT_START) {
|
||||||
throw new ZError(11, 'task not begin');
|
throw new ZError(11, 'task not begin');
|
||||||
}
|
}
|
||||||
if (currentTask.status !== TaskStatusEnum.SUCCESS) {
|
if (currentTask.status === TaskStatusEnum.RUNNING) {
|
||||||
let Task = require('../tasks/' + task);
|
let Task = require('../tasks/' + taskId);
|
||||||
let taskInstance = new Task.default({user, activity});
|
let taskInstance = new Task.default({user, activity});
|
||||||
let result = await taskInstance.execute({});
|
await taskInstance.execute({task: currentTask});
|
||||||
if (result) {
|
|
||||||
await parseNextTask(user, activity, task);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 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 findOrCreate from 'mongoose-findorcreate'
|
||||||
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'
|
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'
|
||||||
import { BaseModule } from './Base'
|
import { BaseModule } from './Base'
|
||||||
|
import exp from 'constants'
|
||||||
|
|
||||||
|
export enum TaskTypeEnum {
|
||||||
|
ONCE = 1,
|
||||||
|
DAILY = 2,
|
||||||
|
}
|
||||||
@modelOptions({ schemaOptions: { _id: false }, options: { allowMixed: Severity.ALLOW }, })
|
@modelOptions({ schemaOptions: { _id: false }, options: { allowMixed: Severity.ALLOW }, })
|
||||||
export class TaskCfg {
|
export class TaskCfg {
|
||||||
@prop()
|
@prop()
|
||||||
id: string
|
id: string
|
||||||
@prop()
|
@prop()
|
||||||
title: string
|
title: string
|
||||||
|
@prop({ enum: TaskTypeEnum, default: TaskTypeEnum.ONCE })
|
||||||
|
type: TaskTypeEnum
|
||||||
@prop()
|
@prop()
|
||||||
desc: string
|
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})
|
@prop({default: true})
|
||||||
show: boolean
|
show: boolean
|
||||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||||
@ -46,6 +61,27 @@ class ActivityInfoClass extends BaseModule {
|
|||||||
@prop()
|
@prop()
|
||||||
public comment?: string
|
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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActivityInfo = getModelForClass(ActivityInfoClass, { existingConnection: ActivityInfoClass.db })
|
export const ActivityInfo = getModelForClass(ActivityInfoClass, { existingConnection: ActivityInfoClass.db })
|
||||||
|
@ -12,6 +12,7 @@ export enum TaskStatusEnum {
|
|||||||
NOT_START = 0,
|
NOT_START = 0,
|
||||||
RUNNING = 1,
|
RUNNING = 1,
|
||||||
SUCCESS = 2,
|
SUCCESS = 2,
|
||||||
|
CLAIMED = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
@modelOptions({ schemaOptions: { _id: false }, options: { allowMixed: Severity.ALLOW }})
|
@modelOptions({ schemaOptions: { _id: false }, options: { allowMixed: Severity.ALLOW }})
|
||||||
@ -22,9 +23,13 @@ export class TaskStatus {
|
|||||||
@prop({ enum: TaskStatusEnum, default: TaskStatusEnum.NOT_START })
|
@prop({ enum: TaskStatusEnum, default: TaskStatusEnum.NOT_START })
|
||||||
status: TaskStatusEnum
|
status: TaskStatusEnum
|
||||||
@prop()
|
@prop()
|
||||||
|
dateTag: string
|
||||||
|
@prop()
|
||||||
timeStart: number
|
timeStart: number
|
||||||
@prop()
|
@prop()
|
||||||
timeFinish: number
|
timeFinish: number
|
||||||
|
@prop()
|
||||||
|
timeClaim: number
|
||||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||||
data: any
|
data: any
|
||||||
}
|
}
|
||||||
@ -73,7 +78,7 @@ class ActivityUserClass extends BaseModule {
|
|||||||
@prop()
|
@prop()
|
||||||
public twitterId?: string
|
public twitterId?: string
|
||||||
@prop()
|
@prop()
|
||||||
public discordId: string
|
public discordId?: string
|
||||||
|
|
||||||
@prop()
|
@prop()
|
||||||
public lastLogin?: Date
|
public lastLogin?: Date
|
||||||
|
@ -2,9 +2,6 @@ import { Severity, getModelForClass, index, modelOptions, mongoose, prop } from
|
|||||||
import { dbconn } from 'decorators/dbconn'
|
import { dbconn } from 'decorators/dbconn'
|
||||||
import { BaseModule } from './Base'
|
import { BaseModule } from './Base'
|
||||||
|
|
||||||
export enum ScoreTypeEnum {
|
|
||||||
INVITE = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
@dbconn()
|
@dbconn()
|
||||||
@index({ user: 1 }, { unique: false })
|
@index({ user: 1 }, { unique: false })
|
||||||
@ -20,8 +17,8 @@ class ScoreRecordClass extends BaseModule {
|
|||||||
@prop()
|
@prop()
|
||||||
public score: number
|
public score: number
|
||||||
|
|
||||||
@prop({ enum: ScoreTypeEnum })
|
@prop()
|
||||||
public type: ScoreTypeEnum
|
public type: string
|
||||||
|
|
||||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||||
public data: any
|
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 { ITask } from "./base/ITask";
|
||||||
import { ZError } from "common/ZError";
|
import { ZError } from "common/ZError";
|
||||||
import { TaskStatusEnum } from "models/ActivityUser";
|
import { TaskStatusEnum } from "models/ActivityUser";
|
||||||
|
import { TaskCfg } from "models/ActivityInfo";
|
||||||
|
|
||||||
export default class DiscordJoin extends ITask {
|
export default class DiscordJoin extends ITask {
|
||||||
static desc = 'join discord'
|
static desc = 'join discord'
|
||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
|
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
let { address } = this.params.user
|
const { address } = this.params.user
|
||||||
let res = await checkDiscord(address)
|
const res = await checkDiscord(address)
|
||||||
console.log(res);
|
let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name)
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new ZError(11, 'discord check failed')
|
throw new ZError(11, 'discord check failed')
|
||||||
}
|
}
|
||||||
if (res.data.errcode) {
|
if (res.data.errcode) {
|
||||||
throw new ZError(res.data.errcode, res.data.errmsg)
|
throw new ZError(res.data.errcode, res.data.errmsg)
|
||||||
}
|
}
|
||||||
let task = this.params.user.taskProgress.find(t => t.id === this.constructor.name)
|
const { task } = data
|
||||||
if (res.data.data.userid && task.status !== TaskStatusEnum.SUCCESS) {
|
if (res.data.data.userid && task.status === TaskStatusEnum.RUNNING) {
|
||||||
task.status = TaskStatusEnum.SUCCESS
|
task.status = TaskStatusEnum.SUCCESS
|
||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = res.data.data
|
task.data = res.data.data
|
||||||
@ -28,7 +29,13 @@ export default class DiscordJoin extends ITask {
|
|||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'discord already binded')
|
throw new ZError(100, 'discord already binded')
|
||||||
}
|
}
|
||||||
|
if (cfg.autoclaim) {
|
||||||
|
try {
|
||||||
|
await this.claimReward(task);
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { ZError } from "common/ZError";
|
import { ZError } from "common/ZError";
|
||||||
import { ITask } from "./base/ITask";
|
import { ITask } from "./base/ITask";
|
||||||
import { TaskStatusEnum } from "models/ActivityUser";
|
import { TaskStatusEnum } from "models/ActivityUser";
|
||||||
|
import { TaskCfg } from "models/ActivityInfo";
|
||||||
|
|
||||||
export default class DiscordRole extends ITask {
|
export default class DiscordRole extends ITask {
|
||||||
static desc = 'acquire discord role'
|
static desc = 'acquire discord role'
|
||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
async execute(data: any) {
|
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 cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name)
|
||||||
let time = cfg.params.time;
|
let time = cfg.params.time;
|
||||||
if (Date.now() - task.timeStart < time * 1000) {
|
if (Date.now() - task.timeStart < time * 1000) {
|
||||||
throw new ZError(11, 'check discord role failed')
|
throw new ZError(11, 'check discord role failed')
|
||||||
@ -19,7 +20,18 @@ export default class DiscordRole extends ITask {
|
|||||||
task.status = TaskStatusEnum.SUCCESS
|
task.status = TaskStatusEnum.SUCCESS
|
||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = {}
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { ActivityUser, TaskStatus, TaskStatusEnum } 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 { updateRankScore } from "services/rank.svr";
|
||||||
import { RedisClient } from "redis/RedisClient";
|
|
||||||
|
|
||||||
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) {
|
if (!user.inviteUser || scores.length <= level) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -12,27 +11,23 @@ const updateInviteScore = async (user: typeof ActivityUser, scores: number[], le
|
|||||||
if (!userSup) {
|
if (!userSup) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let record = new ScoreRecord({
|
await updateRankScore({
|
||||||
user: userSup.id,
|
user: userSup.id,
|
||||||
activity: user.activity,
|
|
||||||
score: scores[level],
|
score: scores[level],
|
||||||
type: ScoreTypeEnum.INVITE,
|
activity: user.activity,
|
||||||
data: {
|
scoreType: reason,
|
||||||
|
scoreParams: {
|
||||||
user: user.id,
|
user: user.id,
|
||||||
level
|
level
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await record.save();
|
await updateInviteScore(userSup, scores, level + 1, reason)
|
||||||
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 ShareCode extends ITask {
|
||||||
static desc = 'update invite score'
|
static desc = 'update invite score'
|
||||||
static show: boolean = false
|
static show: boolean = false
|
||||||
static auto: boolean = true
|
|
||||||
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.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 = {}
|
task.data = {}
|
||||||
await this.params.user.save();
|
await this.params.user.save();
|
||||||
// According to configuration, add score to user who invite current user
|
// 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
|
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 { ITask } from "./base/ITask";
|
||||||
import { ActivityUser, TaskStatusEnum } from "models/ActivityUser";
|
import { ActivityUser, TaskStatusEnum } from "models/ActivityUser";
|
||||||
import { ZError } from "common/ZError";
|
import { ZError } from "common/ZError";
|
||||||
|
import { TaskCfg } from "models/ActivityInfo";
|
||||||
|
|
||||||
export default class TwitterConnect extends ITask {
|
export default class TwitterConnect extends ITask {
|
||||||
static desc = 'twitter connect'
|
static desc = 'twitter connect'
|
||||||
@ -10,15 +11,14 @@ export default class TwitterConnect extends ITask {
|
|||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
let { address } = this.params.user
|
let { address } = this.params.user
|
||||||
let res = await checkTwitter(address)
|
let res = await checkTwitter(address)
|
||||||
console.log(res);
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new ZError(11, 'twitter check failed')
|
throw new ZError(11, 'twitter check failed')
|
||||||
}
|
}
|
||||||
if (res.data.errcode) {
|
if (res.data.errcode) {
|
||||||
throw new ZError(res.data.errcode, res.data.errmsg)
|
throw new ZError(res.data.errcode, res.data.errmsg)
|
||||||
}
|
}
|
||||||
let task = this.params.user.taskProgress.find(t => t.id === this.constructor.name)
|
const { task } = data
|
||||||
if (res.data.data.userid && task.status !== TaskStatusEnum.SUCCESS) {
|
if (res.data.data.userid && task.status === TaskStatusEnum.RUNNING) {
|
||||||
task.status = TaskStatusEnum.SUCCESS
|
task.status = TaskStatusEnum.SUCCESS
|
||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = res.data.data
|
task.data = res.data.data
|
||||||
@ -28,6 +28,14 @@ export default class TwitterConnect extends ITask {
|
|||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'twitter already binded')
|
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
|
return true
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { ITask } from "./base/ITask";
|
import { ITask } from "./base/ITask";
|
||||||
import { TaskStatusEnum } from "models/ActivityUser";
|
import { TaskStatusEnum } from "models/ActivityUser";
|
||||||
import { ZError } from "common/ZError";
|
import { ZError } from "common/ZError";
|
||||||
|
import { TaskCfg } from "models/ActivityInfo";
|
||||||
|
|
||||||
export default class TwitterFollow extends ITask {
|
export default class TwitterFollow extends ITask {
|
||||||
static desc = 'twitter follow'
|
static desc = 'twitter follow'
|
||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
async execute(data: any) {
|
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 cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name)
|
||||||
let time = cfg.params.time;
|
let time = cfg.params.time;
|
||||||
if (Date.now() - task.timeStart < time * 1000) {
|
if (Date.now() - task.timeStart < time * 1000) {
|
||||||
throw new ZError(11, 'follow failed')
|
throw new ZError(11, 'follow failed')
|
||||||
@ -19,7 +20,18 @@ export default class TwitterFollow extends ITask {
|
|||||||
task.status = TaskStatusEnum.SUCCESS
|
task.status = TaskStatusEnum.SUCCESS
|
||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = {}
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ export default class TwitterRetweet extends ITask {
|
|||||||
static desc = 'twitter retweet'
|
static desc = 'twitter retweet'
|
||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
async execute(data: any) {
|
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 cfg = this.params.activity.tasks.find(t => t.id === this.constructor.name)
|
||||||
let time = cfg.params.time;
|
let time = cfg.params.time;
|
||||||
if (Date.now() - task.timeStart < time * 1000) {
|
if (Date.now() - task.timeStart < time * 1000) {
|
||||||
@ -19,7 +19,18 @@ export default class TwitterRetweet extends ITask {
|
|||||||
task.status = TaskStatusEnum.SUCCESS
|
task.status = TaskStatusEnum.SUCCESS
|
||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = {}
|
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
|
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 {
|
export abstract class ITask {
|
||||||
static desc: string
|
static desc: string
|
||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
static auto: boolean = false
|
static auto: boolean = false
|
||||||
|
|
||||||
params: any
|
params: any
|
||||||
constructor(params: any) {
|
constructor(params: any) {
|
||||||
// do nothing
|
// do nothing
|
||||||
this.params = params
|
this.params = params
|
||||||
}
|
}
|
||||||
abstract execute(data: any): Promise<boolean>
|
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