import { ZError, SyncLocker, BaseController, router } from 'zutils' import { TaskCfg, TaskTypeEnum } from 'models/ActivityInfo' import { TaskStatus, TaskStatusEnum } from 'models/ActivityUser' import { join } from 'path' import { formatDate } from 'utils/utcdate.util' import { GeneralScription } from 'models/chain/GeneralScription' import { BASE_TASK_TICKET } from 'common/Constants' import { BASE_TASK_REWARD, SIGN_SEQ, TicketRecord } from 'models/TicketRecord' import { ActivityGame } from 'models/ActivityGame' import { formatNumShow } from 'common/Utils' import { fetchClaimStatus } from 'services/chain.svr' import logger from 'logger/logger' const fs = require('fs') const prod = process.env.NODE_ENV === 'production' const initTasks = () => { const tasks = join(__dirname, '../tasks') const list = new Map() fs.readdirSync(tasks) .filter((file: string) => ~file.search(/^[^.].*\.(ts|js)$/)) .forEach((file: any) => { const Task = require('../tasks/' + file) list.set(Task.default.name, { name: Task.default.name, desc: Task.default.desc, show: Task.default.show, }) }) return list } const allTasks = initTasks() export default class TasksController extends BaseController { @router('post /api/tasks/progress') async taskProgress(req) { let user = req.user let activity = req.activity 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.task)) { continue } if (!task.isVaild()) { continue } if (task.type === TaskTypeEnum.DAILY) { let id = `${task.id}:${dateTag}` if (!taskAddedSet.has(id)) { modified = true user.taskProgress.push({ id, task: task.task, 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, task: task.task, status: TaskStatusEnum.NOT_START }) } if (task.show) visibleTasks.add(task.id) } } if (modified) { await user.save() } return user.taskProgress.filter((t: TaskStatus) => visibleTasks.has(t.id)) } @router('post /api/tasks/begin_task') async beginTask(req) { new SyncLocker().checkLock(req) logger.db('begin_task', req) let user = req.user let activity = req.activity let { task } = req.params const [taskId, dateTag] = task.split(':') const currentDateTag = formatDate(new Date()) if (dateTag && currentDateTag !== dateTag) { throw new ZError(11, 'task date not match') } if (!activity.isVaild()) { throw new ZError(15, 'activity not start or end') } let cfg = activity.tasks.find((t: TaskCfg) => t.id === taskId) if (!cfg) { throw new ZError(12, 'task not found') } if (!cfg.isVaild()) { throw new ZError(16, 'task not start or end') } 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.indexOf(preTask) > -1 } return t.id === preTask }) if ( !preTaskData || preTaskData.status === TaskStatusEnum.NOT_START || preTaskData.status === TaskStatusEnum.RUNNING ) { throw new ZError(14, 'task previous not finish') } } } let currentTask = user.taskProgress.find((t: TaskStatus) => t.id === task) if ( currentTask.status === TaskStatusEnum.PART_SUCCESS || 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) { const user = req.user logger.db('chect_task', req) const activity = req.activity const { task } = req.params const [taskId, dateTag] = task.split(':') const currentDateTag = formatDate(new Date()) if (dateTag && currentDateTag !== dateTag) { throw new ZError(11, 'task date not match') } if (!activity?.isVaild()) { throw new ZError(15, 'activity not start or end') } let cfg = activity.tasks.find((t: TaskCfg) => t.id === taskId) if (!cfg) { throw new ZError(12, 'task not found') } if (!cfg.isVaild()) { throw new ZError(16, 'task not start or end') } let currentTask = user.taskProgress.find((t: TaskStatus) => t.id === task) if (!currentTask) { throw new ZError(11, 'task not found') } if (currentTask.status === TaskStatusEnum.CLAIMED) { return currentTask } if (currentTask.status === TaskStatusEnum.NOT_START) { throw new ZError(11, 'task not begin') } if (currentTask.status === TaskStatusEnum.RUNNING || currentTask.status === TaskStatusEnum.PART_SUCCESS) { let Task = require('../tasks/' + currentTask.task) let taskInstance = new Task.default({ user, activity }) await taskInstance.execute({ task: currentTask }) } return currentTask } @router('post /api/tasks/claim') async claimTask(req) { new SyncLocker().checkLock(req) logger.db('claim_task', req) const user = req.user const activity = req.activity const { task } = req.params const [taskId, dateTag] = task.split(':') if (!activity?.isVaild()) { throw new ZError(15, 'activity not start or end') } let cfg = activity.tasks.find((t: TaskCfg) => t.id === taskId) if (!cfg) { throw new ZError(14, 'task not found') } if (!cfg.isVaild()) { throw new ZError(16, 'task not start or end') } 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 && currentTask.status !== TaskStatusEnum.PART_SUCCESS) { throw new ZError(13, 'task not end') } if (cfg.checkChain) { const chainRecord = await GeneralScription.findOne({ from: user.address.toLowerCase(), op: 'task_claim', data: task, }) if (!chainRecord) { throw new ZError(14, 'waiting for chain confirm') } // let result = await fetchClaimStatus(user.address.toLowerCase(), task) // if (!result) { // throw new ZError(15, 'waiting for chain confirm') // } } const Task = require('../tasks/' + currentTask.task) const taskInstance = new Task.default({ user, activity }) const { score } = await taskInstance.claimReward(currentTask) const baseTaskSet = new Set(activity.baseTasks) let count = 0 for (let task of user.taskProgress) { if (baseTaskSet.has(task.id) && task.status === TaskStatusEnum.CLAIMED) { count++ } } let ticket = 0 if (count === baseTaskSet.size && baseTaskSet.has(taskId)) { // all base task finished, give ticket ticket = BASE_TASK_TICKET const ticketRecord = new TicketRecord({ user: user.id, activity: user.activity, type: BASE_TASK_REWARD, data: { task: currentTask.task }, score: ticket, }) await ActivityGame.updateOne({ user: user.id, activity: user.activity }, { $inc: { tickets: ticket } }) await ticketRecord.save() } return { status: currentTask.status, score: formatNumShow(score), ticket } } }