@ccw-api/api 0.1.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +271 -0
- package/README.md +324 -0
- package/dist/esm/community-web/api/v1/short_code/encode.js +25 -0
- package/dist/esm/community-web/api/v1/short_code/shortcode.test.js +5 -0
- package/dist/esm/community-web/approval/approval.test.js +5 -0
- package/dist/esm/community-web/approval/list.js +16 -0
- package/dist/esm/community-web/base/dateTime.js +10 -0
- package/dist/esm/community-web/base/dateTime.test.js +6 -0
- package/dist/esm/community-web/campaign_resource/detail.js +12 -0
- package/dist/esm/community-web/campaign_resource/detail.test.js +10 -0
- package/dist/esm/community-web/check_in_record/check_in_record.test.js +8 -0
- package/dist/esm/community-web/check_in_record/detail.js +8 -0
- package/dist/esm/community-web/check_in_record/insert.js +12 -0
- package/dist/esm/community-web/cloud_asset/search.js +25 -0
- package/dist/esm/community-web/cloud_asset/search.test.js +6 -0
- package/dist/esm/community-web/cloud_asset/user/privacy.js +11 -0
- package/dist/esm/community-web/cloud_asset/user/privacy.test.js +4 -0
- package/dist/esm/community-web/comment/page.js +26 -0
- package/dist/esm/community-web/comment/page.test.js +15 -0
- package/dist/esm/community-web/comment/page_by_topic.js +26 -0
- package/dist/esm/community-web/comment/page_by_topic.test.js +13 -0
- package/dist/esm/community-web/config/detail.js +13 -0
- package/dist/esm/community-web/creation/creation.test.js +118 -0
- package/dist/esm/community-web/creation/detail.js +14 -0
- package/dist/esm/community-web/creation/excellent/list.js +13 -0
- package/dist/esm/community-web/creation/introduction/detail.js +13 -0
- package/dist/esm/community-web/creation/like/detail.js +13 -0
- package/dist/esm/community-web/creation/loading/tips.js +11 -0
- package/dist/esm/community-web/creation/page.js +24 -0
- package/dist/esm/community-web/creation/page_by_student.js +24 -0
- package/dist/esm/community-web/creation/potential/list.js +13 -0
- package/dist/esm/community-web/creation/recommend.js +23 -0
- package/dist/esm/community-web/creation/search/page.js +24 -0
- package/dist/esm/community-web/creation/student/detail.js +13 -0
- package/dist/esm/community-web/creation/tag/list.js +12 -0
- package/dist/esm/community-web/index.js +50 -0
- package/dist/esm/community-web/short_url/create.js +15 -0
- package/dist/esm/community-web/short_url/shortUrl.test.js +4 -0
- package/dist/esm/index.js +5 -2
- package/dist/esm/queryPages.js +22 -0
- package/dist/esm/sso/index.js +5 -3
- package/dist/esm/sso/web/auth/auth.test.js +13 -61
- package/dist/esm/sso/web/auth/login-by-password.js +25 -82
- package/dist/esm/sso/web/auth/logout.js +9 -48
- package/dist/esm/sso/web/auth/logout_by_session.js +12 -0
- package/dist/node/community-web/api/v1/short_code/encode.js +29 -0
- package/dist/node/community-web/api/v1/short_code/shortcode.test.js +7 -0
- package/dist/node/community-web/approval/approval.test.js +7 -0
- package/dist/node/community-web/approval/list.js +23 -0
- package/dist/node/community-web/base/dateTime.js +17 -0
- package/dist/node/community-web/base/dateTime.test.js +8 -0
- package/dist/node/community-web/campaign_resource/detail.js +16 -0
- package/dist/node/community-web/campaign_resource/detail.test.js +12 -0
- package/dist/node/community-web/check_in_record/check_in_record.test.js +10 -0
- package/dist/node/community-web/check_in_record/detail.js +12 -0
- package/dist/node/community-web/check_in_record/insert.js +16 -0
- package/dist/node/community-web/cloud_asset/search.js +29 -0
- package/dist/node/community-web/cloud_asset/search.test.js +8 -0
- package/dist/node/community-web/cloud_asset/user/privacy.js +15 -0
- package/dist/node/community-web/cloud_asset/user/privacy.test.js +6 -0
- package/dist/node/community-web/comment/page.js +30 -0
- package/dist/node/community-web/comment/page.test.js +17 -0
- package/dist/node/community-web/comment/page_by_topic.js +30 -0
- package/dist/node/community-web/comment/page_by_topic.test.js +15 -0
- package/dist/node/community-web/config/detail.js +17 -0
- package/dist/node/community-web/creation/creation.test.js +120 -0
- package/dist/node/community-web/creation/detail.js +18 -0
- package/dist/node/community-web/creation/excellent/list.js +17 -0
- package/dist/node/community-web/creation/introduction/detail.js +17 -0
- package/dist/node/community-web/creation/like/detail.js +17 -0
- package/dist/node/community-web/creation/loading/tips.js +15 -0
- package/dist/node/community-web/creation/page.js +28 -0
- package/dist/node/community-web/creation/page_by_student.js +28 -0
- package/dist/node/community-web/creation/potential/list.js +17 -0
- package/dist/node/community-web/creation/recommend.js +27 -0
- package/dist/node/community-web/creation/search/page.js +28 -0
- package/dist/node/community-web/creation/student/detail.js +17 -0
- package/dist/node/community-web/creation/tag/list.js +16 -0
- package/dist/node/community-web/index.js +53 -0
- package/dist/node/community-web/short_url/create.js +19 -0
- package/dist/node/community-web/short_url/shortUrl.test.js +6 -0
- package/dist/node/index.js +6 -2
- package/dist/node/queryPages.js +26 -0
- package/dist/node/sso/index.js +4 -2
- package/dist/node/sso/web/auth/auth.test.js +15 -63
- package/dist/node/sso/web/auth/login-by-password.js +25 -82
- package/dist/node/sso/web/auth/logout.js +9 -48
- package/dist/node/sso/web/auth/logout_by_session.js +16 -0
- package/package.json +7 -2
- package/tsconfig.json +5 -3
- package/dist/esm/types.js +0 -1
- package/dist/node/types.js +0 -2
- package/dist/types/index.d.ts +0 -10
- package/dist/types/sso/index.d.ts +0 -6
- package/dist/types/sso/web/auth/login-by-password.d.ts +0 -38
- package/dist/types/sso/web/auth/logout.d.ts +0 -3
- package/dist/types/types.d.ts +0 -17
- package/jest.config.ts +0 -13
- package/src/index.ts +0 -8
- package/src/sso/index.ts +0 -7
- package/src/sso/web/auth/auth.test.ts +0 -16
- package/src/sso/web/auth/login-by-password.ts +0 -58
- package/src/sso/web/auth/logout.ts +0 -9
- package/src/types.ts +0 -18
package/AGENT.md
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# API 编写规范
|
|
2
|
+
|
|
3
|
+
## 目录结构
|
|
4
|
+
|
|
5
|
+
API 文件应按照后端服务域名进行分组,文件路径与 API URL 路径保持一致。
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── sso/ # sso.ccw.site 服务
|
|
10
|
+
│ └── index.ts # 导出该服务的所有 API
|
|
11
|
+
├── community-web/ # community-web.ccw.site 服务
|
|
12
|
+
│ └── index.ts
|
|
13
|
+
├── index.ts # 根导出
|
|
14
|
+
└── types/ # 公共类型定义
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 文件命名规范
|
|
18
|
+
|
|
19
|
+
- 使用 `kebab-case`(短横线分隔)命名文件
|
|
20
|
+
- 文件名应为 API endpoint名称,如 `login-by-password.ts`、`create-short-url.ts`
|
|
21
|
+
- 测试文件命名为 `${功能}.test.ts`,放置在同一目录下
|
|
22
|
+
|
|
23
|
+
## 代码结构规范
|
|
24
|
+
|
|
25
|
+
每个 API 文件应遵循以下顺序:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// 1. 导入依赖
|
|
29
|
+
import { ccwAxios } from "@ccw-api/axios";
|
|
30
|
+
import { ApiResponse, MongoDBId } from "types/api";
|
|
31
|
+
|
|
32
|
+
// 2. 导出 URL(用于调试和测试)
|
|
33
|
+
export const url = "https://domain.ccw.site/path/to/api";
|
|
34
|
+
|
|
35
|
+
// 3. 请求类型定义 优先寻找已在types中定义的类型,oid studentId等key的类型固定为MongoDBId
|
|
36
|
+
export type Req = {
|
|
37
|
+
field1: string;
|
|
38
|
+
field2: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// 4. 响应类型定义
|
|
42
|
+
export type Res<T = any> = {
|
|
43
|
+
data: T;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// 5. 辅助类型或接口(如需要)
|
|
47
|
+
export type ExtraInfo = {
|
|
48
|
+
detail: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// 6. JSDoc 注释
|
|
52
|
+
/**
|
|
53
|
+
* API 功能描述
|
|
54
|
+
* @param {string} param1 参数1说明
|
|
55
|
+
* @param {number} param2 参数2说明
|
|
56
|
+
* @returns {Promise<Res>} 返回值说明
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
// 7. 导出函数
|
|
60
|
+
export async function apiFunctionName(
|
|
61
|
+
param1: string,
|
|
62
|
+
param2: number,
|
|
63
|
+
): Promise<Res> {
|
|
64
|
+
const req: Req = { field1: param1, field2: param2 };
|
|
65
|
+
return await ccwAxios
|
|
66
|
+
.post<ApiResponse<Res>>(url, req)
|
|
67
|
+
.then((res) => res.data.body);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 类型定义规范
|
|
72
|
+
|
|
73
|
+
### 通用类型
|
|
74
|
+
|
|
75
|
+
项目提供以下公共类型,应优先使用:
|
|
76
|
+
|
|
77
|
+
- `ApiResponse<T>` - 统一响应格式
|
|
78
|
+
- `MongoDBId` - MongoDB ObjectID 类型
|
|
79
|
+
- `CachedOssUrl` - OSS 缓存 URL 类型
|
|
80
|
+
- `HexSecTimeStamp` - 十六进制秒级时间戳
|
|
81
|
+
|
|
82
|
+
### 请求类型
|
|
83
|
+
|
|
84
|
+
- 使用 `Req` 命名请求参数类型
|
|
85
|
+
- 定义所有必填字段和可选字段
|
|
86
|
+
- 使用字面量类型限制枚举值,如 `clientCode: "STUDY_COMMUNITY"`
|
|
87
|
+
|
|
88
|
+
### 响应类型
|
|
89
|
+
|
|
90
|
+
- 使用 `Res` 命名响应类型
|
|
91
|
+
- 支持泛型参数以适应不同响应结构,如 `Res<Extra = string>`
|
|
92
|
+
- 直接定义响应体结构,无需嵌套 `body`(由 `ApiResponse` 包装)
|
|
93
|
+
|
|
94
|
+
## 函数编写规范
|
|
95
|
+
|
|
96
|
+
### 参数处理
|
|
97
|
+
|
|
98
|
+
- 函数参数应清晰命名,避免使用缩写
|
|
99
|
+
- 将复杂对象参数拆分为多个简单参数,提高可读性
|
|
100
|
+
- 提供合理的默认值,如 `reqExtra: ReqExtra = { device: "Node", browser: "Node.js" }`
|
|
101
|
+
|
|
102
|
+
### 请求封装
|
|
103
|
+
|
|
104
|
+
- 使用 `ccwAxios` 发送请求,不要直接使用 `axios`
|
|
105
|
+
- 将请求体封装为 `Req` 类型变量后再发送
|
|
106
|
+
- 使用 `satisfies Req` 确保请求体类型正确
|
|
107
|
+
|
|
108
|
+
### 响应处理
|
|
109
|
+
|
|
110
|
+
- 使用 `ApiResponse<T>` 包装响应类型
|
|
111
|
+
- 通过 `.then((res) => res.data.body)` 直接返回响应体
|
|
112
|
+
- 对于需要特殊处理的响应数据(如 JSON 解析),在函数内部完成
|
|
113
|
+
|
|
114
|
+
## 导入规范
|
|
115
|
+
|
|
116
|
+
### 统一导入方式
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// 正确 - 使用命名导入
|
|
120
|
+
import { ccwAxios } from "@ccw-api/axios";
|
|
121
|
+
|
|
122
|
+
// 错误 - 默认导入方式不一致
|
|
123
|
+
import ccwAxios from "@ccw-api/axios";
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 类型导入
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// 正确 - 使用项目路径别名
|
|
130
|
+
import { ApiResponse, MongoDBId } from "types/api";
|
|
131
|
+
import { queryPage } from "src/queryPages";
|
|
132
|
+
|
|
133
|
+
// 错误 - 相对路径不一致或太长
|
|
134
|
+
import { ApiResponse } from "../../../types";
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 注释规范
|
|
138
|
+
|
|
139
|
+
### JSDoc 注释
|
|
140
|
+
|
|
141
|
+
所有导出函数必须添加 JSDoc 注释:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
/**
|
|
145
|
+
* 通过密码登录
|
|
146
|
+
* @param {string} loginKey 用户名
|
|
147
|
+
* @param {string} password 密码
|
|
148
|
+
* @param {ReqExtra} reqExtra 设备和浏览器信息(可选)
|
|
149
|
+
* @returns {Promise<Res<Extra>>} 登录结果
|
|
150
|
+
*/
|
|
151
|
+
export async function loginByPassword(
|
|
152
|
+
loginKey: string,
|
|
153
|
+
password: string,
|
|
154
|
+
reqExtra?: ReqExtra,
|
|
155
|
+
): Promise<Res<Extra>> {
|
|
156
|
+
// ...
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 类型注释
|
|
161
|
+
|
|
162
|
+
对于复杂类型,添加适当的注释说明:
|
|
163
|
+
如果该属性可能为url,请立即询问开发者
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
export type ApprovalStatus<Tid extends number, Tn extends string> = {
|
|
167
|
+
/**
|
|
168
|
+
* 浮于iconLink之上的icon
|
|
169
|
+
*/
|
|
170
|
+
mediumImage: CachedOssUrl;
|
|
171
|
+
/**
|
|
172
|
+
* 有时会包含tag的名称,如"Gandi开发者"
|
|
173
|
+
*/
|
|
174
|
+
iconLink: CachedOssUrl;
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## 测试规范
|
|
179
|
+
|
|
180
|
+
### 测试文件位置
|
|
181
|
+
|
|
182
|
+
测试文件与 API 文件放在同一目录下,命名为 `${功能}.test.ts`。
|
|
183
|
+
|
|
184
|
+
### 测试内容
|
|
185
|
+
|
|
186
|
+
测试应覆盖:
|
|
187
|
+
|
|
188
|
+
- 正常流程返回值验证
|
|
189
|
+
- 异常情况错误信息验证
|
|
190
|
+
- 参数边界情况
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { getApprovalTags } from "./list";
|
|
194
|
+
|
|
195
|
+
test("test approval list", async () => {
|
|
196
|
+
const tags = await getApprovalTags("63c2807d669fa967f17f5559");
|
|
197
|
+
expect(tags.find((v) => v.approvalTagId == 235).approvalTagName).toEqual(
|
|
198
|
+
"Gandi 开发者",
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## 导出规范
|
|
204
|
+
|
|
205
|
+
### 服务入口导出
|
|
206
|
+
|
|
207
|
+
每个服务目录(如 `sso/`、`community-web/`)应有 `index.ts`,统一导出该服务的所有 API:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { loginByPassword } from "./web/auth/login-by-password";
|
|
211
|
+
import { logout } from "./web/auth/logout";
|
|
212
|
+
|
|
213
|
+
export const sso = {
|
|
214
|
+
loginByPassword,
|
|
215
|
+
logout,
|
|
216
|
+
};
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 根导出
|
|
220
|
+
|
|
221
|
+
`src/index.ts` 导出所有服务:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { sso } from "./sso";
|
|
225
|
+
import { communityWeb } from "./community-web";
|
|
226
|
+
export type * from "./types";
|
|
227
|
+
|
|
228
|
+
export { sso, communityWeb };
|
|
229
|
+
|
|
230
|
+
export default {
|
|
231
|
+
sso,
|
|
232
|
+
communityWeb,
|
|
233
|
+
};
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## 错误处理
|
|
237
|
+
|
|
238
|
+
- 使用 `@ccw-api/axios` 的拦截器自动处理错误
|
|
239
|
+
- 测试时验证错误信息是否符合预期
|
|
240
|
+
- 不要在 API 函数中捕获错误,让调用方处理
|
|
241
|
+
|
|
242
|
+
## 最佳实践
|
|
243
|
+
|
|
244
|
+
1. **URL 一致性**:`url` 常量应与实际 API 地址完全一致
|
|
245
|
+
2. **类型安全**:使用 TypeScript 泛型提供灵活的类型支持
|
|
246
|
+
3. **函数命名**:使用 `camelCase`,前缀描述操作(如 `get`, `create`, `login`)
|
|
247
|
+
4. **单一职责**:每个文件只包含一个 API 函数
|
|
248
|
+
5. **可测试性**:导出 `url` 常量便于测试和调试
|
|
249
|
+
|
|
250
|
+
### 分页请求
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import { DEFAULT_PAGE_ARGS, queryPage } from "src/queryPages";
|
|
254
|
+
|
|
255
|
+
// 其他一般请求配置
|
|
256
|
+
|
|
257
|
+
export async function getDonatedRecordRanking(
|
|
258
|
+
args: any, // 其他参数
|
|
259
|
+
pageArgs_: Partial<PageArgs> = {},
|
|
260
|
+
): Promise<Res> {
|
|
261
|
+
const pageArgs = {
|
|
262
|
+
...DEFAULT_PAGE_ARGS,
|
|
263
|
+
...pageArgs_,
|
|
264
|
+
};
|
|
265
|
+
const queryUrl = queryPage(url, pageArgs);
|
|
266
|
+
const req: Req = { ... }; // 构造请求
|
|
267
|
+
return await ccwAxios
|
|
268
|
+
.post<ApiResponse<Res>>(queryUrl, req)
|
|
269
|
+
.then((res) => res.data.body);
|
|
270
|
+
}
|
|
271
|
+
```
|
package/README.md
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# @ccw-api/api
|
|
2
|
+
|
|
3
|
+
CCW(创作社区)前后端接口 SDK,整合 `sso.ccw.site` 与 `community-web.ccw.site`
|
|
4
|
+
两大服务的常用 API,提供完整的 TypeScript 类型与开箱即用的分页封装。
|
|
5
|
+
|
|
6
|
+
- 包格式:**ESM + CJS + .d.ts 三产物**同时产出
|
|
7
|
+
- 类型:所有请求 / 响应字段**强类型**,后端 key 固定约束(如 `MongoDBId`、`CNameOssUrl`、`UUID`)
|
|
8
|
+
- 分页:统一返回 `PagesRes<T>`(含 `totalNum` / `totalPages` / `sortField` 等元信息)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @ccw-api/api
|
|
16
|
+
# 或
|
|
17
|
+
pnpm add @ccw-api/api
|
|
18
|
+
# 或
|
|
19
|
+
yarn add @ccw-api/api
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> 唯一运行时依赖:`@ccw-api/axios`(内置鉴权 header 注入的 axios 封装)。
|
|
23
|
+
> `@ccw-api/api` 已将常用的 token 设置方法 **`setToken` 重新导出**,无需再单独依赖 axios 包。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 快速开始
|
|
28
|
+
|
|
29
|
+
### 1. 初始化 Token(前置条件)
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { setToken } from "@ccw-api/api";
|
|
33
|
+
|
|
34
|
+
// 直接传入后端下发的 session token
|
|
35
|
+
setToken("abcdefgfoo");
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
所有 `@ccw-api/api` 的方法都会复用同一个 axios 单例,**调用 API 时无需再传入鉴权参数**。
|
|
39
|
+
|
|
40
|
+
### 2. 调用方式
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { sso, communityWeb } from "@ccw-api/api"; // ✅ 推荐:具名导入
|
|
44
|
+
// 或
|
|
45
|
+
import api from "@ccw-api/api"; // ✅ default 导入:api.sso / api.communityWeb
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 模块一:SSO(sso.ccw.site)
|
|
51
|
+
|
|
52
|
+
账号认证相关,共 3 个 API:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const { loginByPassword, logout, logoutBySession } = sso;
|
|
56
|
+
|
|
57
|
+
// 账号密码登录
|
|
58
|
+
await loginByPassword("student_number", "xxxxx", {
|
|
59
|
+
device: "WEB",
|
|
60
|
+
browser: "Chrome",
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// 登出当前 session
|
|
64
|
+
await logout();
|
|
65
|
+
|
|
66
|
+
// 强制某个 session 下线用于登陆设备管理
|
|
67
|
+
await logoutBySession("64...session_oid");
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 模块二:Community-Web(community-web.ccw.site)
|
|
73
|
+
|
|
74
|
+
覆盖作品 / 学生 / 星球 / 评论 / 通知 / 任务 / 表情 / 签到 / 云资产 / 学科专区 等业务领域,共 **68 个 API**。
|
|
75
|
+
下面是常用场景示例。
|
|
76
|
+
|
|
77
|
+
### 学生与作品
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// 拉取学生档案
|
|
81
|
+
const profile = await communityWeb.getStudentProfile(
|
|
82
|
+
"63c2807d669fa967f17f5559",
|
|
83
|
+
);
|
|
84
|
+
// ^? StudentOverview(包含头像、简介、统计字段等)
|
|
85
|
+
|
|
86
|
+
// 我的创作分
|
|
87
|
+
const score = await communityWeb.getCreatorScore();
|
|
88
|
+
console.log(score.rank); // "ORDINARY" | "HACKER" | "ADVANCED"
|
|
89
|
+
|
|
90
|
+
// 分页拉取某学生的作品列表(含 totalNum / totalPages)
|
|
91
|
+
const creations = await communityWeb.getCreationsByStudent(
|
|
92
|
+
"63c2807d669fa967f17f5559",
|
|
93
|
+
{ page: 1, perPage: 12, sortField: "createdAt", sortType: "DESC" },
|
|
94
|
+
);
|
|
95
|
+
// creations: PagesRes<CreationSimple>
|
|
96
|
+
console.log(creations.totalNum, creations.data[0].title);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 星球(HashTag)与作品关联
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// 查询某作品加入了哪些星球
|
|
103
|
+
const planets = await communityWeb.getPlanetsOfCreation(
|
|
104
|
+
"69929185f8d6142487fd4b2e",
|
|
105
|
+
);
|
|
106
|
+
// planets: HashTagCreationRelation[]
|
|
107
|
+
// planets[0].rank → "ORDINARY"(星球审核等级,与创作分 CreatorScore.rank 概念不同)
|
|
108
|
+
|
|
109
|
+
// 查询我加入某星球的作品分页(排除一个星球 + 过滤状态)
|
|
110
|
+
const mine = await communityWeb.getMyHashTagCreations(
|
|
111
|
+
"蔚蓝档案",
|
|
112
|
+
["PUBLISHED"],
|
|
113
|
+
{ page: 1, perPage: 8, sortField: "lastPassedAt", sortType: "DESC" },
|
|
114
|
+
);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 通知(细化了 15 种 contentCategory)
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import type {
|
|
121
|
+
NotificationContent,
|
|
122
|
+
PostCommentContent,
|
|
123
|
+
FollowedContent,
|
|
124
|
+
} from "@ccw-api/api";
|
|
125
|
+
|
|
126
|
+
const page = await communityWeb.getNotificationPage("COMMENT_TO_ME", {
|
|
127
|
+
page: 1,
|
|
128
|
+
perPage: 20,
|
|
129
|
+
});
|
|
130
|
+
// page.data[].content 类型是 NotificationContent(判别联合)
|
|
131
|
+
// 用 contentCategory 做类型收窄:
|
|
132
|
+
|
|
133
|
+
for (const notif of page.data) {
|
|
134
|
+
switch (notif.contentCategory) {
|
|
135
|
+
case "POST_COMMENT": {
|
|
136
|
+
const c = notif.content as PostCommentContent;
|
|
137
|
+
console.log("评论者:", c.sender, "文章:", c.subject_outline);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case "FOLLOWED": {
|
|
141
|
+
const c = notif.content as FollowedContent;
|
|
142
|
+
console.log("新粉丝:", c.sender, c.sender_id);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "CREATION_SHARE":
|
|
146
|
+
case "SESSION_CREATED":
|
|
147
|
+
case "COMMUNITY_ACTIVITY":
|
|
148
|
+
case "POST_VISIBILITY_CHANGED":
|
|
149
|
+
// ……共 15 种,可按 IDE 补全逐项处理
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 拉取所有分组的未读统计
|
|
154
|
+
const stats = await communityWeb.getNotificationStats();
|
|
155
|
+
// stats.creationInteraction stats.commentToMe stats.followMe ……
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 任务、表情、金币、签到
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
// 我的任务列表
|
|
162
|
+
const tasks = await communityWeb.getMyTasks();
|
|
163
|
+
|
|
164
|
+
// 领取某个任务奖励
|
|
165
|
+
await communityWeb.acceptAward("61273ccf1730f4308e853f6a");
|
|
166
|
+
|
|
167
|
+
// 个人金币余额(充值 / 赠送 / 提现 三栏分离)
|
|
168
|
+
const coin = await communityWeb.getPersonalCurrencyAccount();
|
|
169
|
+
// coin: { internalCurrencyBalance, topUpCurrencyBalance, withdrawCurrencyBalance }
|
|
170
|
+
|
|
171
|
+
// 全部表情包 + 分页 + 分类
|
|
172
|
+
const [allEmoji, emojiPage, categories] = await Promise.all([
|
|
173
|
+
communityWeb.getAllEmojis(),
|
|
174
|
+
communityWeb.getEmojiPage("ENABLED", { page: 1, perPage: 50 }),
|
|
175
|
+
communityWeb.getEmojiCategoryList(),
|
|
176
|
+
]);
|
|
177
|
+
|
|
178
|
+
// 签到
|
|
179
|
+
await communityWeb.insertCheckInRecord(); // 今日打卡
|
|
180
|
+
const history = await communityWeb.getCheckInRecords();
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 黑名单、封禁、禁言查询
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const STUDENT = "63c2807d669fa967f17f5559";
|
|
187
|
+
|
|
188
|
+
// 轻量:是否拉黑(只返回 NOT_BLOCKED / BLOCKING)
|
|
189
|
+
const s = await communityWeb.getStudentBlockStatus(STUDENT);
|
|
190
|
+
|
|
191
|
+
// 详细:完整的 BlockActionRecord(拉黑动作记录,oid / createdAt / fromEntityId)
|
|
192
|
+
const detail = await communityWeb.getStudentBlockRecordDetail(STUDENT);
|
|
193
|
+
|
|
194
|
+
// 封禁详情(带泛型,locked=false 时其他字段全为 null)
|
|
195
|
+
const locked = await communityWeb.getLockedUserDetail<true>(
|
|
196
|
+
"642c0e8a59230841adf62406",
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// 禁言详情(studentNumber 是 244373873 这种数字字符串,null 表示未禁言)
|
|
200
|
+
const muted = await communityWeb.getMutedUserDetail("244373873");
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 通用工具型 API
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
// 生成短链
|
|
207
|
+
await communityWeb.createShortUrl("https://ccw.site/detail/xxx");
|
|
208
|
+
|
|
209
|
+
// 生成邀请码(api/v1/short_code/encode)
|
|
210
|
+
await communityWeb.encodeShortCode("hello world", 1, 6);
|
|
211
|
+
|
|
212
|
+
// 服务器时间
|
|
213
|
+
await communityWeb.getTime();
|
|
214
|
+
|
|
215
|
+
// 埋点事件上报
|
|
216
|
+
await communityWeb.sendEvent("creation_detail_view_6880873d2211fa69e41c9d19");
|
|
217
|
+
|
|
218
|
+
// 广告横幅
|
|
219
|
+
await communityWeb.getLeafletsItemList(1001);
|
|
220
|
+
|
|
221
|
+
// 学科专区(分页 / 按频道)
|
|
222
|
+
await communityWeb.getSubjectAreaPage({ page: 1, perPage: 20 });
|
|
223
|
+
await communityWeb.getSubjectAreaPageByChannel("PRIMARY");
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 类型系统
|
|
229
|
+
|
|
230
|
+
### 原语类型(`types/api.d.ts`)
|
|
231
|
+
|
|
232
|
+
可直接从包顶层作为 `type` 导入:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import type {
|
|
236
|
+
MongoDBId, // 24 位 hex 字符串(ObjectId)
|
|
237
|
+
CNameOssUrl, // https://m.ccw.site/... 或 https://m.xiguacity.cn/... OSS URL
|
|
238
|
+
UUID, // `${string}-${string}-${string}-${string}-${string}` 类型体操
|
|
239
|
+
ApiResponse, // { code, msg, body: T } 后端统一响应壳(SDK 内部已拆)
|
|
240
|
+
} from "@ccw-api/api";
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 领域命名空间
|
|
244
|
+
|
|
245
|
+
每个 `types/*.d.ts` 作为一个命名空间重新导出,**避免同名类型冲突**(典型如 `Student` vs `StudentOverview` vs `Account.Types.Student`):
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import type {
|
|
249
|
+
Api, // 原语类型(MongoDBId / CNameOssUrl / UUID / ApiResponse ...)
|
|
250
|
+
UserData, // Student / StudentOverview / CreatorScore / UserCard ...
|
|
251
|
+
Creation, // Creation / HashTag / HashTagCreationRank / CreationRelease ...
|
|
252
|
+
Comment, // Comment / CommentStatus ...
|
|
253
|
+
Notification, // Notification / NotificationSenderInfo / NotificationGroup ...
|
|
254
|
+
NotificationContent, // 15 种通知 content 判别联合
|
|
255
|
+
Pages, // PageArgs / PagesRes
|
|
256
|
+
Session, // StudentSession / SessionArea
|
|
257
|
+
Account, // AccountTypes
|
|
258
|
+
Approval, // ApprovalTag
|
|
259
|
+
} from "@ccw-api/api";
|
|
260
|
+
|
|
261
|
+
type Score = UserData.CreatorScore["rank"]; // "ORDINARY" | "HACKER" | "ADVANCED"
|
|
262
|
+
type PlanetRank = Creation.HashTagCreationRank; // "ORDINARY"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 分页约定
|
|
268
|
+
|
|
269
|
+
所有分页 API 都遵循同一套形状(详见 `types/pages.d.ts` + `src/queryPages.ts`):
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import type { PageArgs, PagesRes } from "@ccw-api/api";
|
|
273
|
+
|
|
274
|
+
// 入参 Partial<PageArgs<SortField>>,默认 { page:1, perPage:20, sortType:"DESC" }
|
|
275
|
+
// 出参 PagesRes<T> 完整结构:
|
|
276
|
+
const p: PagesRes<Creation.Creation> = {
|
|
277
|
+
data: [], // 当前页数据
|
|
278
|
+
page: 1,
|
|
279
|
+
perPage: 20,
|
|
280
|
+
offset: 0,
|
|
281
|
+
totalNum: 1234, // 总条数 —— 之前版本被截断,目前 SDK 已完整透出
|
|
282
|
+
totalPages: 62, // 总页数
|
|
283
|
+
sortField: "createdAt",
|
|
284
|
+
sortType: "DESC",
|
|
285
|
+
};
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## 开发 & 发布
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
# 三产物构建(node / esm / .d.ts)
|
|
294
|
+
npm run build
|
|
295
|
+
|
|
296
|
+
# 跑测试(Jest 30 + ts-jest,67 suites / 72 tests)
|
|
297
|
+
npm test
|
|
298
|
+
npm run test:dev # watch 模式
|
|
299
|
+
|
|
300
|
+
# 文档(TypeDoc,生成到 doc/ 或默认输出目录)
|
|
301
|
+
npm run doc
|
|
302
|
+
npm run doc:dev
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`npm publish` 会自动触发 `prepublishOnly` → **build + test** 全跑一遍,失败会阻断发布。
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 目录与规范
|
|
310
|
+
|
|
311
|
+
所有 API 文件严格遵循 `AGENT.md` 规范:
|
|
312
|
+
|
|
313
|
+
- **按域名分组**:`src/sso` / `src/community-web`,内部子目录对应 URL 路径
|
|
314
|
+
- **单文件单接口**:文件名 `kebab-case`,对应 endpoint 最后一段
|
|
315
|
+
- **7 段式模板**(每个 API 文件内部固定顺序):
|
|
316
|
+
1. `import`
|
|
317
|
+
2. `export const url = "https://..."`(调试 & 测试直接引用)
|
|
318
|
+
3. `export type Req = { ... }`
|
|
319
|
+
4. `export type Res = { ... }`(响应体内部,不含 `ApiResponse` 外层)
|
|
320
|
+
5. 辅助类型
|
|
321
|
+
6. JSDoc(`@param` / `@returns`)
|
|
322
|
+
7. `export async function xxx(): Promise<Res>`
|
|
323
|
+
|
|
324
|
+
新增 API 时,**最后记得在 `src/community-web/index.ts` 里 `import + 对象字面量聚合导出`**,否则外部访问不到。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ccwAxios } from "@ccw-api/axios";
|
|
2
|
+
export const url = "https://community-web.ccw.site/api/v1/short_code/encode";
|
|
3
|
+
/**
|
|
4
|
+
* 生成作品邀请码
|
|
5
|
+
* @param {MongoDBId} inviterId 邀请人id
|
|
6
|
+
* @param {CachedOssUrl} inviterAvatar 邀请人头像
|
|
7
|
+
* @param {string} inviterName 邀请人用户名
|
|
8
|
+
* @param {MongoDBId} creationOid 作品id
|
|
9
|
+
* @returns {Promise<Res>}
|
|
10
|
+
*/
|
|
11
|
+
export async function encodeShortCode(inviterId, inviterAvatar, inviterName, creationOid) {
|
|
12
|
+
const req = {
|
|
13
|
+
origin: {
|
|
14
|
+
inviterAvatar,
|
|
15
|
+
inviterName,
|
|
16
|
+
inviterId,
|
|
17
|
+
inviterType: "STUDENT",
|
|
18
|
+
entrance: "create_detail",
|
|
19
|
+
creationOid,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
return await ccwAxios
|
|
23
|
+
.post(url, req)
|
|
24
|
+
.then((res) => res.data.body);
|
|
25
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { encodeShortCode } from "./encode";
|
|
2
|
+
test("test shortcode", async () => {
|
|
3
|
+
const res = await encodeShortCode("63c2807d669fa967f17f5559", "https://m.ccw.site/user_projects_assets/244373873/3fc7886d05aed9f369d2c0bb2d677fc0.jpeg", "孟夫子驾到", "6386e11cfc1ed14684de07f8");
|
|
4
|
+
expect(res.code).toHaveLength("yZdXmD3PvwVPA30W".length);
|
|
5
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import ccwAxios from "@ccw-api/axios";
|
|
2
|
+
export const url = "https://community-web.ccw.site/approval/list";
|
|
3
|
+
/**
|
|
4
|
+
* 获取特定用户的勋章数据
|
|
5
|
+
* @param {MongoDBId} studentOid 用户id
|
|
6
|
+
* @returns {Promise<Res<Tid, Tn>>} 勋章数据
|
|
7
|
+
*/
|
|
8
|
+
export async function getApprovalTags(studentOid) {
|
|
9
|
+
const req = {
|
|
10
|
+
objectId: studentOid,
|
|
11
|
+
objectType: "STUDENT",
|
|
12
|
+
};
|
|
13
|
+
return await ccwAxios
|
|
14
|
+
.post(url, req)
|
|
15
|
+
.then((res) => res.data.body);
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import ccwAxios from "@ccw-api/axios";
|
|
2
|
+
export const url = "https://community-web.ccw.site/base/dateTime";
|
|
3
|
+
/**
|
|
4
|
+
* @returns {Promise<number>} 当前时间戳,相当于Date.now()
|
|
5
|
+
*/
|
|
6
|
+
export async function getTime() {
|
|
7
|
+
return ccwAxios
|
|
8
|
+
.post(url, undefined)
|
|
9
|
+
.then((res) => res.data.body);
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ccwAxios } from "@ccw-api/axios";
|
|
2
|
+
export const url = "https://community-web.ccw.site/campaign_resource/detail";
|
|
3
|
+
/**
|
|
4
|
+
* 不知道这个接口有什么用,请查看https://www.ccw.site/pages/tags自行寻找用法
|
|
5
|
+
* @param {K} keyword 活动关键词
|
|
6
|
+
* @returns {Promise<Res<K>>}
|
|
7
|
+
*/
|
|
8
|
+
export async function getCampaignResources(keyword) {
|
|
9
|
+
return await ccwAxios
|
|
10
|
+
.post(url, { keyword })
|
|
11
|
+
.then((res) => res.data.body);
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getCampaignResources } from "./detail";
|
|
2
|
+
test("get campaign resources with tags keyword", async () => {
|
|
3
|
+
const res = await getCampaignResources("tags");
|
|
4
|
+
expect(res.keyword).toBe("tags");
|
|
5
|
+
expect(res.resourceType).toBe("SUBJECT_AREA");
|
|
6
|
+
});
|
|
7
|
+
test("get campaign resources with mmo keyword", async () => {
|
|
8
|
+
const res = await getCampaignResources("mmo");
|
|
9
|
+
expect(res.keyword).toBe("mmo");
|
|
10
|
+
});
|