@ccw-api/api 0.4.2 → 0.5.0

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.
Files changed (76) hide show
  1. package/README.md +82 -10
  2. package/dist/esm/community-web/comment/page_by_topic.js +1 -1
  3. package/dist/esm/community-web/great_creation/update.js +13 -0
  4. package/dist/esm/community-web/great_creation/update.test.js +4 -0
  5. package/dist/esm/community-web/index.js +8 -0
  6. package/dist/esm/community-web/smart_contract/list.js +14 -0
  7. package/dist/esm/community-web/smart_contract/list.test.js +4 -0
  8. package/dist/esm/community-web/user_package/status.js +14 -0
  9. package/dist/esm/community-web/user_package/status.test.js +4 -0
  10. package/dist/esm/community-web/user_package/user_product.js +29 -0
  11. package/dist/esm/community-web/user_package/user_product.test.js +9 -0
  12. package/dist/esm/community-web-cloud-database/cloud_variable/save.js +29 -0
  13. package/dist/esm/community-web-cloud-database/cloud_variable/save.test.js +13 -0
  14. package/dist/esm/community-web-cloud-database/index.js +5 -0
  15. package/dist/esm/gandi-main/creation/leaderboards/{oid}/records.js +28 -0
  16. package/dist/esm/gandi-main/creation/leaderboards/{oid}/records.test.js +11 -0
  17. package/dist/esm/gandi-main/index.js +3 -0
  18. package/dist/esm/index.js +5 -1
  19. package/dist/esm/op-parent-api/captcha/check.js +15 -0
  20. package/dist/esm/op-parent-api/captcha/check.test.js +12 -0
  21. package/dist/esm/op-parent-api/captcha/create.js +13 -0
  22. package/dist/esm/op-parent-api/captcha/create.test.js +13 -0
  23. package/dist/esm/op-parent-api/index.js +6 -0
  24. package/dist/esm/sso/index.js +4 -0
  25. package/dist/esm/sso/web/auth/assistant/captcha/v2/create.js +17 -0
  26. package/dist/esm/sso/web/auth/assistant/captcha/v2/create.test.js +8 -0
  27. package/dist/esm/sso/web/auth/login-by-password.js +4 -3
  28. package/dist/esm/sso/web/auth/v3/login/by-phone.js +33 -0
  29. package/dist/esm/sso/web/auth/v3/login/by-phone.test.js +7 -0
  30. package/dist/node/community-web/comment/page_by_topic.js +1 -1
  31. package/dist/node/community-web/great_creation/update.js +17 -0
  32. package/dist/node/community-web/great_creation/update.test.js +6 -0
  33. package/dist/node/community-web/index.js +8 -0
  34. package/dist/node/community-web/smart_contract/list.js +18 -0
  35. package/dist/node/community-web/smart_contract/list.test.js +6 -0
  36. package/dist/node/community-web/user_package/status.js +18 -0
  37. package/dist/node/community-web/user_package/status.test.js +6 -0
  38. package/dist/node/community-web/user_package/user_product.js +33 -0
  39. package/dist/node/community-web/user_package/user_product.test.js +11 -0
  40. package/dist/node/community-web-cloud-database/cloud_variable/save.js +34 -0
  41. package/dist/node/community-web-cloud-database/cloud_variable/save.test.js +15 -0
  42. package/dist/node/community-web-cloud-database/index.js +8 -0
  43. package/dist/node/gandi-main/creation/leaderboards/{oid}/records.js +33 -0
  44. package/dist/node/gandi-main/creation/leaderboards/{oid}/records.test.js +13 -0
  45. package/dist/node/gandi-main/index.js +3 -0
  46. package/dist/node/index.js +7 -1
  47. package/dist/node/op-parent-api/captcha/check.js +19 -0
  48. package/dist/node/op-parent-api/captcha/check.test.js +14 -0
  49. package/dist/node/op-parent-api/captcha/create.js +17 -0
  50. package/dist/node/op-parent-api/captcha/create.test.js +15 -0
  51. package/dist/node/op-parent-api/index.js +9 -0
  52. package/dist/node/sso/index.js +4 -0
  53. package/dist/node/sso/web/auth/assistant/captcha/v2/create.js +21 -0
  54. package/dist/node/sso/web/auth/assistant/captcha/v2/create.test.js +10 -0
  55. package/dist/node/sso/web/auth/login-by-password.js +4 -3
  56. package/dist/node/sso/web/auth/v3/login/by-phone.js +37 -0
  57. package/dist/node/sso/web/auth/v3/login/by-phone.test.js +9 -0
  58. package/dist/types/community-web/comment/page_by_topic.d.ts +1 -1
  59. package/dist/types/community-web/great_creation/update.d.ts +12 -0
  60. package/dist/types/community-web/index.d.ts +8 -0
  61. package/dist/types/community-web/smart_contract/list.d.ts +49 -0
  62. package/dist/types/community-web/user_package/status.d.ts +13 -0
  63. package/dist/types/community-web/user_package/user_product.d.ts +40 -0
  64. package/dist/types/community-web-cloud-database/cloud_variable/save.d.ts +26 -0
  65. package/dist/types/community-web-cloud-database/index.d.ts +5 -0
  66. package/dist/types/gandi-main/creation/leaderboards/{oid}/records.d.ts +62 -0
  67. package/dist/types/gandi-main/index.d.ts +3 -0
  68. package/dist/types/index.d.ts +19 -1
  69. package/dist/types/op-parent-api/captcha/check.d.ts +21 -0
  70. package/dist/types/op-parent-api/captcha/create.d.ts +24 -0
  71. package/dist/types/op-parent-api/index.d.ts +6 -0
  72. package/dist/types/sso/index.d.ts +4 -0
  73. package/dist/types/sso/web/auth/assistant/captcha/v2/create.d.ts +34 -0
  74. package/dist/types/sso/web/auth/login-by-password.d.ts +2 -18
  75. package/dist/types/sso/web/auth/v3/login/by-phone.d.ts +38 -0
  76. package/package.json +1 -1
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # @ccw-api/api
2
2
 
