340 lines
9.6 KiB
Markdown
340 lines
9.6 KiB
Markdown
# catArr Server Cloud Functions Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Add `catArr` initialization, reading, and manual saving to the Laf server cloud functions.
|
|
|
|
**Architecture:** Keep `catArr` independent from `headArr`. Initialize it in `login`, and expose a dedicated `setCatArr` cloud function with `read` and `save` actions while preserving the existing database routing and token-validation patterns.
|
|
|
|
**Tech Stack:** TypeScript, Laf cloud functions, Node.js built-in test runner.
|
|
|
|
## Global Constraints
|
|
|
|
- `catArr` is an array of non-negative integer IDs.
|
|
- New and legacy users default to `[1, 2, 3]` when the field is absent.
|
|
- Existing `catArr` values are never overwritten during login.
|
|
- The function must support both `users` and `usersAd`.
|
|
- No cat-drawing or gacha behavior is included.
|
|
- Do not create Git commits for this work.
|
|
|
|
---
|
|
|
|
### Task 1: Initialize catArr During Login
|
|
|
|
**Files:**
|
|
- Modify: `server/laf-cloud/functions/login.ts`
|
|
- Create: `server/laf-cloud/tests/login-cat-arr.test.mjs`
|
|
|
|
**Interfaces:**
|
|
- Consumes: the existing new-user object passed to `db.collection(dbname).add(...)`
|
|
- Produces: a persisted `catArr: number[]` field for newly created users
|
|
|
|
- [ ] **Step 1: Write the failing source-level regression test**
|
|
|
|
```js
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
|
|
test("login initializes new users with catArr [1, 2, 3]", async () => {
|
|
const source = await readFile(
|
|
new URL("../functions/login.ts", import.meta.url),
|
|
"utf8"
|
|
);
|
|
|
|
assert.match(
|
|
source,
|
|
/catArr:\s*user\?\.catArr\s*\|\|\s*\[1,\s*2,\s*3\]/
|
|
);
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run the test and verify RED**
|
|
|
|
Run:
|
|
|
|
```powershell
|
|
& "C:\Users\EDY\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe" --test server/laf-cloud/tests/login-cat-arr.test.mjs
|
|
```
|
|
|
|
Expected: FAIL because `login.ts` does not yet initialize `catArr`.
|
|
|
|
- [ ] **Step 3: Add the minimal login field**
|
|
|
|
Add beside `headArr` in the new-user object:
|
|
|
|
```ts
|
|
catArr: user?.catArr || [1, 2, 3],
|
|
```
|
|
|
|
- [ ] **Step 4: Run the test and verify GREEN**
|
|
|
|
Run the same Node test command.
|
|
|
|
Expected: one passing test.
|
|
|
|
### Task 2: Add the Independent setCatArr Cloud Function
|
|
|
|
**Files:**
|
|
- Create: `server/laf-cloud/functions/setCatArr.ts`
|
|
- Create: `server/laf-cloud/functions/setCatArr.yaml`
|
|
- Create: `server/laf-cloud/tests/set-cat-arr.test.mjs`
|
|
|
|
**Interfaces:**
|
|
- Consumes: `{ action, uid, token, gameName?, catNum? }` from `ctx.body`
|
|
- Produces for `read`: `{ code: 1, data: { catArr: number[] }, msg: "成功" }`
|
|
- Produces for `save`: the same shape after persisting a unique non-negative integer ID
|
|
|
|
- [ ] **Step 1: Write failing behavior tests with a Laf database stub**
|
|
|
|
```js
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { registerHooks } from "node:module";
|
|
import { readFile } from "node:fs/promises";
|
|
|
|
const state = {
|
|
user: null,
|
|
collections: [],
|
|
updates: [],
|
|
};
|
|
|
|
globalThis.__catArrCloudMock = {
|
|
database() {
|
|
return {
|
|
collection(name) {
|
|
state.collections.push(name);
|
|
return {
|
|
where() {
|
|
return {
|
|
async getOne() {
|
|
return { data: state.user };
|
|
},
|
|
async update(payload) {
|
|
state.updates.push(payload);
|
|
return {};
|
|
},
|
|
};
|
|
},
|
|
};
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
globalThis.__catArrUtilsMock = {
|
|
checkToken(received, expected) {
|
|
return received === expected;
|
|
},
|
|
};
|
|
|
|
registerHooks({
|
|
resolve(specifier, context, nextResolve) {
|
|
if (specifier === "@lafjs/cloud") {
|
|
return {
|
|
url: "data:text/javascript,export default globalThis.__catArrCloudMock",
|
|
shortCircuit: true,
|
|
};
|
|
}
|
|
if (specifier === "@/Utils") {
|
|
return {
|
|
url: "data:text/javascript,export default globalThis.__catArrUtilsMock",
|
|
shortCircuit: true,
|
|
};
|
|
}
|
|
return nextResolve(specifier, context);
|
|
},
|
|
});
|
|
|
|
const { default: setCatArr } = await import(
|
|
new URL("../functions/setCatArr.ts", import.meta.url)
|
|
);
|
|
|
|
function reset(user) {
|
|
state.user = user;
|
|
state.collections.length = 0;
|
|
state.updates.length = 0;
|
|
}
|
|
|
|
test("configuration exposes the setCatArr cloud function", async () => {
|
|
const yaml = await readFile(
|
|
new URL("../functions/setCatArr.yaml", import.meta.url),
|
|
"utf8"
|
|
);
|
|
assert.match(yaml, /^name:\s*setCatArr$/m);
|
|
});
|
|
|
|
test("read returns defaults for a legacy user", async () => {
|
|
reset({ _id: "u1", token: "secret" });
|
|
const result = await setCatArr({
|
|
body: { action: "read", uid: "u1", token: "secret" },
|
|
});
|
|
assert.deepEqual(result.data.catArr, [1, 2, 3]);
|
|
});
|
|
|
|
test("save appends and persists a new cat ID", async () => {
|
|
reset({ _id: "u1", token: "secret" });
|
|
const result = await setCatArr({
|
|
body: { action: "save", uid: "u1", token: "secret", catNum: 4 },
|
|
});
|
|
assert.deepEqual(result.data.catArr, [1, 2, 3, 4]);
|
|
assert.deepEqual(state.updates, [{ catArr: [1, 2, 3, 4] }]);
|
|
});
|
|
|
|
test("save rejects duplicate and invalid cat IDs", async () => {
|
|
reset({ _id: "u1", token: "secret", catArr: [1, 2, 3] });
|
|
const duplicate = await setCatArr({
|
|
body: { action: "save", uid: "u1", token: "secret", catNum: 2 },
|
|
});
|
|
const invalid = await setCatArr({
|
|
body: { action: "save", uid: "u1", token: "secret", catNum: -1 },
|
|
});
|
|
assert.equal(duplicate.code, 0);
|
|
assert.equal(invalid.code, 0);
|
|
assert.deepEqual(state.updates, []);
|
|
});
|
|
|
|
test("gameName iaa routes to usersAd", async () => {
|
|
reset({ _id: "u1", token: "secret", catArr: [1, 2, 3] });
|
|
await setCatArr({
|
|
body: {
|
|
action: "read",
|
|
uid: "u1",
|
|
token: "secret",
|
|
gameName: "iaa",
|
|
},
|
|
});
|
|
assert.equal(state.collections[0], "usersAd");
|
|
});
|
|
|
|
test("missing user returns an error", async () => {
|
|
reset(null);
|
|
const result = await setCatArr({
|
|
body: { action: "read", uid: "missing", token: "secret" },
|
|
});
|
|
assert.equal(result.code, 0);
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run the tests and verify RED**
|
|
|
|
Run:
|
|
|
|
```powershell
|
|
& "C:\Users\EDY\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe" --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --test server/laf-cloud/tests/set-cat-arr.test.mjs
|
|
```
|
|
|
|
Expected: FAIL because `setCatArr.ts` and `setCatArr.yaml` do not exist.
|
|
|
|
- [ ] **Step 3: Add the cloud-function configuration**
|
|
|
|
Create `setCatArr.yaml`:
|
|
|
|
```yaml
|
|
name: setCatArr
|
|
desc: ""
|
|
methods:
|
|
- GET
|
|
- POST
|
|
tags: []
|
|
```
|
|
|
|
- [ ] **Step 4: Implement the minimal cloud function**
|
|
|
|
Create `setCatArr.ts`:
|
|
|
|
```ts
|
|
import cloud from '@lafjs/cloud'
|
|
import Utils from "@/Utils";
|
|
|
|
const db = cloud.database();
|
|
const DEFAULT_CAT_ARR = [1, 2, 3];
|
|
|
|
export default async function (ctx: FunctionContext) {
|
|
const action = ctx.body.action;
|
|
const uid = ctx.body.uid;
|
|
const dbname = ctx.body.gameName == "iaa" ? "usersAd" : "users";
|
|
|
|
if (!action) {
|
|
return { code: 0, data: null, msg: "未获取到action" };
|
|
}
|
|
if (!uid) {
|
|
return { code: 0, data: null, msg: "未获取到uid" };
|
|
}
|
|
|
|
const res = await db.collection(dbname).where({ _id: uid }).getOne();
|
|
if (!res.data) {
|
|
return { code: 0, data: null, msg: "未获取到玩家信息" };
|
|
}
|
|
|
|
if (res.data.token && !Utils.checkToken(ctx.body.token, res.data.token)) {
|
|
return { code: 0, data: null, msg: "token校验失败" };
|
|
}
|
|
|
|
const catArr = Array.isArray(res.data.catArr)
|
|
? [...res.data.catArr]
|
|
: [...DEFAULT_CAT_ARR];
|
|
|
|
switch (action) {
|
|
case "save": {
|
|
const catNum = Number(ctx.body.catNum);
|
|
if (!Number.isInteger(catNum) || catNum < 0) {
|
|
return { code: 0, data: null, msg: "猫咪id错误" };
|
|
}
|
|
if (catArr.includes(catNum)) {
|
|
return { code: 0, data: null, msg: "已保存猫咪" };
|
|
}
|
|
catArr.push(catNum);
|
|
await db.collection(dbname).where({ _id: uid }).update({ catArr });
|
|
return {
|
|
code: 1,
|
|
data: { catArr },
|
|
msg: "用户数据更新成功",
|
|
};
|
|
}
|
|
case "read":
|
|
return { code: 1, data: { catArr }, msg: "成功" };
|
|
default:
|
|
return {
|
|
code: 400,
|
|
message: "无效的操作类型,请使用 save 或 read",
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 5: Run the behavior tests and verify GREEN**
|
|
|
|
Run the same Node test command.
|
|
|
|
Expected: six passing tests.
|
|
|
|
### Task 3: Run Combined Verification
|
|
|
|
**Files:**
|
|
- Verify: `server/laf-cloud/functions/login.ts`
|
|
- Verify: `server/laf-cloud/functions/setCatArr.ts`
|
|
- Verify: `server/laf-cloud/functions/setCatArr.yaml`
|
|
- Verify: `server/laf-cloud/tests/login-cat-arr.test.mjs`
|
|
- Verify: `server/laf-cloud/tests/set-cat-arr.test.mjs`
|
|
|
|
**Interfaces:**
|
|
- Consumes: completed Tasks 1 and 2
|
|
- Produces: verified server behavior for initialization, reading, and saving
|
|
|
|
- [ ] **Step 1: Run all catArr tests**
|
|
|
|
```powershell
|
|
& "C:\Users\EDY\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe" --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --test server/laf-cloud/tests/*cat-arr.test.mjs
|
|
```
|
|
|
|
Expected: seven passing tests and zero failures.
|
|
|
|
- [ ] **Step 2: Inspect only the scoped changes**
|
|
|
|
```powershell
|
|
& "C:\Users\EDY\.cache\codex-runtimes\codex-primary-runtime\dependencies\native\git\cmd\git.exe" diff -- server/laf-cloud/functions/login.ts server/laf-cloud/functions/setCatArr.ts server/laf-cloud/functions/setCatArr.yaml server/laf-cloud/tests/login-cat-arr.test.mjs server/laf-cloud/tests/set-cat-arr.test.mjs
|
|
```
|
|
|
|
Expected: only `catArr` initialization, the independent cloud function/configuration, and their tests.
|