import cloud from '@lafjs/cloud' import { createHash } from 'crypto'; const db = cloud.database(); const CONFIG_COLLECTION = "sign_in_activity_config"; const CLAIM_COLLECTION = "sign_in_activity_claims"; const USERS_COLLECTION = "users"; const DAY_MS = 24 * 60 * 60 * 1000; const UTC_8_OFFSET_MS = 8 * 60 * 60 * 1000; const REWARD_DAYS = 7; export type SignInRewardItem = { type: "coin" | "freeze" | "hammer" | "magic_wand" | "infinite_health" | "cat_skin"; count: number; itemId?: number; }; export type SignInActivityConfig = { activityId: string; enabled: boolean; triggerLevel: number; durationDays: number; timezone?: string; rewards: Array<{ day: number; items: SignInRewardItem[]; }>; updatedAt?: number; createdAt?: number; }; export type SignInActivityState = { activityId: string; startAt: number; endAt: number; activatedAt: number; completedAt?: number | null; }; export function getChinaDayStart(now = Date.now()) { return Math.floor((now + UTC_8_OFFSET_MS) / DAY_MS) * DAY_MS - UTC_8_OFFSET_MS; } export function getChinaDateKey(now = Date.now()) { return String(Math.floor((now + UTC_8_OFFSET_MS) / DAY_MS)); } export function getUserSignInLevel(user: any) { const level = Number(user?.endLevelNum); return Number.isFinite(level) && level > 0 ? level : 0; } export function normalizeSignInState(value: any): SignInActivityState | null { if (!value) { return null; } if (typeof value === "string") { try { value = JSON.parse(value); } catch { return null; } } if (!value.activityId || !Number.isFinite(Number(value.startAt)) || !Number.isFinite(Number(value.endAt))) { return null; } return { activityId: String(value.activityId), startAt: Number(value.startAt), endAt: Number(value.endAt), activatedAt: Number(value.activatedAt) || Number(value.startAt), completedAt: Number(value.completedAt) || null, }; } export function isValidSignInConfig(config: any): config is SignInActivityConfig { if (!config?.activityId) { return false; } if (!Number.isFinite(Number(config.triggerLevel)) || Number(config.triggerLevel) < 0) { return false; } if (!Number.isInteger(Number(config.durationDays)) || Number(config.durationDays) <= 0) { return false; } if (!Array.isArray(config.rewards) || config.rewards.length !== REWARD_DAYS) { return false; } return config.rewards.every((reward, index) => Number(reward?.day) === index + 1 && Array.isArray(reward.items) && reward.items.length > 0 && reward.items.every(isValidRewardItem) ); } function isValidRewardItem(item: any) { const validTypes = ["coin", "freeze", "hammer", "magic_wand", "infinite_health", "cat_skin"]; const validCatId = item?.type !== "cat_skin" || (Number.isInteger(Number(item.itemId)) && Number(item.itemId) > 0); return validTypes.includes(item?.type) && Number.isFinite(Number(item.count)) && Number(item.count) > 0 && validCatId && (item.itemId === undefined || Number.isFinite(Number(item.itemId))); } export async function getActiveSignInConfig(): Promise { try { const result = await db.collection(CONFIG_COLLECTION).where({ enabled: true }).get(); const configs = Array.isArray(result.data) ? result.data.filter(isValidSignInConfig) : []; if (configs.length !== 1) { return null; } configs.sort((left, right) => Number(right.updatedAt || right.createdAt || 0) - Number(left.updatedAt || left.createdAt || 0)); return configs[0]; } catch { return null; } } export async function getSignInConfig(activityId: string): Promise { try { const result = await db.collection(CONFIG_COLLECTION).where({ activityId }).getOne(); return isValidSignInConfig(result.data) ? result.data : null; } catch { return null; } } export async function activateSignInActivity(user: any, now = Date.now()) { if (!user?._id || normalizeSignInState(user.signInActivity)) { return { activated: false, state: normalizeSignInState(user?.signInActivity), config: null }; } const config = await getActiveSignInConfig(); if (!config || getUserSignInLevel(user) < Number(config.triggerLevel)) { return { activated: false, state: null, config }; } const startAt = getChinaDayStart(now); const state: SignInActivityState = { activityId: config.activityId, startAt, endAt: startAt + Number(config.durationDays) * DAY_MS, activatedAt: now, completedAt: null, }; const result = await db.collection(USERS_COLLECTION) .where({ _id: user._id }) .update({ signInActivity: state }); const activated = getUpdatedCount(result) > 0; if (activated) { user.signInActivity = state; } return { activated, state: activated ? state : normalizeSignInState(user.signInActivity), config }; } function getUpdatedCount(result: any) { const count = Number(result?.updated ?? result?.matched ?? result?.data?.updated); return Number.isFinite(count) ? count : 1; } export async function getSignInClaims(uid: string, activityId: string) { const result = await db.collection(CLAIM_COLLECTION).where({ uid, activityId }).get(); const claims = Array.isArray(result.data) ? result.data.slice() : []; claims.sort((left, right) => Number(left.rewardDay) - Number(right.rewardDay)); return claims; } export function buildClaimId(uid: string, activityId: string, dateKey: string) { return createHash("sha256").update(`${uid}:${activityId}:${dateKey}`).digest("hex"); } export async function addSignInClaim(payload: any) { return db.collection(CLAIM_COLLECTION).add(payload); } export async function markSignInCompleted(uid: string, state: SignInActivityState, completedAt: number) { const nextState = { ...state, completedAt }; await db.collection(USERS_COLLECTION).where({ _id: uid }).update({ signInActivity: nextState }); return nextState; } export function getSignInActivityStatus(state: SignInActivityState | null, claimedCount: number, now = Date.now()) { if (!state) { return "locked" as const; } if (claimedCount >= REWARD_DAYS || state.completedAt) { return "completed" as const; } if (now >= state.endAt) { return "expired" as const; } return "active" as const; } export const SIGN_IN_REWARD_DAYS = REWARD_DAYS;