181 lines
5.7 KiB
TypeScript
181 lines
5.7 KiB
TypeScript
import cloud from '@lafjs/cloud'
|
||
import {
|
||
buildClaimId,
|
||
getActiveSignInConfig,
|
||
getChinaDateKey,
|
||
getChinaDayStart,
|
||
getSignInClaims,
|
||
normalizeSignInState,
|
||
} from "@/SignInActivity";
|
||
|
||
const db = cloud.database();
|
||
const USERS_COLLECTION = "users";
|
||
const CLAIM_COLLECTION = "sign_in_activity_claims";
|
||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||
|
||
export default async function (ctx: FunctionContext) {
|
||
const adminToken = ctx.body?.adminToken || ctx.query?.adminToken;
|
||
if (!checkAdminToken(adminToken)) {
|
||
return { code: 0, data: null, msg: "测试接口未启用或管理密钥错误" };
|
||
}
|
||
|
||
const action = String(ctx.body?.action || ctx.query?.action || "inspect");
|
||
const uid = ctx.body?.uid || ctx.query?.uid;
|
||
if (!uid) {
|
||
return { code: 0, data: null, msg: "未获取到uid" };
|
||
}
|
||
const userResult = await db.collection(USERS_COLLECTION).where({ _id: uid }).getOne();
|
||
const user = userResult.data;
|
||
if (!user) {
|
||
return { code: 0, data: null, msg: "未获取到用户信息" };
|
||
}
|
||
|
||
switch (action) {
|
||
case "inspect":
|
||
return await inspectUser(user);
|
||
case "reset":
|
||
await clearClaims(uid);
|
||
await updateUser(uid, { signInActivity: null });
|
||
return await inspectUser({ ...user, signInActivity: null });
|
||
case "set_level":
|
||
return await setLevel(user, ctx.body?.levelAmount);
|
||
case "prepare":
|
||
return await prepareScenario(user, String(ctx.body?.scenario || ""));
|
||
default:
|
||
return { code: 0, data: null, msg: "无效action,请使用 inspect、reset、set_level 或 prepare" };
|
||
}
|
||
}
|
||
|
||
function checkAdminToken(adminToken: string) {
|
||
const env = (globalThis as any).process?.env || {};
|
||
const expected = String(env.SIGN_IN_TEST_ADMIN_TOKEN || "");
|
||
return !!expected && !!adminToken && adminToken === expected;
|
||
}
|
||
|
||
async function setLevel(user: any, rawLevel: any) {
|
||
const levelAmount = Number(rawLevel);
|
||
if (!Number.isInteger(levelAmount) || levelAmount < 0) {
|
||
return { code: 0, data: null, msg: "levelAmount必须是大于等于0的整数" };
|
||
}
|
||
await updateUser(user._id, { levelAmount });
|
||
return await inspectUser({ ...user, levelAmount });
|
||
}
|
||
|
||
async function prepareScenario(user: any, scenario: string) {
|
||
const config = await getActiveSignInConfig();
|
||
if (!config) {
|
||
return { code: 0, data: null, msg: "签到活动配置不存在或配置无效" };
|
||
}
|
||
const now = Date.now();
|
||
const triggerLevel = Number(config.triggerLevel);
|
||
|
||
if (scenario === "locked" || scenario === "eligible") {
|
||
const levelAmount = scenario === "locked" ? Math.max(0, triggerLevel - 1) : triggerLevel;
|
||
await clearClaims(user._id);
|
||
await updateUser(user._id, { levelAmount, signInActivity: null });
|
||
return await inspectUser({ ...user, levelAmount, signInActivity: null }, scenario);
|
||
}
|
||
|
||
const dayReadyMatch = /^day_([1-7])_ready$/.exec(scenario);
|
||
if (dayReadyMatch) {
|
||
const claimedCount = Number(dayReadyMatch[1]) - 1;
|
||
return await prepareActivity(user, config, now, scenario, claimedCount, false, false);
|
||
}
|
||
if (scenario === "claimed_today") {
|
||
return await prepareActivity(user, config, now, scenario, 1, true, false);
|
||
}
|
||
if (scenario === "expired") {
|
||
return await prepareActivity(user, config, now, scenario, 0, false, true);
|
||
}
|
||
if (scenario === "completed") {
|
||
return await prepareActivity(user, config, now, scenario, 7, false, false, true);
|
||
}
|
||
return {
|
||
code: 0,
|
||
data: null,
|
||
msg: "无效scenario,请使用 locked、eligible、day_1_ready至day_7_ready、claimed_today、expired或completed",
|
||
};
|
||
}
|
||
|
||
async function prepareActivity(
|
||
user: any,
|
||
config: any,
|
||
now: number,
|
||
scenario: string,
|
||
claimedCount: number,
|
||
todayClaimed: boolean,
|
||
expired: boolean,
|
||
completed = false,
|
||
) {
|
||
await clearClaims(user._id);
|
||
const todayStart = getChinaDayStart(now);
|
||
const startAt = expired
|
||
? todayStart - Number(config.durationDays) * DAY_MS
|
||
: todayStart - Math.min(claimedCount, 7) * DAY_MS;
|
||
const endAt = expired
|
||
? todayStart
|
||
: startAt + Number(config.durationDays) * DAY_MS;
|
||
const signInActivity = {
|
||
activityId: config.activityId,
|
||
startAt,
|
||
endAt,
|
||
activatedAt: startAt,
|
||
completedAt: completed ? now : null,
|
||
};
|
||
await updateUser(user._id, {
|
||
levelAmount: Number(config.triggerLevel),
|
||
signInActivity: JSON.stringify(signInActivity),
|
||
});
|
||
|
||
for (let index = 0; index < claimedCount; index++) {
|
||
const rewardDay = index + 1;
|
||
const daysAgo = todayClaimed && index === claimedCount - 1
|
||
? 0
|
||
: claimedCount - index;
|
||
const claimedAt = now - daysAgo * DAY_MS;
|
||
const dateKey = getChinaDateKey(claimedAt);
|
||
const claimId = buildClaimId(user._id, config.activityId, dateKey);
|
||
await db.collection(CLAIM_COLLECTION).add({
|
||
_id: claimId,
|
||
claimId,
|
||
uid: user._id,
|
||
activityId: config.activityId,
|
||
dateKey,
|
||
rewardDay,
|
||
rewards: config.rewards[index].items,
|
||
claimedAt,
|
||
testPrepared: true,
|
||
});
|
||
}
|
||
return await inspectUser({
|
||
...user,
|
||
levelAmount: Number(config.triggerLevel),
|
||
signInActivity,
|
||
}, scenario);
|
||
}
|
||
|
||
async function inspectUser(user: any, scenario?: string) {
|
||
const state = normalizeSignInState(user.signInActivity);
|
||
const claims = state ? await getSignInClaims(user._id, state.activityId) : [];
|
||
return {
|
||
code: 1,
|
||
data: {
|
||
scenario: scenario || null,
|
||
uid: user._id,
|
||
levelAmount: Number(user.levelAmount) || 0,
|
||
signInActivity: state,
|
||
claimedCount: claims.length,
|
||
claims,
|
||
},
|
||
msg: "测试状态准备成功",
|
||
};
|
||
}
|
||
|
||
async function clearClaims(uid: string) {
|
||
await db.collection(CLAIM_COLLECTION).where({ uid }).remove({ multi: true });
|
||
}
|
||
|
||
async function updateUser(uid: string, data: any) {
|
||
await db.collection(USERS_COLLECTION).where({ _id: uid }).update(data);
|
||
}
|