3
- CCW(创作社区)前后端接口 SDK,整合 `sso.ccw.site` 与 `community-web.ccw.site`
4
- 两大服务的常用 API,提供完整的 TypeScript 类型与开箱即用的分页封装。
3
+ CCW(创作社区)前后端接口 SDK,整合 `sso.ccw.site`、`community-web.ccw.site`、
4
+ `gandi-main.ccw.site`、`bfs-web.ccw.site`、`community-web-cloud-database.ccw.site`
5
+ 五大服务的常用 API,提供完整的 TypeScript 类型与开箱即用的分页封装。
5
6
 
6
7
  - 包格式:**ESM + CJS + .d.ts 三产物**同时产出
7
8
  - 类型:所有请求 / 响应字段**强类型**,后端 key 固定约束(如 `MongoDBId`、`CNameOssUrl`、`UUID`)
@@ -40,9 +41,15 @@ setToken("abcdefgfoo");
40
41
  ### 2. 调用方式
41
42
 
42
43
  ```ts
43
- import { sso, communityWeb, gandiMain, bfsWeb } from "@ccw-api/api"; // ✅ 推荐:具名导入
44
+ import {
45
+ sso,
46
+ communityWeb,
47
+ gandiMain,
48
+ bfsWeb,
49
+ communityWebCloudDatabase,
50
+ } from "@ccw-api/api"; // ✅ 推荐:具名导入
44
51
  // 或
45
- import api from "@ccw-api/api"; // ✅ default 导入:api.sso / api.communityWeb / api.gandiMain / api.bfsWeb
52
+ import api from "@ccw-api/api"; // ✅ default 导入:api.sso / api.communityWeb / api.gandiMain / api.bfsWeb / api.communityWebCloudDatabase
46
53
  ```
47
54
 
48
55
  ---
@@ -71,7 +78,7 @@ await logoutBySession("64...session_oid");
71
78
 
72
79
  ## 模块二:Community-Web(community-web.ccw.site)
73
80
 
74
- 覆盖作品 / 学生 / 星球 / 评论 / 通知 / 任务 / 表情 / 签到 / 云资产 / 学科专区 等业务领域,共 **95 个 API**。
81
+ 覆盖作品 / 学生 / 星球 / 评论 / 通知 / 任务 / 表情 / 签到 / 云资产 / 学科专区 / 打赏 等业务领域,共 **96 个 API**。
75
82
  下面是常用场景示例。
76
83
 
77
84
  ### 学生与作品
