MatchMaster/docs/superpowers/plans/2026-07-24-cat-arr.md
2026-07-30 12:22:02 +08:00

9.6 KiB

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

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:

& "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:

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

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:

& "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:

name: setCatArr
desc: ""
methods:
  - GET
  - POST
tags: []
  • Step 4: Implement the minimal cloud function

Create setCatArr.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

& "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
& "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.