增加uaw相关接口
This commit is contained in:
parent
010ada3bd4
commit
6ff402e670
46
configs/uaw_daily_sign.json
Normal file
46
configs/uaw_daily_sign.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"total_sign": [
|
||||
{
|
||||
"days": 3,
|
||||
"reward": 1
|
||||
},
|
||||
{
|
||||
"days": 4,
|
||||
"reward": 4
|
||||
},
|
||||
{
|
||||
"days": 5,
|
||||
"reward": 5
|
||||
},
|
||||
{
|
||||
"days": 6,
|
||||
"reward": 6
|
||||
},
|
||||
{
|
||||
"days": 7,
|
||||
"reward": 7
|
||||
}
|
||||
],
|
||||
"sequential_sign": [
|
||||
{
|
||||
"days": 3,
|
||||
"reward": 1
|
||||
},
|
||||
{
|
||||
"days": 4,
|
||||
"reward": 1
|
||||
},
|
||||
{
|
||||
"days": 5,
|
||||
"reward": 1
|
||||
},
|
||||
{
|
||||
"days": 6,
|
||||
"reward": 1
|
||||
},
|
||||
{
|
||||
"days": 7,
|
||||
"reward": 1
|
||||
}
|
||||
]
|
||||
}
|
26
configs/uaw_rank_level.json
Normal file
26
configs/uaw_rank_level.json
Normal file
@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"score": 600,
|
||||
"level": "Challenger"
|
||||
},
|
||||
{
|
||||
"score": 500,
|
||||
"level": "Diamond"
|
||||
},
|
||||
{
|
||||
"score": 400,
|
||||
"level": "Platnum"
|
||||
},
|
||||
{
|
||||
"score": 100,
|
||||
"level": "Bronze"
|
||||
},
|
||||
{
|
||||
"score": 300,
|
||||
"level": "Gold"
|
||||
},
|
||||
{
|
||||
"score": 0,
|
||||
"level": "Silver"
|
||||
}
|
||||
]
|
629
docs/uaw.md
Normal file
629
docs/uaw.md
Normal file
@ -0,0 +1,629 @@
|
||||
# UAW相关接口
|
||||
|
||||
### 说明
|
||||
|
||||
1. 通用返回格式, errcode=0 表示无错误
|
||||
|
||||
2. 如无特别说明, 以下接口的 Response 格式指的是 data 字段
|
||||
3. 接口名中带\*的表示, 需要验证 token, token 可以设置 header 的 Authorization: Bearer JWT_token, 或 Post body 的 token 字段, 或 Get 的 query token
|
||||
|
||||
```json
|
||||
{
|
||||
"errcode": Number,
|
||||
"errmsg": String,
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 1. 钱包预登录
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/wallet/nonce?address=`
|
||||
- 方法:`GET`
|
||||
|
||||
query param
|
||||
|
||||
| Name | Type | Desc |
|
||||
| ------- | ------ | -------- |
|
||||
| address | string | 钱包地址 |
|
||||
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"nonce": String,
|
||||
"tips": String
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 钱包登录
|
||||
|
||||
客户端在获得钱包地址后,须先调用预登录方法获取 nonce 和 tips, 然后调用钱包进行 EIP-721 签名.
|
||||
|
||||
登录成功后返回的 jwt 需要保存至本地存储, 再次载入后, 可解析并获取 exp 字段, 判断当前 token 是否已经过期
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/wallet/login`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Content-type: application/json
|
||||
- Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"activity": String,
|
||||
"signature": String,
|
||||
"message": SiweMessage
|
||||
}
|
||||
```
|
||||
|
||||
SiweMessage说明: https://docs.login.xyz/sign-in-with-ethereum/quickstart-guide/creating-siwe-messages
|
||||
|
||||
SiweMessage的nonce说明(具体参考例子):
|
||||
|
||||
```
|
||||
1. 从钱包预登录接口获取nonce
|
||||
2. nonce = nonce + '|' + 钱包类型字符串 // 比如okx钱包, nonce|okx
|
||||
3. 使用 活动id 作为 key, 调用aesEncrypt 加密步骤2的字符串
|
||||
4. 将hex string 转换成base58, 传给SiweMessage
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"token": String,
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 社交任务活动信息
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/activity/:id`
|
||||
- 方法:`GET`
|
||||
- 参数:
|
||||
- `id`(当前活动的ID)
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
"_id": "TwitterConnect", // 任务id
|
||||
"name": "活动名称",
|
||||
"description": "活动描述",
|
||||
"tasks": [ // 该活动需要完成的任务
|
||||
{
|
||||
"id": "任务id",
|
||||
"task": "任务类型",
|
||||
"title": "任务名",
|
||||
"desc": "任务描述",
|
||||
"type": 1, //任务类型, 1: 一次性任务, 2: 日常任务
|
||||
"repeat": 1, // 任务可重复次数
|
||||
"pretasks": ["task id 1"], //前置任务
|
||||
"score": 0, // 完成任务可获得的积分
|
||||
"category": "", // 任务分类
|
||||
"cfg": {}, // 其他一些任务相关配置参数, 比如icon, 或者其他未考虑的参数
|
||||
"end": false, // 是否已经结束
|
||||
"autoclaim": false // 任务完成后是否自动获取奖励
|
||||
}
|
||||
],
|
||||
"startTime": 1702628292366, // 活动开始时间
|
||||
"endTime": 1705220292366 // 活动结束时间
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 4. *社交任务进度
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/tasks/progress`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
- Body: {}
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
"status": 2, // 任务状态, 0: 未开始, 1: 进行中, 2: 成功, 9: 失败
|
||||
"id": "TwitterConnect", // 任务id
|
||||
"timeStart": 1703150269527, // 任务开始时间
|
||||
"data": { // 当前任务带的额外信息, 比如twitter的id和昵称等
|
||||
"username": "zhl01",
|
||||
"userid": "564269223"
|
||||
},
|
||||
"timeFinish": 1703150280059 // 任务结束时间
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 5.\* 开始某个任务
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/tasks/begin_task`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
"task": "TwitterFollow" // 任务id
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 1, // 任务状态, 0: 未开始, 1: 进行中, 2: 成功, 9: 失败
|
||||
"id": "TwitterFollow", // 任务id
|
||||
"timeStart": 1703150294051 // 任务开始时间
|
||||
}
|
||||
```
|
||||
|
||||
### 6.\* 检查任务状态
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/tasks/check_task`
|
||||
- 方法:`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
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
- URL:`/api/activity/upload_invite_code`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
"code": "邀请人的邀请码"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
> 只要不返回errcode, 即表示上传成功
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
### 9. 积分排行榜
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/activity/leaderboard/:activity/:page`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
- 参数:
|
||||
- `activity`(当前活动的ID)
|
||||
- `page` (返回数据的分页序号, 0 开始)
|
||||
|
||||
> 默认返回100条记录, 如果要返回不同数量, query param传 limit
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"rank": 1, // 排名, 从1开始
|
||||
"level": 1, // 段位
|
||||
"nickname": "昵称",
|
||||
"score": 1 //获得的积分
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 10.\* 用户状态
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/user/state`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"address": "钱包地址",
|
||||
"boost": 1, // 正常值为1, 本次活动不用考虑
|
||||
"boostExpire": 0, // 计算得分时, 如果boost过期, 即使boost大于1, 也不计算boost, 本次活动不用考虑
|
||||
"twitterId": "",
|
||||
"twitterName": "",
|
||||
"discordId": "",
|
||||
"discordName": "",
|
||||
"scoreToday": 100, // 今日获得积分
|
||||
"scoreTotal": 200, // 总积分
|
||||
"rankTotal": "-",
|
||||
"invite": "邀请人address",
|
||||
"inviteCount": 0, // 我邀请的用户总数
|
||||
"inviteScore": 0, // 我邀请用户总数获得的分数
|
||||
"code": "自己的邀请码",
|
||||
"mapopen": 0, // 地图开启状态, 0: 未开启, 1: 已开启
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 11.\* 签到列表
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/user/checkin/list/:tag`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
query param
|
||||
|
||||
| Name | Type | Desc |
|
||||
| ------- | ------ | -------- |
|
||||
| tag | string | last: 最新, 前一天+今天, 1month: 当月 |
|
||||
#### Response
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"address": "钱包地址",
|
||||
"day": "20240105", // 格式化后签到日期, 时区按SG(UTC+8)
|
||||
"time": 1704436745, // 具体的签到时间
|
||||
"count": 0, //连签天数
|
||||
"total": 10 //累计签到天数
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 12.\* 探索状态
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/game/stat`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
ticket: 1, // 可用探索次数
|
||||
todayStat: 0, // 当日签到状态, 0: 未签到, 1: 已签到,但未领取 9:已签到, 已领取
|
||||
todayTickets: 1, // 当日签到可领取次数
|
||||
daysTotal: 1, // 累计签到天数
|
||||
daysSeq: 1, // 连续签到天数
|
||||
totalStat: [{ // 累计签到状态
|
||||
days: 3, // 天数
|
||||
tickets: 2, // 满足条件后可领取数量
|
||||
state: 0, // 领取状态: 0: 未领取, 1: 可领取, 9: 已领取
|
||||
}],
|
||||
signCfg: [{ // 连续签到配置
|
||||
days: 2, //天数
|
||||
tickets: 1, //额外增加的次数
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### 13.\* 探索
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/game/step`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
"step": 2 // 使用的次数
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
score: 20, //获得积分数量
|
||||
chest: [{
|
||||
id: '1112323131', // 获得宝箱id
|
||||
level: 1, //宝箱品级
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### 14.\* 领取累计签到奖励
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/user/checkin/claim`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
"days": 3 // 领取的累计签到天数
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
ticket: 2, //获得探索次数
|
||||
}
|
||||
```
|
||||
|
||||
### 15.\* 已邀请列表
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/activity/invite_list`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[{
|
||||
level: 1, // 段位
|
||||
nickname: '用户昵称',
|
||||
score: 100, // 获得的积分
|
||||
}]
|
||||
```
|
||||
|
||||
### 16.\* 宝箱列表
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/chest/list`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[{
|
||||
id: 1, // 箱子id
|
||||
stat: 0, // 0: 锁定, 1: 正常
|
||||
shareCode: '箱子的分享码',
|
||||
level: 1, // 箱子品级
|
||||
maxBonus: 10, // 最大可助力数量
|
||||
scoreInit: 5, // 初始可获得积分
|
||||
scoreBonus: 10, // 助力增加的分数
|
||||
bonusCount: 2, // 已助力次数
|
||||
}]
|
||||
```
|
||||
|
||||
### 17.\* 宝箱助力记录
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/chest/enhance/list`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
chestid: '12312313' // 宝箱id
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[{
|
||||
level: 1, // 段位
|
||||
nickname: '用户昵称',
|
||||
score: 100, // 获得的积分
|
||||
}]
|
||||
```
|
||||
|
||||
### 18.\* 宝箱助力
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/chest/enhance`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
code: '131aasd`1' // 宝箱的分享码
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
score: 100, // 自己获得的积分
|
||||
}
|
||||
```
|
||||
|
||||
### 19.\* 开启宝箱(这个应该会修改)
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/chest/open`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
chestid: '131aasd`1' // 宝箱id
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
score: 100, // 获得的积分
|
||||
}
|
||||
```
|
||||
|
||||
### 20.\* 宝箱开启记录
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/chest/open/history`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[{
|
||||
chest: '123123123',
|
||||
level: 1, // 箱子品级
|
||||
score: 100, // 获得的积分
|
||||
time: 111 // 开启时间
|
||||
}]
|
||||
```
|
||||
|
||||
### 21.\* 积分详情列表
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/activity/score_list`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[{
|
||||
score: 100, // 获得的积分
|
||||
type: '', // 获取原因
|
||||
time: 111 // 开启时间
|
||||
}]
|
||||
```
|
||||
|
||||
### 22.\* 开启地图
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/game/open`
|
||||
- 方法:`GET`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
> 返回没errcode就表示开启成功
|
||||
|
||||
### 23.\* 检查签到并领取奖励
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/user/checkin`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
ticket: 1 // 获得的探索次数
|
||||
}
|
||||
```
|
@ -281,7 +281,7 @@
|
||||
"score": 200,
|
||||
"params": {"address": "0xe2E4D5a4045fBFcbCBECAf5b8A94303712d2FA97"}
|
||||
}],
|
||||
"startTime": 1702628292366,
|
||||
"endTime": 1705220292366
|
||||
"startTime": 1711086450119,
|
||||
"endTime": 1713678477701
|
||||
}
|
||||
]
|
179
initdatas/uaw_cfg.json
Normal file
179
initdatas/uaw_cfg.json
Normal file
@ -0,0 +1,179 @@
|
||||
[
|
||||
{
|
||||
"_id": "uaw_activity",
|
||||
"name": "UAW Activity",
|
||||
"description": "UAW",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "e2yhq2lj30vwcpedv7p",
|
||||
"task": "TwitterConnect",
|
||||
"title": "",
|
||||
"type": 1,
|
||||
"desc": "",
|
||||
"score": 0,
|
||||
"category": "",
|
||||
"autoclaim": false,
|
||||
"cfg": {"icon": "twitter"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {}
|
||||
}, {
|
||||
"id": "e2fclylj30vwcpe0szl",
|
||||
"task": "TwitterFollow",
|
||||
"title": "",
|
||||
"type": 1,
|
||||
"desc": "Follow Counter Fire’s official X account",
|
||||
"category": "Social Tasks",
|
||||
"score": 100,
|
||||
"autoclaim": false,
|
||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
||||
"cfg": {"account": "@_CounterFire", "icon": "twitter"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "e2feyflj30vwcpe0sjy",
|
||||
"task": "TwitterFollow",
|
||||
"title": "",
|
||||
"type": 1,
|
||||
"desc": "",
|
||||
"category": "Social Tasks",
|
||||
"score": 100,
|
||||
"autoclaim": false,
|
||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
||||
"cfg": {"icon": "twitter"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "e2fuah0j30vwcpe0my7",
|
||||
"task": "TwitterRetweet",
|
||||
"title": "",
|
||||
"type": 1,
|
||||
"desc": "Show your friends Counter Fire.",
|
||||
"category": "Social Tasks",
|
||||
"score": 150,
|
||||
"autoclaim": false,
|
||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
||||
"cfg": {"icon": "twitter", "content": "Just joined Counter Fire! 🎮 Excited about the endless opportunities ahead. 🔥 Let's team up and conquer together! Come in with me #CounterFire #GamingAdventures"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "e2fuah0j30vwcpe1my7",
|
||||
"task": "TwitterRetweet",
|
||||
"title": "",
|
||||
"type": 1,
|
||||
"desc": "Click Verify button and retweet on the tweet",
|
||||
"category": "Social Tasks",
|
||||
"score": 200,
|
||||
"autoclaim": false,
|
||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
||||
"cfg": {"icon": "twitter", "content": "输入框自动生成,活动开始时添加内容)"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "e2far3lj30vwcpe0mh7",
|
||||
"task": "DiscordConnect",
|
||||
"title": "",
|
||||
"type": 1,
|
||||
"desc": "",
|
||||
"category": "",
|
||||
"score": 0,
|
||||
"autoclaim": false,
|
||||
"pretasks": [],
|
||||
"cfg": {"icon": "discord"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {}
|
||||
}, {
|
||||
"id": "e2far3lj30vwcpe0mf8",
|
||||
"task": "DiscordJoin",
|
||||
"title": "",
|
||||
"type": 1,
|
||||
"desc": "Join Counter Fire’s official Discord server",
|
||||
"category": "Social Tasks",
|
||||
"score": 100,
|
||||
"autoclaim": false,
|
||||
"pretasks": [],
|
||||
"cfg": {"icon": "discord"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "e2fak2lj30vwcpe0awc",
|
||||
"task": "DiscordRole",
|
||||
"title": "Discord Role",
|
||||
"type": 1,
|
||||
"desc": "Get a role in Counter Fire’s official Discord ",
|
||||
"category": "Social Tasks",
|
||||
"score": 200,
|
||||
"autoclaim": false,
|
||||
"pretasks": ["e2far3lj30vwcpe0mf8"],
|
||||
"cfg": {"icon": "discord"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "e2feyflj30vwcpe0sjx",
|
||||
"task": "YoutubeFollow",
|
||||
"title": "",
|
||||
"type": 1,
|
||||
"desc": "Follow Counter Fire’s official YTB account",
|
||||
"category": "Social Tasks",
|
||||
"score": 100,
|
||||
"autoclaim": false,
|
||||
"pretasks": [],
|
||||
"cfg": {"icon": "youtube"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "e2feyflj30vwcpe0sjz",
|
||||
"task": "YoutubePost",
|
||||
"title": "",
|
||||
"type": 1,
|
||||
"desc": "Post a video introducing @_CounterFire",
|
||||
"category": "Social Tasks",
|
||||
"score": 500,
|
||||
"autoclaim": false,
|
||||
"pretasks": [],
|
||||
"cfg": {"icon": "youtube"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "e2fuah0j30vwcpe2my7",
|
||||
"task": "TwitterRetweet",
|
||||
"title": "",
|
||||
"type": 2,
|
||||
"desc": "Showcase your performance in Counter Fire to your friends!",
|
||||
"category": "Social Tasks",
|
||||
"score": 300,
|
||||
"autoclaim": false,
|
||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
||||
"cfg": {"icon": "twitter", "content": "Just scored xx Flame on @_CounterFire! 🔥 Join me in the action-packed fun and let's play to earn! #GamingAdventures #CounterFire"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}, {
|
||||
"id": "e2fuah0j30vwcpe2my7",
|
||||
"task": "TwitterRetweet",
|
||||
"title": "",
|
||||
"type": 1,
|
||||
"desc": "Post to confess your 💕 for @_CounterFire",
|
||||
"category": "Referral to Earn",
|
||||
"score": 100,
|
||||
"autoclaim": false,
|
||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
||||
"cfg": {"icon": "twitter", "content": "Just scored xx Flame on @_CounterFire! 🔥 Join me in the action-packed fun and let's play to earn! #GamingAdventures #CounterFire"},
|
||||
"start": "2024-01-01 00:00",
|
||||
"end": "2025-01-01 00:00",
|
||||
"params": {"time": 6, "failRate": 60}
|
||||
}
|
||||
],
|
||||
"startTime": 1711086450119,
|
||||
"endTime": 1713678477701
|
||||
}
|
||||
]
|
@ -1 +1 @@
|
||||
Subproject commit e207a95fd79c0e926668074e68ca2467952d1ded
|
||||
Subproject commit b97e33472f46eb8fb47a8cf3c3924c5d26af5eca
|
@ -1,10 +1,11 @@
|
||||
import { ActivityInfo } from 'models/ActivityInfo'
|
||||
import { ActivityUser } from 'models/ActivityUser'
|
||||
import { rankKey } from 'services/rank.svr'
|
||||
import { yesterday } from 'zutils/utils/date.util'
|
||||
import { rankKey, rankLevel } from 'services/rank.svr'
|
||||
import { BaseController, ROLE_ANON, SyncLocker, ZError, ZRedisClient, role, router } from 'zutils'
|
||||
import { ScoreRecord } from 'models/ScoreRecord'
|
||||
import { formatAddress } from 'zutils/utils/chain.util'
|
||||
|
||||
const MAX_LIMIT = 50
|
||||
const MAX_LIMIT = 100
|
||||
export default class ActivityController extends BaseController {
|
||||
@role(ROLE_ANON)
|
||||
@router('get /api/activity/:id')
|
||||
@ -42,6 +43,45 @@ export default class ActivityController extends BaseController {
|
||||
return {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 邀请列表
|
||||
*/
|
||||
@router('get /api/activity/invite_list')
|
||||
async inviteUserList(req) {
|
||||
let user = req.user
|
||||
const totalKey = rankKey(user.activity)
|
||||
let users = await ActivityUser.find({ inviteUser: user.id })
|
||||
let results = []
|
||||
for (let u of users) {
|
||||
const totalScore = await new ZRedisClient().zscore(totalKey, u.id)
|
||||
const score = totalScore ? parseInt(totalScore + '') : 0
|
||||
results.push({
|
||||
// user: u.id,
|
||||
level: rankLevel(score),
|
||||
nickname: u.twitterName || u.discordName || formatAddress(u.address),
|
||||
score,
|
||||
})
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* 积分详情列表
|
||||
*/
|
||||
@router('get /api/activity/score_list')
|
||||
async scoreList(req) {
|
||||
let user = req.user
|
||||
const records = await ScoreRecord.find({ user: user.id, activity: user.activity })
|
||||
return records.map(record => {
|
||||
return {
|
||||
score: record.score,
|
||||
type: record.type,
|
||||
//@ts-ignore
|
||||
time: record.createdAt.getTime(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@role(ROLE_ANON)
|
||||
@router('get /api/activity/leaderboard/:activity/:page')
|
||||
async inviteCode(req) {
|
||||
@ -53,25 +93,25 @@ export default class ActivityController extends BaseController {
|
||||
const end = start + limit - 1
|
||||
const records = await new ZRedisClient().zrevrange(`${activity}:score`, start, end)
|
||||
let results: any = []
|
||||
const yesterdayKey = rankKey(activity, yesterday())
|
||||
// const yesterdayKey = rankKey(activity, yesterday())
|
||||
for (let i = 0; i < records.length; i += 2) {
|
||||
const id = records[i]
|
||||
let score = parseInt(records[i + 1])
|
||||
const user = await ActivityUser.findById(id)
|
||||
let invite = ''
|
||||
if (user?.inviteUser) {
|
||||
const inviteUser = await ActivityUser.findById(user.inviteUser)
|
||||
if (inviteUser) {
|
||||
invite = inviteUser.address
|
||||
}
|
||||
}
|
||||
const yesterdayScore = await new ZRedisClient().zscore(yesterdayKey, id)
|
||||
// let invite = ''
|
||||
// if (user?.inviteUser) {
|
||||
// const inviteUser = await ActivityUser.findById(user.inviteUser)
|
||||
// if (inviteUser) {
|
||||
// invite = inviteUser.address
|
||||
// }
|
||||
// }
|
||||
// const yesterdayScore = await new ZRedisClient().zscore(yesterdayKey, id)
|
||||
results.push({
|
||||
rank: start + i / 2 + 1,
|
||||
address: user?.address || 'unknow',
|
||||
invite,
|
||||
level: rankLevel(score),
|
||||
nickname: user.twitterName || user.discordName || formatAddress(user.address),
|
||||
score,
|
||||
yesterday: yesterdayScore ? parseInt(yesterdayScore + '') : 0,
|
||||
// yesterday: yesterdayScore ? parseInt(yesterdayScore + '') : 0,
|
||||
})
|
||||
}
|
||||
return results
|
||||
|
@ -6,7 +6,7 @@ import { queryStakeList } from 'services/chain.svr'
|
||||
import { sign } from 'zutils/utils/chain.util'
|
||||
|
||||
export default class ChainController extends BaseController {
|
||||
@router('get /api/stake/list')
|
||||
// @router('get /api/stake/list')
|
||||
async stakeList(req) {
|
||||
const user = req.user
|
||||
const records = await queryStakeList(user.address)
|
||||
@ -14,7 +14,7 @@ export default class ChainController extends BaseController {
|
||||
return result
|
||||
}
|
||||
|
||||
@router('post /api/lottery/claim_usdt')
|
||||
// @router('post /api/lottery/claim_usdt')
|
||||
async preClaimUsdt(req: FastifyRequest) {
|
||||
new SyncLocker().checkLock(req)
|
||||
let user = req.user
|
||||
|
155
src/controllers/chest.controller.ts
Normal file
155
src/controllers/chest.controller.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import { ZError, SyncLocker, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils'
|
||||
import { ActivityChest } from 'models/ActivityChest'
|
||||
import { ActivityUser } from 'models/ActivityUser'
|
||||
import { mongoose } from '@typegoose/typegoose'
|
||||
import { rankKey, rankLevel, updateRankScore } from 'services/rank.svr'
|
||||
import { formatDate } from 'zutils/utils/date.util'
|
||||
import { ScoreRecord } from 'models/ScoreRecord'
|
||||
/**
|
||||
* 宝箱相关接口
|
||||
*/
|
||||
class BoxController extends BaseController {
|
||||
/**
|
||||
* 宝箱列表
|
||||
*/
|
||||
@router('get /api/chest/list')
|
||||
async chestList(req) {
|
||||
const user = req.user
|
||||
const chests = await ActivityChest.find({ activity: user.activity, user, status: 0 })
|
||||
return chests.map(chest => chest.toJson())
|
||||
}
|
||||
/**
|
||||
* 宝箱助力列表
|
||||
*/
|
||||
@router('post /api/chest/enhance/list')
|
||||
async enhanceList(req) {
|
||||
const user = req.user
|
||||
const { chestid } = req.query
|
||||
if (!chestid) {
|
||||
throw new ZError(11, 'chestid is required')
|
||||
}
|
||||
const chest = await ActivityChest.findById(chestid)
|
||||
if (!chest) {
|
||||
throw new ZError(12, 'chest not found')
|
||||
}
|
||||
if (chest.status === 0) {
|
||||
throw new ZError(13, 'chest is locked')
|
||||
}
|
||||
if (chest.status === 9) {
|
||||
throw new ZError(14, 'chest had been opened')
|
||||
}
|
||||
const result = []
|
||||
const users = await ActivityUser.find({ _id: { $in: chest.bonusUsers } })
|
||||
const userMap = new Map()
|
||||
const totalKey = rankKey(user.activity)
|
||||
for (let user of users) {
|
||||
// get score from redis
|
||||
userMap.set(user.id, user)
|
||||
}
|
||||
for (let i = 0; i < chest.bonusUsers.length; i++) {
|
||||
const user = userMap.get(chest.bonusUsers[i])
|
||||
const totalScore = await new ZRedisClient().zscore(totalKey, user.id)
|
||||
const score = totalScore ? parseInt(totalScore + '') : 0
|
||||
result.push({
|
||||
nickname: user.twitterName || user.discordName,
|
||||
level: rankLevel(score),
|
||||
score: score,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* 宝箱助力
|
||||
*/
|
||||
@router('post /api/chest/enhance')
|
||||
async enhance(req) {
|
||||
new SyncLocker().checkLock(req)
|
||||
const { code } = req.params
|
||||
const user = req.user
|
||||
const uid = user.uid
|
||||
const score = 10 //TODO:: 根据规则生成助力积分
|
||||
const session = await mongoose.startSession()
|
||||
session.startTransaction()
|
||||
try {
|
||||
const chest = await ActivityChest.findOne({ shareCode: code, activity: user.activity }).session(session)
|
||||
if (chest.bonusUsers.includes(uid)) {
|
||||
throw new ZError(10, 'user already enhanced')
|
||||
}
|
||||
if (chest.bonusUsers.length >= chest.maxBonus) {
|
||||
throw new ZError(12, 'enhanced times exceed')
|
||||
}
|
||||
chest.bonusUsers.push(uid)
|
||||
chest.bonusScores.push(score)
|
||||
chest.scoreBonus += score
|
||||
await chest.save({ session })
|
||||
await session.commitTransaction()
|
||||
session.endSession()
|
||||
return {
|
||||
score: 1, // TODO:: 根据规则生成自己获得助力积分
|
||||
}
|
||||
} catch (error) {
|
||||
session.abortTransaction()
|
||||
session.endSession()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启宝箱
|
||||
*/
|
||||
@router('post /api/chest/open')
|
||||
async openChest(req) {
|
||||
new SyncLocker().checkLock(req)
|
||||
const user = req.user
|
||||
const { chestid } = req.query
|
||||
if (!chestid) {
|
||||
throw new ZError(11, 'chestid is required')
|
||||
}
|
||||
const chest = await ActivityChest.findById(chestid)
|
||||
if (!chest) {
|
||||
throw new ZError(12, 'chest not found')
|
||||
}
|
||||
if (chest.user !== user.id) {
|
||||
throw new ZError(13, 'chest not belong to user')
|
||||
}
|
||||
if (chest.status === 1) {
|
||||
throw new ZError(14, 'chest already opened')
|
||||
}
|
||||
chest.status = 1
|
||||
const score = chest.scoreInit + chest.scoreBonus
|
||||
const dateTag = formatDate(new Date())
|
||||
await updateRankScore({
|
||||
user: user.id,
|
||||
score: score,
|
||||
activity: user.activity,
|
||||
scoreType: 'open_chest',
|
||||
scoreParams: {
|
||||
date: dateTag,
|
||||
chestId: chest.id,
|
||||
level: chest.level,
|
||||
},
|
||||
})
|
||||
await chest.save()
|
||||
return { score }
|
||||
}
|
||||
|
||||
/**
|
||||
* 宝箱开启记录
|
||||
*/
|
||||
@router('get /api/chest/open/history')
|
||||
async openChestHistory(req) {
|
||||
const user = req.user
|
||||
const records = await ScoreRecord.find({ user: user.id, activity: user.activity, scoreType: 'open_chest' }).sort({
|
||||
createdAt: -1,
|
||||
})
|
||||
return records.map(record => {
|
||||
return {
|
||||
chest: record.data.chestId,
|
||||
score: record.score,
|
||||
level: record.data.level,
|
||||
// @ts-ignore
|
||||
time: record.createdAt.getTime(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
204
src/controllers/game.controller.ts
Normal file
204
src/controllers/game.controller.ts
Normal file
@ -0,0 +1,204 @@
|
||||
import { ActivityGame } from 'models/ActivityGame'
|
||||
import { DAILY_SIGN, SIGN_TOTAL, TicketRecord, USE_TICKET } from 'models/TicketRecord'
|
||||
import { queryCheckInList } from 'services/chain.svr'
|
||||
import { checkInToday, seqSignCfg, seqSignScore, totalSignCfg, totalSignScore } from 'services/sign.svr'
|
||||
import { ZError, SyncLocker, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils'
|
||||
import { formatDate } from 'zutils/utils/date.util'
|
||||
/**
|
||||
* 探索游戏相关接口
|
||||
*/
|
||||
class GameController extends BaseController {
|
||||
/**
|
||||
* 签到
|
||||
*/
|
||||
@router('post /api/user/checkin')
|
||||
async checkIn(req) {
|
||||
new SyncLocker().checkLock(req)
|
||||
const user = req.user
|
||||
const { address } = user
|
||||
const dateTag = formatDate(new Date())
|
||||
const gameRecord = await ActivityGame.insertOrUpdate({ user: user.id, activity: user.activity }, {})
|
||||
if (gameRecord.status === 0) {
|
||||
throw new ZError(11, 'map not open')
|
||||
}
|
||||
if (dateTag === gameRecord.lastSignDay) {
|
||||
throw new ZError(12, 'already claimed')
|
||||
}
|
||||
const record = await checkInToday(address, dateTag)
|
||||
if (!record) {
|
||||
throw new ZError(13, 'had not signed in')
|
||||
}
|
||||
const reward = seqSignScore(record.count)
|
||||
gameRecord.lastSignDay = dateTag
|
||||
gameRecord.tickets += 1 + reward
|
||||
const ticketRecord = new TicketRecord({
|
||||
user: user.id,
|
||||
activity: user.activity,
|
||||
type: DAILY_SIGN,
|
||||
data: {},
|
||||
score: 1 + reward,
|
||||
})
|
||||
await ticketRecord.save()
|
||||
await gameRecord.save()
|
||||
return { ticket: reward + 1 }
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到列表
|
||||
*/
|
||||
@router('get /api/user/checkin/list/:tag')
|
||||
async checkInList(req) {
|
||||
const user = req.user
|
||||
const { tag } = req.params
|
||||
let days: any = 1
|
||||
if (tag === '1month') {
|
||||
days = '1month'
|
||||
} else if (tag === 'last') {
|
||||
days = '1'
|
||||
}
|
||||
const res = await queryCheckInList(user.address, days, 0)
|
||||
return res
|
||||
}
|
||||
/**
|
||||
* 领取累计签到奖励
|
||||
* //TODO:: test
|
||||
*/
|
||||
@router('post /api/user/checkin/claim')
|
||||
async claimCheckResult(req) {
|
||||
new SyncLocker().checkLock(req)
|
||||
const user = req.user
|
||||
let { days } = req.params
|
||||
if (!days) {
|
||||
throw new ZError(11, 'invalid days')
|
||||
}
|
||||
days = parseInt(days)
|
||||
const dateTag = formatDate(new Date())
|
||||
const checkRecord = await checkInToday(user.address, dateTag)
|
||||
if (!checkRecord) {
|
||||
throw new ZError(12, 'not signed in')
|
||||
}
|
||||
if (days > checkRecord.total) {
|
||||
throw new ZError(13, 'invalid days')
|
||||
}
|
||||
const ticketRecords = await TicketRecord.find({ user: user.id, activity: user.activity, type: SIGN_TOTAL })
|
||||
const claimedSet = new Set()
|
||||
ticketRecords.forEach(record => {
|
||||
claimedSet.add(record.data.day)
|
||||
})
|
||||
if (claimedSet.has(days)) {
|
||||
throw new ZError(14, 'already claimed')
|
||||
}
|
||||
|
||||
const score = totalSignScore(days)
|
||||
if (score === 0) {
|
||||
throw new ZError(15, 'invalid days')
|
||||
}
|
||||
const record = new TicketRecord({
|
||||
user: user.id,
|
||||
activity: user.activity,
|
||||
type: SIGN_TOTAL,
|
||||
data: { day: days },
|
||||
score,
|
||||
})
|
||||
const gameRecord = await ActivityGame.insertOrUpdate({ user: user.id, activity: user.activity }, {})
|
||||
gameRecord.tickets += score
|
||||
await gameRecord.save()
|
||||
await record.save()
|
||||
return { ticket: score }
|
||||
}
|
||||
/**
|
||||
* //TODO::开启地图
|
||||
*/
|
||||
@router('get /api/game/open')
|
||||
async openMap(req) {
|
||||
const user = req.user
|
||||
return {}
|
||||
}
|
||||
/**
|
||||
* 探索状态
|
||||
*/
|
||||
@router('get /api/game/stat')
|
||||
async gameStat(req, res) {
|
||||
const user = req.user
|
||||
const record = await ActivityGame.insertOrUpdate({ user: user.id, activity: user.activity }, {})
|
||||
const signCfg = seqSignCfg
|
||||
const dateTag = formatDate(new Date())
|
||||
const checkRecord = await checkInToday(user.address, dateTag)
|
||||
let todayStat = 0
|
||||
if (checkRecord) {
|
||||
// 有签到的链上记录, 说明已签到
|
||||
todayStat = 1
|
||||
}
|
||||
if (record.lastSignDay === dateTag) {
|
||||
// 检查是否已领取
|
||||
todayStat = 2
|
||||
}
|
||||
const scoreBonus = seqSignScore(checkRecord.count)
|
||||
const ticketRecords = await TicketRecord.find({ user: user.id, activity: user.activity, type: SIGN_TOTAL })
|
||||
const claimedSet = new Set()
|
||||
ticketRecords.forEach(record => {
|
||||
claimedSet.add(record.data.day)
|
||||
})
|
||||
|
||||
const totalStat = []
|
||||
for (let cfg of totalSignCfg) {
|
||||
let state = 0
|
||||
if (cfg.days <= checkRecord.total) {
|
||||
state = 1
|
||||
}
|
||||
if (claimedSet.has(cfg.days)) {
|
||||
state = 9
|
||||
}
|
||||
totalStat.push({
|
||||
days: cfg.days,
|
||||
tickets: cfg.reward,
|
||||
state,
|
||||
})
|
||||
}
|
||||
return {
|
||||
ticket: record.tickets,
|
||||
signCfg,
|
||||
todayStat,
|
||||
todayTickets: 1 + scoreBonus,
|
||||
daysTotal: checkRecord.total,
|
||||
daysSeq: checkRecord.count,
|
||||
totalStat,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 探索
|
||||
*/
|
||||
@router('post /api/game/step')
|
||||
async gameStep(req, res) {
|
||||
new SyncLocker().checkLock(req)
|
||||
const user = req.user
|
||||
let { step } = req.params
|
||||
step = step || '1'
|
||||
if (isNaN(step)) {
|
||||
throw new ZError(11, 'invalid step')
|
||||
}
|
||||
step = parseInt(step)
|
||||
|
||||
const record = await ActivityGame.insertOrUpdate({ user: user.id, activity: user.activity }, {})
|
||||
if (record.status === 0) {
|
||||
throw new ZError(12, 'map not open')
|
||||
}
|
||||
if (record.tickets < step) {
|
||||
throw new ZError(13, 'insufficient tickets')
|
||||
}
|
||||
record.tickets -= step
|
||||
// TODO:: 生成奖励, 存入data, 并返回
|
||||
const ticketRecord = new TicketRecord({
|
||||
user: user.id,
|
||||
activity: user.activity,
|
||||
type: USE_TICKET,
|
||||
data: {},
|
||||
score: -step,
|
||||
})
|
||||
await ticketRecord.save()
|
||||
await record.save()
|
||||
let result = []
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ const draw = (rewards: { probability: number }[]) => {
|
||||
}
|
||||
|
||||
export default class LotteryController extends BaseController {
|
||||
@router('get /api/lottery/stats')
|
||||
// @router('get /api/lottery/stats')
|
||||
async userStats(req) {
|
||||
let user = req.user
|
||||
let record = await new LotteryCache().getData(user.id, user.activity)
|
||||
@ -37,7 +37,7 @@ export default class LotteryController extends BaseController {
|
||||
return result
|
||||
}
|
||||
|
||||
@router('post /api/lottery/history')
|
||||
// @router('post /api/lottery/history')
|
||||
async userLotteryHistory(req) {
|
||||
let user = req.user
|
||||
let { day, page, limit } = req.params
|
||||
@ -59,7 +59,7 @@ export default class LotteryController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
@router('get /api/lottery/items')
|
||||
// @router('get /api/lottery/items')
|
||||
async items(req) {
|
||||
const items = ALL_ITEMS
|
||||
const cfgs = LOTTERY_CFG
|
||||
@ -78,7 +78,7 @@ export default class LotteryController extends BaseController {
|
||||
return { items: result, start: cfgs.start, end: cfgs.end }
|
||||
}
|
||||
|
||||
@router('get /api/lottery/draw')
|
||||
// @router('get /api/lottery/draw')
|
||||
async draw(req) {
|
||||
new SyncLocker().checkLock(req)
|
||||
let user = req.user
|
||||
@ -142,7 +142,7 @@ export default class LotteryController extends BaseController {
|
||||
return reward
|
||||
}
|
||||
|
||||
@router('get /api/lottery/fusion')
|
||||
// @router('get /api/lottery/fusion')
|
||||
async fusion(req) {
|
||||
new SyncLocker().checkLock(req)
|
||||
let user = req.user
|
||||
|
@ -5,11 +5,12 @@ import { DEFAULT_EXPIRED, NonceRecord } from 'models/NonceRecord'
|
||||
import { ScoreRecord } from 'models/ScoreRecord'
|
||||
import { LoginRecordQueue } from 'queue/loginrecord.queue'
|
||||
import { queryCheckInList } from 'services/chain.svr'
|
||||
import { rankKey } from 'services/rank.svr'
|
||||
import { rankKey, updateRankScore } from 'services/rank.svr'
|
||||
import { SiweMessage } from 'siwe'
|
||||
import { nextday } from 'zutils/utils/date.util'
|
||||
import { checkParamsNeeded } from 'zutils/utils/net.util'
|
||||
import { aesDecrypt, base58ToHex } from 'zutils/utils/security.util'
|
||||
import { aesDecrypt } from 'zutils/utils/security.util'
|
||||
import { base58ToHex } from 'zutils/utils/string.util'
|
||||
|
||||
const LOGIN_TIP = 'This signature is just to verify your identity'
|
||||
|
||||
@ -133,7 +134,8 @@ class SignController extends BaseController {
|
||||
}
|
||||
return result
|
||||
}
|
||||
@router('get /api/user/state/boost')
|
||||
|
||||
// @router('get /api/user/state/boost')
|
||||
async boost(req) {
|
||||
new SyncLocker().checkLock(req)
|
||||
const user = req.user
|
||||
@ -145,32 +147,4 @@ class SignController extends BaseController {
|
||||
await user.save()
|
||||
return { boost: user.boost, boostExpire: user.boostExpire }
|
||||
}
|
||||
|
||||
@router('get /api/user/checkin/list/:tag')
|
||||
async checkInList(req) {
|
||||
const user = req.user
|
||||
const { tag } = req.params
|
||||
let days: any = 1
|
||||
if (tag === '1month') {
|
||||
days = '1month'
|
||||
} else if (tag === 'last') {
|
||||
days = '1'
|
||||
}
|
||||
const res = await queryCheckInList(user.address, days, 0)
|
||||
return res.data
|
||||
}
|
||||
|
||||
/**
|
||||
* regist user by token from wallet-svr
|
||||
* TODO::
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
@role(ROLE_ANON)
|
||||
@router('post /api/wallet/verify_token')
|
||||
async verifyToken(req, res) {
|
||||
const { wallet_token } = req.params
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
74
src/models/ActivityChest.ts
Normal file
74
src/models/ActivityChest.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import { getModelForClass, index, modelOptions, mongoose, pre, prop } from '@typegoose/typegoose'
|
||||
import { Severity } from '@typegoose/typegoose/lib/internal/constants'
|
||||
import { BaseModule } from './Base'
|
||||
import { convert } from 'zutils/utils/number.util'
|
||||
const alphabet = '3fBCM8j17XNA9xYun4wmLWep2oHFlhPcgyEJskqOz6GK0UtV5ZRaDSvrTbidQI'
|
||||
|
||||
/**
|
||||
* 活动宝箱
|
||||
*/
|
||||
@dbconn()
|
||||
@index({ user: 1, activity: 1 }, { unique: false })
|
||||
@index({ shareCode: 1, activity: 1 }, { unique: true })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'activity_box', timestamps: true },
|
||||
options: { allowMixed: Severity.ALLOW },
|
||||
})
|
||||
@pre<ActivityChestClass>('save', async function () {
|
||||
if (!this.shareCode) {
|
||||
// 取ObjectId的time和inc段,
|
||||
// 将time段倒序(倒序后, 如果以0开始, 则移除0, 随机拼接一个hex字符), 然后拼接inc段, 再转换成52进制
|
||||
let timeStr = this.id.slice(0, 8).split('').reverse().join('')
|
||||
if (timeStr.indexOf('0') === 0) {
|
||||
let randomStr = convert({ numStr: ((Math.random() * 51) | (0 + 1)) + '', base: 10, to: 52 })
|
||||
timeStr = randomStr + timeStr.slice(1)
|
||||
}
|
||||
let shortId = timeStr + this.id.slice(-6)
|
||||
this.shareCode = convert({ numStr: shortId, base: 16, to: 52, alphabet })
|
||||
}
|
||||
})
|
||||
class ActivityChestClass extends BaseModule {
|
||||
// 盒子的分享码
|
||||
@prop()
|
||||
public shareCode: string
|
||||
// 0 锁定, 1 未开启 9 已开启
|
||||
@prop({ default: 0 })
|
||||
public status: number
|
||||
@prop()
|
||||
public user: string
|
||||
@prop()
|
||||
public activity: string
|
||||
// 品级
|
||||
@prop({ default: 0 })
|
||||
public level: number
|
||||
// 最大助力次数
|
||||
@prop()
|
||||
public maxBonus: number
|
||||
// 基础积分
|
||||
@prop({ default: 0 })
|
||||
public scoreInit: number
|
||||
// 助力积分
|
||||
@prop({ default: 0 })
|
||||
public scoreBonus: number
|
||||
|
||||
@prop({ type: () => [String], default: [] })
|
||||
public bonusUsers: string[]
|
||||
@prop({ type: () => [Number], default: [] })
|
||||
public bonusScores: number[]
|
||||
|
||||
public toJson() {
|
||||
return {
|
||||
// @ts-ignore
|
||||
id: this._id,
|
||||
stat: this.status,
|
||||
shareCode: this.shareCode,
|
||||
level: this.level,
|
||||
maxBonus: this.maxBonus,
|
||||
scoreInit: this.scoreInit,
|
||||
scoreBonus: this.scoreBonus,
|
||||
bounsCount: this.bonusUsers.length,
|
||||
}
|
||||
}
|
||||
}
|
||||
export const ActivityChest = getModelForClass(ActivityChestClass, { existingConnection: ActivityChestClass['db'] })
|
38
src/models/ActivityGame.ts
Normal file
38
src/models/ActivityGame.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
|
||||
import { Severity } from '@typegoose/typegoose/lib/internal/constants'
|
||||
import { BaseModule } from './Base'
|
||||
|
||||
/**
|
||||
* 活动游戏数据
|
||||
*/
|
||||
@dbconn()
|
||||
@index({ user: 1, activity: 1 }, { unique: false })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'activity_game', timestamps: true },
|
||||
options: { allowMixed: Severity.ALLOW },
|
||||
})
|
||||
class ActivityGameClass extends BaseModule {
|
||||
@prop()
|
||||
public user: string
|
||||
@prop()
|
||||
public activity: string
|
||||
// 0 未开启
|
||||
@prop({ default: 0 })
|
||||
public status: number
|
||||
// 拥有的ticket数量
|
||||
@prop()
|
||||
public tickets: number
|
||||
// 最后一次签到日期
|
||||
@prop()
|
||||
public lastSignDay: string
|
||||
|
||||
public toJson() {
|
||||
return {
|
||||
// @ts-ignore
|
||||
id: this._id,
|
||||
stat: this.status,
|
||||
}
|
||||
}
|
||||
}
|
||||
export const ActivityGame = getModelForClass(ActivityGameClass, { existingConnection: ActivityGameClass['db'] })
|
@ -25,6 +25,17 @@ class ScoreRecordClass extends BaseModule {
|
||||
|
||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||
public data: any
|
||||
|
||||
public toJson() {
|
||||
return {
|
||||
user: this.user,
|
||||
activity: this.activity,
|
||||
score: this.score,
|
||||
type: this.type,
|
||||
//@ts-ignore
|
||||
time: this.createdAt.getTime(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const ScoreRecord = getModelForClass(ScoreRecordClass, { existingConnection: ScoreRecordClass['db'] })
|
||||
|
47
src/models/TicketRecord.ts
Normal file
47
src/models/TicketRecord.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { Severity, getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose'
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import { BaseModule } from './Base'
|
||||
|
||||
export const DAILY_SIGN = 'daily_sign'
|
||||
// 累计签到奖励
|
||||
export const SIGN_TOTAL = 'sign_total'
|
||||
// 使用门票
|
||||
export const USE_TICKET = 'use_ticket'
|
||||
|
||||
@dbconn()
|
||||
@index({ user: 1 }, { unique: false })
|
||||
@index({ activity: 1 }, { unique: false })
|
||||
@index({ user: 1, activity: 1, type: 1 }, { unique: false })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'ticket_record', timestamps: true },
|
||||
options: { allowMixed: Severity.ALLOW },
|
||||
})
|
||||
class TicketRecordClass extends BaseModule {
|
||||
@prop({ required: true })
|
||||
public user: string
|
||||
|
||||
@prop({ required: true })
|
||||
public activity: string
|
||||
|
||||
@prop()
|
||||
public score: number
|
||||
|
||||
@prop()
|
||||
public type: string
|
||||
|
||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||
public data: any
|
||||
|
||||
public toJson() {
|
||||
return {
|
||||
user: this.user,
|
||||
activity: this.activity,
|
||||
score: this.score,
|
||||
type: this.type,
|
||||
//@ts-ignore
|
||||
time: this.createdAt.getTime(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const TicketRecord = getModelForClass(TicketRecordClass, { existingConnection: TicketRecordClass['db'] })
|
@ -26,8 +26,11 @@ export class CheckInClass extends BaseModule {
|
||||
@prop()
|
||||
public dateTag: string
|
||||
// 连签天数
|
||||
@prop({ default: 0 })
|
||||
@prop({ default: 1 })
|
||||
public count: number
|
||||
// 累计签到天数
|
||||
@prop({ default: 1 })
|
||||
public total: number
|
||||
@prop()
|
||||
public value: string
|
||||
@prop()
|
||||
@ -39,6 +42,8 @@ export class CheckInClass extends BaseModule {
|
||||
if (preDayEvent) {
|
||||
event.count = preDayEvent.count + 1
|
||||
}
|
||||
const total = await CheckIn.countDocuments({ from: event.from })
|
||||
event.total = total + 1
|
||||
return CheckIn.insertOrUpdate({ hash: event.hash }, event)
|
||||
}
|
||||
|
||||
|
@ -1,41 +1,58 @@
|
||||
import { CheckIn } from 'models/chain/CheckIn'
|
||||
import { NftHolder } from 'models/chain/NftHolder'
|
||||
import { NftStake } from 'models/chain/NftStake'
|
||||
import { getMonthBegin, getNDayAgo } from 'zutils/utils/date.util'
|
||||
|
||||
export const queryCheckInList = async (address: string, days: string | number | string[], limit: number = 0) => {
|
||||
const url = process.env.CHAIN_SVR + '/task/check_in'
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
address,
|
||||
days,
|
||||
limit,
|
||||
}),
|
||||
}).then(res => res.json())
|
||||
let query: any = { from: address.toLowerCase() }
|
||||
if (!limit) {
|
||||
if (typeof days === 'number') {
|
||||
let begin = getNDayAgo(days, true)
|
||||
query.blockTime = { $gt: (begin.getTime() / 1000) | 0 }
|
||||
} else if (typeof days === 'string') {
|
||||
if (days === '1month') {
|
||||
let date = getMonthBegin(new Date())
|
||||
query.blockTime = { $gt: (date.getTime() / 1000) | 0 }
|
||||
} else {
|
||||
query.dateTag = days
|
||||
}
|
||||
} else if (Array.isArray(days)) {
|
||||
query.dateTag = { $in: days }
|
||||
}
|
||||
}
|
||||
let records
|
||||
if (limit) {
|
||||
records = await CheckIn.find(query).sort({ _id: -1 }).limit(limit)
|
||||
} else {
|
||||
records = await CheckIn.find(query).sort({ _id: -1 })
|
||||
}
|
||||
let result = []
|
||||
for (let record of records) {
|
||||
result.push(record.toJson())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const queryCheckInSeq = async (address: string) => {
|
||||
const url = process.env.CHAIN_SVR + '/task/check_in/max_seq'
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
address,
|
||||
}),
|
||||
}).then(res => res.json())
|
||||
const record = await CheckIn.findOne({ from: address.toLowerCase() }).sort({ count: -1 })
|
||||
return record.toJson()
|
||||
}
|
||||
|
||||
export const queryBurnNftList = async (address: string, user: string, chain: number) => {
|
||||
const url = process.env.CHAIN_SVR + '/task/nft/checkburn'
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
address,
|
||||
user,
|
||||
chain,
|
||||
}),
|
||||
}).then(res => res.json())
|
||||
address = address.toLowerCase()
|
||||
user = user.toLowerCase()
|
||||
|
||||
let records = await NftHolder.find({ address, chain, user, burn: true }).sort({ blockNumber: -1 })
|
||||
let result = []
|
||||
for (let record of records) {
|
||||
result.push({
|
||||
address: record.address,
|
||||
chain: record.chain,
|
||||
user: record.user,
|
||||
tokenId: record.tokenId,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const checkHadGacha = async (user: string) => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ScoreRecord } from 'models/ScoreRecord'
|
||||
import { ZRedisClient } from 'zutils'
|
||||
import { formatDate } from 'zutils/utils/date.util'
|
||||
|
||||
const rankLevels = require('../../configs/uaw_rank_level.json')
|
||||
/**
|
||||
* 更新排行榜
|
||||
* @param param0
|
||||
@ -63,3 +63,8 @@ export const rankKey = (activity: string, date?: Date) => {
|
||||
const dateTag = formatDate(date)
|
||||
return `${activity}:score:${dateTag}`
|
||||
}
|
||||
|
||||
export const rankLevel = (score: number) => {
|
||||
const data = rankLevels.find(o => score >= o.score)
|
||||
return data.level
|
||||
}
|
||||
|
28
src/services/sign.svr.ts
Normal file
28
src/services/sign.svr.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { CheckIn } from 'models/chain/CheckIn'
|
||||
|
||||
const signCfg = require('../../configs/uaw_daily_sign.json')
|
||||
export const totalSignCfg = signCfg.total_sign.sort((a, b) => a.days - b.days)
|
||||
|
||||
// 按days倒序排列
|
||||
const totalSignCfg2 = signCfg.total_sign.sort((a, b) => b.days - a.days)
|
||||
export const seqSignCfg = signCfg.sequential_sign.sort((a, b) => a.seq - b.seq)
|
||||
let total = 0
|
||||
let seqSignCfg2 = []
|
||||
for (let i = 0, l = seqSignCfg.length; i < l; i++) {
|
||||
total += seqSignCfg[i].reward
|
||||
seqSignCfg2[l - i - 1] = { days: seqSignCfg[i].days, reward: total }
|
||||
}
|
||||
|
||||
export const totalSignScore = (days: number) => {
|
||||
const data = totalSignCfg2.find(o => days >= o.days)
|
||||
return data?.reward || 0
|
||||
}
|
||||
|
||||
export const seqSignScore = (days: number) => {
|
||||
const data = seqSignCfg2.find(o => days >= o.days)
|
||||
return data?.reward || 0
|
||||
}
|
||||
|
||||
export const checkInToday = async (address: string, dateTag: string) => {
|
||||
return CheckIn.findOne({ from: address.toLowerCase(), dateTag })
|
||||
}
|
@ -13,11 +13,7 @@ export default class BurnNft extends ITask {
|
||||
let cfg = this.activity.tasks.find((t: TaskCfg) => t.id === task.id)
|
||||
const address = cfg.params.address.toLowerCase()
|
||||
const chain = parseInt(process.env.CHAIN)
|
||||
const res = await queryBurnNftList(address, this.user.address, chain)
|
||||
if (res.errcode) {
|
||||
throw new ZError(res.errcode, res.errmsg)
|
||||
}
|
||||
const nftList = res.data
|
||||
const nftList = await queryBurnNftList(address, this.user.address, chain)
|
||||
const localNft = await NftBurnRecord.find({
|
||||
user: this.user.id,
|
||||
chain,
|
||||
|
@ -26,17 +26,16 @@ export default class DailyCheckIn extends ITask {
|
||||
cfg.type === TaskTypeEnum.DAILY
|
||||
? await queryCheckInList(address, days - 1, limit)
|
||||
: await queryCheckInSeq(address)
|
||||
if (res.errcode) {
|
||||
throw new ZError(res.errcode, res.errmsg)
|
||||
}
|
||||
|
||||
if (
|
||||
(cfg.type === TaskTypeEnum.DAILY && task.status === TaskStatusEnum.RUNNING && res.data.length >= days) ||
|
||||
(cfg.type === TaskTypeEnum.ONCE && task.status === TaskStatusEnum.RUNNING && res.data.count >= days)
|
||||
// @ts-ignore
|
||||
(cfg.type === TaskTypeEnum.DAILY && task.status === TaskStatusEnum.RUNNING && res.length >= days) ||
|
||||
// @ts-ignore
|
||||
(cfg.type === TaskTypeEnum.ONCE && task.status === TaskStatusEnum.RUNNING && res.count >= days)
|
||||
) {
|
||||
task.status = TaskStatusEnum.SUCCESS
|
||||
task.timeFinish = Date.now()
|
||||
task.data = res.data
|
||||
task.data = res
|
||||
try {
|
||||
await this.user.save()
|
||||
} catch (err) {
|
||||
@ -60,7 +59,7 @@ export default class DailyCheckIn extends ITask {
|
||||
if (cfg.type === TaskTypeEnum.DAILY) {
|
||||
const res = await queryCheckInList(this.user.address, 1, 0)
|
||||
const [taskId, dateTag] = task.id.split(':')
|
||||
let list: { day: string; time: number; count: number }[] = res.data
|
||||
let list: { day: string; time: number; count: number }[] = res
|
||||
|
||||
const countCfg = cfg.params.score.length
|
||||
let count = list.length > 0 ? list[0].count : 0
|
||||
|
Loading…
x
Reference in New Issue
Block a user