@@ -178,6 +185,10 @@ const [allEmoji, emojiPage, categories] = await Promise.all([
178
185
  // 签到
179
186
  await communityWeb.insertCheckInRecord(); // 今日打卡
180
187
  const history = await communityWeb.getCheckInRecords();
188
+
189
+ // 作品 / 评论 打赏(需要token,消耗账户金币)
190
+ // 给某个作品打赏 1 金币(objectType 默认 "CREATION")
191
+ await communityWeb.donateTrade(1, "68ce4849811b737483bf7027", "CREATION");
181
192
  ```
182
193
 
183
194
  ### 黑名单、封禁、禁言查询
@@ -204,10 +215,16 @@ const muted = await communityWeb.getMutedUserDetail("244373873");
204
215
 
205
216
  ## 模块三:Gandi-Main(gandi-main.ccw.site)
206
217
 
207
- 公告与服务器时间相关,共 **3 个 API**:
218
+ 公告、服务器时间与作品排行榜相关,共 **5 个 API**:
208
219
 
209
220
  ```ts
210
- const { getCurrentTimestamp, getBulletinsPage, getBulletinDetail } = gandiMain;
221
+ const {
222
+ getCurrentTimestamp,
223
+ getBulletinsPage,
224
+ getBulletinDetail,
225
+ submitLeaderboardRecord,
226
+ getLeaderboardRecords,
227
+ } = gandiMain;
211
228
 
212
229
  // 服务端当前毫秒级时间戳(比客户端时间更可靠,可用于签到、排行榜等需要严格时间判断的场景)
213
230
  const ts = await gandiMain.getCurrentTimestamp();
@@ -221,16 +238,37 @@ const bulletins = await gandiMain.getBulletinsPage("PUBLISHED", {
221
238
 
222
239
  // 某条公告详情
223
240
  const detail = await gandiMain.getBulletinDetail(123);
241
+
242
+ // ====== 作品排行榜 ======
243
+
244
+ // 获取排行榜记录(含榜单配置、当前用户排名、完整记录分页列表)
245
+ const board = await gandiMain.getLeaderboardRecords("65a2314daa96bd09b5beae5a");
246
+ console.log(board.title); // "总收益"
247
+ console.log(board.scoreUnit); // "币"
248
+ console.log(board.sortType); // "DESC"
249
+ console.log(board.leaderboardRecordTotalNum); // 834
250
+ console.log(board.curUserLeaderboardRecord?.ranking); // 139
251
+ console.log(board.leaderboardRecords[0].ranking); // 1
252
+ console.log(board.leaderboardRecords[0].score); // 排行榜最高分
253
+ console.log(board.leaderboardRecords[0].user?.nickname); // 榜首昵称
254
+
255
+ // 向排行榜提交分数记录(需要token:userOid必须对应当前登录用户)
256
+ await gandiMain.submitLeaderboardRecord(
257
+ "65a2314daa96bd09b5beae5a", // 排行榜oid
258
+ 1500, // 分数
259
+ "6a42413ccc79993c436d04b6", // 用户oid
260
+ "", // 扩展信息(可选)
261
+ );
224
262
  ```
225
263
 
226
264
  ---
227
265
 
228
266
  ## 模块四:BFS-Web(bfs-web.ccw.site)
229
267
 
230
- 用户扩展(extension)管理,共 **1 个 API**:
268
+ 用户扩展(extension)管理与扩展打赏,共 **2 个 API**:
231
269
 
232
270
  ```ts
233
- const { getUserExtensions } = bfsWeb;
271
+ const { getUserExtensions, donateExtension } = bfsWeb;
234
272
 
235
273
  // 分页查询某用户上传/使用过的扩展列表(默认按 updatedAt 倒序)
236
274
  const exts = await bfsWeb.getUserExtensions("63c2807d669fa967f17f5559", {
@@ -238,6 +276,40 @@ const exts = await bfsWeb.getUserExtensions("63c2807d669fa967f17f5559", {
238
276
  perPage: 20,
239
277
  });
240
278
  // exts: PagesRes<Extension>
279
+
280
+ // 给某个扩展打赏(需要token,消耗账户金币)
281
+ await bfsWeb.donateExtension(
282
+ "spineAnimation", // 扩展id(eid)
283
+ 5, // 打赏金币数量
284
+ );
285
+ ```
286
+
287
+ ---
288
+
289
+ ## 模块五:Community-Web-Cloud-Database(community-web-cloud-database.ccw.site)
290
+
291
+ 作品 / 用户的云变量(键值对存储)管理,共 **2 个 API**:
292
+
293
+ ```ts
294
+ const { saveProjectCloudVariable, saveUserCloudVariable } =
295
+ communityWebCloudDatabase;
296
+
297
+ // 保存作品云变量(无需token,主键是作品id;value结构任意,泛型自动推导返回)
298
+ const saved1 = await communityWebCloudDatabase.saveProjectCloudVariable(
299
+ "651c9a5142f8fb5ada70fad2",
300
+ "backup_65d31eaeb2ce1b785ebea87b_云时间",
301
+ { v: "2025-07-05T17:40:53.057+0800" },
302
+ );
303
+ // saved1: { v: string }
304
+ // ^? 泛型自动匹配 value 传入的结构
305
+
306
+ // 保存用户云变量(需要token,主键内部自动为 userId 拼接 "-u" 后缀)
307
+ const saved2 = await communityWebCloudDatabase.saveUserCloudVariable(
308
+ "68dddc63d28b025cb53fae25",
309
+ "256053228",
310
+ { key2: "0" },
311
+ );
312
+ // saved2: { key2: string }
241
313
  ```
242
314
 
243
315
  ---
@@ -336,7 +408,7 @@ const p: PagesRes<Creation.Creation> = {
336
408
  # 三产物构建(node / esm / .d.ts)
337
409
  npm run build
338
410
 
339
- # 跑测试(Jest 30 + ts-jest,101 suites / 106 tests)
411
+ # 跑测试(Jest 30 + ts-jest,105 suites / 112 tests)
340
412
  npm test
341
413
  npm run test:dev # watch 模式
342
414
 
@@ -9,7 +9,7 @@ const dpa = {
9
9
  * 根据主题获取评论列表
10
10
  * @param {string} subjectOid 主题oid
11
11
  * @param {SubjectType} subjectType 主题类型 (如: "POST")
12
- * @param {string} sectionType 区块类型 (如: "COMMENT")
12
+ * @param {SectionType} sectionType 区块类型 (如: "COMMENT")
13
13
  * @param {Partial<PageArgs<SortField| T>>} pageArgs_ 分页参数
14
14
  * @returns {Promise<Res>} 评论分页结果
15
15
  */
@@ -0,0 +1,13 @@
1
+ import { ccwAxios } from "@ccw-api/axios";
2
+ export const url = "https://community-web.ccw.site/great_creation/update";
3
+ /**
4
+ * 更换代表作
5
+ * @param {MongoDBId|""} creationOid 作品OID 若为""则是取消展示
6
+ * @returns {Promise<Res>} 更换结果
7
+ */
8
+ export async function updateGreatCreation(creationOid) {
9
+ const req = { creationOid };
10
+ return await ccwAxios
11
+ .post(url, req)
12
+ .then((res) => res.data.body);
13
+ }
@@ -0,0 +1,4 @@
1
+ import { updateGreatCreation } from "./update";
2
+ test("should fail without token when updating great creation", async () => {
3
+ await expect(updateGreatCreation("69740f1a61b891733d5ee2c6")).rejects.toThrow();
4
+ });
@@ -93,6 +93,10 @@ import { createComment } from "./comment/create";
93
93
  import { likeComment } from "./comment/like_record/create";
94
94
  import { getStudentFollowingPage } from "./student/following/page";
95
95
  import { donateTrade } from "./study-trade/trade/donate";
96
+ import { getUserProductsPage } from "./user_package/user_product";
97
+ import { setUserProductStatus } from "./user_package/status";
98
+ import { updateGreatCreation } from "./great_creation/update";
99
+ import { getSmartContractList } from "./smart_contract/list";
96
100
  export const communityWeb = {
97
101
  acceptAward,
98
102
  donateTrade,
@@ -153,6 +157,7 @@ export const communityWeb = {
153
157
  getRemixedCreationPage,
154
158
  getReputationScoreLogPage: getReputationScoreLog,
155
159
  getSearchHotWords,
160
+ getSmartContractList,
156
161
  getSplitRuleDetail,
157
162
  getSpreadFeedUnreadCount,
158
163
  getStudentBlockRecordDetail,
@@ -176,6 +181,7 @@ export const communityWeb = {
176
181
  getUserAssetStorageSize,
177
182
  getUserCardDetail,
178
183
  getUserLabelList,
184
+ getUserProductsPage,
179
185
  getCreationsByStudent,
180
186
  insertCheckInRecord,
181
187
  likeComment,
@@ -184,9 +190,11 @@ export const communityWeb = {
184
190
  searchCloudAssets,
185
191
  searchCreationsByTag,
186
192
  sendEvent,
193
+ setUserProductStatus,
187
194
  submitCreation,
188
195
  topComment,
189
196
  updateCreation,
197
+ updateGreatCreation,
190
198
  updateStudent,
191
199
  viewCreationStats,
192
200
  };
@@ -0,0 +1,14 @@
1
+ import { ccwAxios } from "@ccw-api/axios";
2
+ export const url = "https://community-web.ccw.site/smart_contract/list";
3
+ /**
4
+ * 列出作品中的智能金币合约
5
+ * @param {MongoDBId} bizId 业务ID(如作品OID)
6
+ * @param {SmartContractBizType} bizType 业务类型,CREATION
7
+ * @returns {Promise<Res>} 智能合约列表
8
+ */
9
+ export async function getSmartContractList(bizId, bizType = "CREATION") {
10
+ const req = { bizId, bizType };
11
+ return await ccwAxios
12
+ .post(url, req)
13
+ .then((res) => res.data.body);
14
+ }
@@ -0,0 +1,4 @@
1
+ import { getSmartContractList } from "./list";
2
+ test("should fail without token when listing smart contracts", async () => {
3
+ await expect(getSmartContractList("679d79862de3ec4dde55d534")).rejects.toThrow();
4
+ });
@@ -0,0 +1,14 @@
1
+ import { ccwAxios } from "@ccw-api/axios";
2
+ export const url = "https://community-web.ccw.site/user_package/status";
3
+ /**
4
+ * 设置用户商品状态(如佩戴/卸下头像框)
5
+ * @param {number} id 用户商品ID
6
+ * @param {boolean} status 商品状态,true为佩戴,false为卸下
7
+ * @returns {Promise<Res>} 设置结果
8
+ */
9
+ export async function setUserProductStatus(id, status) {
10
+ const req = { status, id };
11
+ return await ccwAxios
12
+ .post(url, req)
13
+ .then((res) => res.data.body);
14
+ }
@@ -0,0 +1,4 @@
1
+ import { setUserProductStatus } from "./status";
2
+ test("should fail without token when setting user product status", async () => {
3
+ await expect(setUserProductStatus(105847, false)).rejects.toThrow();
4
+ });
@@ -0,0 +1,29 @@
1
+ import { ccwAxios } from "@ccw-api/axios";
2
+ import { queryPage, DEFAULT_PAGE_ARGS } from "src/queryPages";
3
+ export const url = "https://community-web.ccw.site/user_package/user_product";
4
+ export var ProductCategory;
5
+ (function (ProductCategory) {
6
+ ProductCategory[ProductCategory["AVATAR_FRAME"] = 5] = "AVATAR_FRAME";
7
+ })(ProductCategory || (ProductCategory = {}));
8
+ /**
9
+ * 分页获取用户商品(如头像框)
10
+ * @param {ProductCategory} productCategory 商品分类,默认5为头像框
11
+ * @param {Partial<PageArgs<SortField>>} pageArgs_ 分页参数
12
+ * @returns {Promise<Res>} 分页用户商品数据
13
+ */
14
+ export async function getUserProductsPage(productCategory = ProductCategory.AVATAR_FRAME, pageArgs_ = {}) {
15
+ const pageArgs = {
16
+ ...DEFAULT_PAGE_ARGS,
17
+ ...pageArgs_,
18
+ };
19
+ const queryUrl = queryPage(url, pageArgs);
20
+ const req = {
21
+ page: pageArgs.page,
22
+ perPage: pageArgs.perPage,
23
+ sortType: pageArgs.sortType,
24
+ productCategory,
25
+ };
26
+ return await ccwAxios
27
+ .post(queryUrl, req)
28
+ .then((res) => res.data.body);
29
+ }
@@ -0,0 +1,9 @@
1
+ import { getUserProductsPage, ProductCategory } from "./user_product";
2
+ test("should fail without token for avatar frame products", async () => {
3
+ await expect(getUserProductsPage(ProductCategory.AVATAR_FRAME, {
4
+ page: 1,
5
+ perPage: 8,
6
+ sortType: "DESC",
7
+ sortField: "",
8
+ })).rejects.toThrow();
9
+ });
@@ -0,0 +1,29 @@
1
+ import { ccwAxios } from "@ccw-api/axios";
2
+ export const url = "https://community-web-cloud-database.ccw.site/cloud_variable/save";
3
+ async function saveCloudVariableBase(primaryKey, secondaryKey, value) {
4
+ const req = { primaryKey, secondaryKey, value };
5
+ return await ccwAxios
6
+ .post(url, req)
7
+ .then((res) => res.data.body);
8
+ }
9
+ /**
10
+ * 保存作品云变量到云数据库
11
+ * @param {MongoDBId} projectId 作品id
12
+ * @param {string} secondaryKey 次级键 - 变量名或标识
13
+ * @param {T} value 要保存数据
14
+ * @returns {Promise<SaveCloudVariableRes<T>>} 保存后的值对象
15
+ */
16
+ export async function saveProjectCloudVariable(projectId, secondaryKey, value) {
17
+ return await saveCloudVariableBase(projectId, secondaryKey, { v: value });
18
+ }
19
+ /**
20
+ * 保存数据到用户云数据库
21
+ * @param {MongoDBId} projectId 作品id
22
+ * @param {number} studentNum 次级键 - 用户id
23
+ * @param {T} value 要保存的键值对
24
+ * @returns {Promise<SaveCloudVariableRes<T>>} 保存后的值对象
25
+ */
26
+ export async function saveUserCloudVariable(projectId, studentNum, value) {
27
+ const primaryKey = `${projectId}-u`;
28
+ return await saveCloudVariableBase(primaryKey, `${studentNum}`, value);
29
+ }
@@ -0,0 +1,13 @@
1
+ import { saveProjectCloudVariable, saveUserCloudVariable } from "./save";
2
+ test("save project cloud variable should return saved value", async () => {
3
+ const testValue = "2025-07-05T17:40:53.057+0800";
4
+ const res = await saveProjectCloudVariable("68dddc63d28b025cb53fae25", "test", testValue);
5
+ expect(res).toBeDefined();
6
+ expect(res.v).toBe(testValue);
7
+ });
8
+ test("save user cloud variable should ok", async () => {
9
+ const res = await saveUserCloudVariable("68dddc63d28b025cb53fae25", 0, {
10
+ key2: "0",
11
+ });
12
+ expect(res.key2).toEqual("0");
13
+ });
@@ -0,0 +1,5 @@
1
+ import { saveProjectCloudVariable, saveUserCloudVariable, } from "./cloud_variable/save";
2
+ export const communityWebCloudDatabase = {
3
+ saveProjectCloudVariable,
4
+ saveUserCloudVariable,
5
+ };
@@ -0,0 +1,28 @@
1
+ import { ccwAxios } from "@ccw-api/axios";
2
+ export const url = "https://gandi-main.ccw.site/creation/leaderboards";
3
+ /**
4
+ * 向作品排行榜提交一条分数记录
5
+ * @param {MongoDBId} leaderboardOid 排行榜id
6
+ * @param {number} score 分数
7
+ * @param {MongoDBId} userOid 用户id
8
+ * @param {string} ext 扩展信息(可选)
9
+ * @returns {Promise<SubmitRes>} 是否提交成功
10
+ */
11
+ export async function submitLeaderboardRecord(leaderboardOid, score, userOid, ext = "") {
12
+ const req = { score, oid: userOid, ext };
13
+ const requestUrl = `${url}/${leaderboardOid}/records`;
14
+ return await ccwAxios
15
+ .post(requestUrl, req)
16
+ .then((res) => res.data.body);
17
+ }
18
+ /**
19
+ * 获取作品排行榜记录列表
20
+ * @param {MongoDBId} leaderboardOid 排行榜id
21
+ * @returns {Promise<LeaderboardRecordsRes>} 排行榜记录列表
22
+ */
23
+ export async function getLeaderboardRecords(leaderboardOid) {
24
+ const requestUrl = `${url}/${leaderboardOid}/records`;
25
+ return await ccwAxios
26
+ .get(requestUrl)
27
+ .then((res) => res.data.body);
28
+ }
@@ -0,0 +1,11 @@
1
+ import { getLeaderboardRecords, submitLeaderboardRecord } from "./records";
2
+ test("submit leaderboard record should fail without token", async () => {
3
+ await expect(submitLeaderboardRecord("65a2314daa96bd09b5beae5a", 0, "65a2314daa96bd09b5beae5a", "")).rejects.toThrow();
4
+ });
5
+ test("get leaderboard records should return data", async () => {
6
+ const res = await getLeaderboardRecords("65a2314daa96bd09b5beae5a");
7
+ expect(res.oid).toBe("65a2314daa96bd09b5beae5a");
8
+ expect(res.title).toBeDefined();
9
+ expect(res.leaderboardRecordTotalNum).toBeDefined();
10
+ expect(Array.isArray(res.leaderboardRecords)).toBe(true);
11
+ });
@@ -1,8 +1,11 @@
1
1
  import { getCurrentTimestamp } from "./timestamp/current";
2
2
  import { getBulletinsPage } from "./bulletins/page";
3
3
  import { getBulletinDetail } from "./bulletins/detail";
4
+ import { getLeaderboardRecords, submitLeaderboardRecord, } from "./creation/leaderboards/{oid}/records";
4
5
  export const gandiMain = {
5
6
  getCurrentTimestamp,
6
7
  getBulletinsPage,
7
8
  getBulletinDetail,
9
+ submitLeaderboardRecord,
10
+ getLeaderboardRecords,
8
11
  };
package/dist/esm/index.js CHANGED
@@ -2,11 +2,15 @@ import { sso } from "./sso/index";
2
2
  import { communityWeb } from "./community-web";
3
3
  import { gandiMain } from "./gandi-main";
4
4
  import { bfsWeb } from "./bfs-web";
5
- export { sso, communityWeb, gandiMain, bfsWeb };
5
+ import { communityWebCloudDatabase } from "./community-web-cloud-database";
6
+ import { opParentApi } from "./op-parent-api";
7
+ export { sso, communityWeb, gandiMain, bfsWeb, communityWebCloudDatabase, opParentApi, };
6
8
  export { setToken } from "@ccw-api/axios";
7
9
  export default {
8
10
  sso,
9
11
  communityWeb,
10
12
  gandiMain,
11
13
  bfsWeb,
14
+ communityWebCloudDatabase,
15
+ opParentApi,
12
16
  };
@@ -0,0 +1,15 @@
1
+ import { ccwAxios } from "@ccw-api/axios";
2
+ export const url = "https://op-parent-api.xiguacity.cn/captcha/check";
3
+ /**
4
+ * 校验图形验证码(拼图滑块坐标)
5
+ * @param {string} token createCaptcha 返回的 token
6
+ * @param {number} x 拼图滑块目标位置的 x 坐标
7
+ * @param {number} y 拼图滑块目标位置的 y 坐标
8
+ * @returns {Promise<Res>} 校验结果
9
+ */
10
+ export async function checkCaptcha(token, x, y = 5) {
11
+ const req = { data: { x, y }, token };
12
+ return await ccwAxios
13
+ .post(url, req)
14
+ .then((res) => res.data.body);
15
+ }
@@ -0,0 +1,12 @@
1
+ import { checkCaptcha } from "./check";
2
+ import { createCaptcha } from "./create";
3
+ test("checkCaptcha returns structured result with token and coords without auth", async () => {
4
+ const created = await createCaptcha("BLOCK_PUZZLE");
5
+ const res = await checkCaptcha(created.token, 0, -1);
6
+ expect(res).toHaveProperty("captchaExpired");
7
+ expect(res).toHaveProperty("errorMsg");
8
+ expect(res).toHaveProperty("success");
9
+ expect(res.captchaExpired).toBeFalsy();
10
+ expect(res.success).toBeFalsy();
11
+ expect(res.errorMsg).toEqual("验证失败");
12
+ }, 30000);
@@ -0,0 +1,13 @@
1
+ import { ccwAxios } from "@ccw-api/axios";
2
+ export const url = "https://op-parent-api.xiguacity.cn/captcha/create";
3
+ /**
4
+ * 创建图形验证码(拼图滑块)
5
+ * @param {CaptchaType} type 验证码类型,默认 BLOCK_PUZZLE(滑块拼图)
6
+ * @returns {Promise<Res>} 验证码信息:bgImg为背景图base64 data url,jigsawImg为拼图块base64 data url,token用于后续校验
7
+ */
8
+ export async function createCaptcha(type = "BLOCK_PUZZLE") {
9
+ const req = { type };
10
+ return await ccwAxios
11
+ .post(url, req)
12
+ .then((res) => res.data.body);
13
+ }
@@ -0,0 +1,13 @@
1
+ import { createCaptcha } from "./create";
2
+ test("create BLOCK_PUZZLE captcha returns bgImg, jigsawImg and token without token", async () => {
3
+ const res = await createCaptcha("BLOCK_PUZZLE");
4
+ expect(res).toHaveProperty("data");
5
+ expect(res.data).toHaveProperty("bgImg");
6
+ expect(res.data).toHaveProperty("jigsawImg");
7
+ expect(res).toHaveProperty("token");
8
+ expect(typeof res.token).toBe("string");
9
+ expect(typeof res.data.bgImg).toBe("string");
10
+ expect(typeof res.data.jigsawImg).toBe("string");
11
+ expect(res.data.bgImg.startsWith("data:image/png;base64,")).toBeTruthy();
12
+ expect(res.data.jigsawImg.startsWith("data:image/png;base64,")).toBeTruthy();
13
+ }, 30000);
@@ -0,0 +1,6 @@
1
+ import { createCaptcha } from "./captcha/create";
2
+ import { checkCaptcha } from "./captcha/check";
3
+ export const opParentApi = {
4
+ createCaptcha,
5
+ checkCaptcha,
6
+ };
@@ -1,8 +1,12 @@
1
1
  import { loginByPassword } from "./web/auth/login-by-password";
2
2
  import { logout } from "./web/auth/logout";
3
3
  import { logoutBySession } from "./web/auth/logout_by_session";
4
+ import { createSmsCaptcha } from "./web/auth/assistant/captcha/v2/create";
5
+ import { loginByPhone } from "./web/auth/v3/login/by-phone";
4
6
  export const sso = {
5
7
  loginByPassword,
6
8
  logout,
7
9
  logoutBySession,
10
+ createSmsCaptcha,
11
+ loginByPhone,
8
12
  };
@@ -0,0 +1,17 @@
1
+ import { ccwAxios } from "@ccw-api/axios";
2
+ export const url = "https://sso.ccw.site/web/auth/assistant/captcha/v2/create";
3
+ /**
4
+ * 发送短信验证码
5
+ * @param {string} recipient 接收手机号,如 "10011451919"
6
+ * @param {SmsScene} scene 场景,默认 loginbyPhone
7
+ * @param {string} countryCode 国家代码,默认 "86"
8
+ * @param {SmsRuleCode} ruleCode 短信规则编码,默认 "ccw_sms"
9
+ * @param {CaptchaPayload} captcha 图形验证码校验结果(滑块拼图的坐标与token),可选(部分场景未强制要求时可省略,但失败时sendResult!=1)
10
+ * @returns {Promise<Res>} 发送结果:batchId 为短信批次ID,sendResult=1 表示成功
11
+ */
12
+ export async function createSmsCaptcha(recipient, scene = "loginbyPhone", countryCode = "86", ruleCode = "ccw_sms", captcha) {
13
+ const req = { recipient, countryCode, scene, ruleCode, captcha };
14
+ return await ccwAxios
15
+ .post(url, req)
16
+ .then((res) => res.data.body);
17
+ }
@@ -0,0 +1,8 @@
1
+ import { createSmsCaptcha } from "./create";
2
+ test("createSmsCaptchaV2 returns batchId and sendResult fields without token", async () => {
3
+ const res = await createSmsCaptcha("12345671145", "loginbyPhone", "86", "ccw_sms");
4
+ expect(res).toHaveProperty("batchId");
5
+ expect(res).toHaveProperty("sendResult");
6
+ expect(res.batchId).toBeNull();
7
+ expect(res.sendResult).toBe(2);
8
+ }, 30000);
@@ -11,13 +11,14 @@ export async function loginByPassword(loginKey, password, reqExtra = {
11
11
  device: "Node",
12
12
  browser: "Node.js",
13
13
  }) {
14
- const { extra, ...restBody } = await ccwAxios
15
- .post(url, {
14
+ const req = {
16
15
  loginKey,
17
16
  password,
18
17
  clientCode: "STUDY_COMMUNITY",
19
18
  extra: JSON.stringify({ ...reqExtra, scene: null }),
20
- })
19
+ };
20
+ const { extra, ...restBody } = await ccwAxios
21
+ .post(url, req)
21
22
  .then((res) => res.data.body);
22
23
  const resExtra = JSON.parse(extra);
23
24
  return {
@@ -0,0 +1,33 @@
1
+ import { ccwAxios } from "@ccw-api/axios";
2
+ export const url = "https://sso.ccw.site/web/auth/v3/login/by_phone";
3
+ /**
4
+ * 通过手机号+短信验证码登录(V3)
5
+ * @param {string} phone 手机号,如 "12345671145"
6
+ * @param {string} code 收到的短信验证码
7
+ * @param {ReqExtra} reqExtra 设备和浏览器信息
8
+ * @param {string} countryCode 国家代码,默认 "86"
9
+ * @param {LoginRuleCode} ruleCode 短信规则编码,默认 "ccw_sms"
10
+ * @param {LoginScene} scene 登录场景,默认 "loginbyPhone"
11
+ * @returns {Promise<Res<Extra>>} session 信息
12
+ */
13
+ export async function loginByPhone(phone, code, reqExtra = {
14
+ device: "Node",
15
+ browser: "Node.js",
16
+ }, countryCode = "86", ruleCode = "ccw_sms", scene = "loginbyPhone") {
17
+ const { extra, ...restBody } = await ccwAxios
18
+ .post(url, {
19
+ phone,
20
+ code,
21
+ countryCode,
22
+ ruleCode,
23
+ scene,
24
+ clientCode: "STUDY_COMMUNITY",
25
+ extra: JSON.stringify({ ...reqExtra, scene: null }),
26
+ })
27
+ .then((res) => res.data.body);
28
+ const resExtra = JSON.parse(extra);
29
+ return {
30
+ ...restBody,
31
+ extra: resExtra,
32
+ };
33
+ }
@@ -0,0 +1,7 @@
1
+ import { loginByPhone } from "./by-phone";
2
+ test("expect wrong sms code to throw error", async () => {
3
+ await expect(loginByPhone("12345671145", "000000", {
4
+ device: "Node.js Test",
5
+ browser: "Jest",
6
+ })).rejects.toThrow();
7
+ }, 30000);
@@ -13,7 +13,7 @@ const dpa = {
13
13
  * 根据主题获取评论列表
14
14
  * @param {string} subjectOid 主题oid
15
15
  * @param {SubjectType} subjectType 主题类型 (如: "POST")
16
- * @param {string} sectionType 区块类型 (如: "COMMENT")
16
+ * @param {SectionType} sectionType 区块类型 (如: "COMMENT")
17
17
  * @param {Partial<PageArgs<SortField| T>>} pageArgs_ 分页参数
18
18
  * @returns {Promise<Res>} 评论分页结果
19
19
  */
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.url = void 0;
4
+ exports.updateGreatCreation = updateGreatCreation;
5
+ const axios_1 = require("@ccw-api/axios");
6
+ exports.url = "https://community-web.ccw.site/great_creation/update";
7
+ /**
8
+ * 更换代表作
9
+ * @param {MongoDBId|""} creationOid 作品OID 若为""则是取消展示
10
+ * @returns {Promise<Res>} 更换结果
11
+ */
12
+ async function updateGreatCreation(creationOid) {
13
+ const req = { creationOid };
14
+ return await axios_1.ccwAxios
15
+ .post(exports.url, req)
16
+ .then((res) => res.data.body);
17
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const update_1 = require("./update");
4
+ test("should fail without token when updating great creation", async () => {
5
+ await expect((0, update_1.updateGreatCreation)("69740f1a61b891733d5ee2c6")).rejects.toThrow();
6
+ });