177 lines
4.6 KiB
JavaScript
177 lines
4.6 KiB
JavaScript
|
|
const config = require("../config/index");
|
||
|
|
const { formatDate } = require("../utils/date");
|
||
|
|
const { safeGetStorage, safeSetStorage } = require("../utils/storage");
|
||
|
|
const { normalizeRegionArray, deriveRegionFromParts, getRegionParts } = require("../utils/region");
|
||
|
|
|
||
|
|
const STORAGE_KEYS = {
|
||
|
|
history: "fortune:history",
|
||
|
|
quotaPrefix: "fortune:quota:",
|
||
|
|
form: "fortune:form"
|
||
|
|
};
|
||
|
|
|
||
|
|
function createDefaultForm() {
|
||
|
|
return {
|
||
|
|
birthDate: formatDate(new Date()),
|
||
|
|
birthTime: "",
|
||
|
|
birthProvince: "",
|
||
|
|
birthCity: "",
|
||
|
|
birthDistrict: "",
|
||
|
|
birthRegion: []
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function normalizeForm(payload, fallback) {
|
||
|
|
const base = fallback || createDefaultForm();
|
||
|
|
const toStringValue = (value, fallbackValue = "") => (typeof value === "string" ? value : fallbackValue);
|
||
|
|
const normalized = {
|
||
|
|
birthDate: toStringValue(payload && payload.birthDate, base.birthDate),
|
||
|
|
birthTime: toStringValue(payload && payload.birthTime, base.birthTime || ""),
|
||
|
|
birthProvince: toStringValue(payload && payload.birthProvince, base.birthProvince || ""),
|
||
|
|
birthCity: toStringValue(payload && payload.birthCity, base.birthCity || ""),
|
||
|
|
birthDistrict: toStringValue(payload && payload.birthDistrict, base.birthDistrict || ""),
|
||
|
|
birthRegion: []
|
||
|
|
};
|
||
|
|
const persistedRegion = normalizeRegionArray(payload && payload.birthRegion);
|
||
|
|
const baseRegion = normalizeRegionArray(base.birthRegion);
|
||
|
|
if (persistedRegion.length) {
|
||
|
|
normalized.birthRegion = persistedRegion;
|
||
|
|
} else if (baseRegion.length) {
|
||
|
|
normalized.birthRegion = baseRegion;
|
||
|
|
} else {
|
||
|
|
normalized.birthRegion = deriveRegionFromParts({
|
||
|
|
province: normalized.birthProvince,
|
||
|
|
city: normalized.birthCity,
|
||
|
|
district: normalized.birthDistrict
|
||
|
|
});
|
||
|
|
}
|
||
|
|
const parts = getRegionParts(normalized.birthRegion, {
|
||
|
|
province: normalized.birthProvince,
|
||
|
|
city: normalized.birthCity,
|
||
|
|
district: normalized.birthDistrict
|
||
|
|
});
|
||
|
|
normalized.birthProvince = parts.province;
|
||
|
|
normalized.birthCity = parts.city;
|
||
|
|
normalized.birthDistrict = parts.district;
|
||
|
|
return normalized;
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadPersistedForm() {
|
||
|
|
const stored = safeGetStorage(STORAGE_KEYS.form, null);
|
||
|
|
if (stored && typeof stored === "object") {
|
||
|
|
return normalizeForm(stored);
|
||
|
|
}
|
||
|
|
const defaults = createDefaultForm();
|
||
|
|
safeSetStorage(STORAGE_KEYS.form, defaults);
|
||
|
|
return defaults;
|
||
|
|
}
|
||
|
|
|
||
|
|
function persistForm(form) {
|
||
|
|
const normalized = normalizeForm(form);
|
||
|
|
safeSetStorage(STORAGE_KEYS.form, normalized);
|
||
|
|
}
|
||
|
|
|
||
|
|
const state = {
|
||
|
|
form: loadPersistedForm(),
|
||
|
|
currentFortune: null,
|
||
|
|
history: [],
|
||
|
|
quota: {
|
||
|
|
date: "",
|
||
|
|
count: 0
|
||
|
|
},
|
||
|
|
lastError: ""
|
||
|
|
};
|
||
|
|
|
||
|
|
function getQuotaStorageKey(dateStr) {
|
||
|
|
return `${STORAGE_KEYS.quotaPrefix}${dateStr.replace(/-/g, "")}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
function syncQuota() {
|
||
|
|
const today = formatDate(new Date());
|
||
|
|
const storageKey = getQuotaStorageKey(today);
|
||
|
|
const stored = safeGetStorage(storageKey, 0);
|
||
|
|
const count = typeof stored === "number" ? stored : 0;
|
||
|
|
state.quota = {
|
||
|
|
date: today,
|
||
|
|
count
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function incrementQuota() {
|
||
|
|
const today = formatDate(new Date());
|
||
|
|
if (state.quota.date !== today) {
|
||
|
|
syncQuota();
|
||
|
|
}
|
||
|
|
const nextCount = state.quota.count + 1;
|
||
|
|
state.quota = {
|
||
|
|
date: today,
|
||
|
|
count: nextCount
|
||
|
|
};
|
||
|
|
safeSetStorage(getQuotaStorageKey(today), nextCount);
|
||
|
|
}
|
||
|
|
|
||
|
|
function canGenerateToday() {
|
||
|
|
const today = formatDate(new Date());
|
||
|
|
if (state.quota.date !== today) {
|
||
|
|
syncQuota();
|
||
|
|
}
|
||
|
|
return state.quota.count < config.maxDailyQuota;
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadHistory() {
|
||
|
|
const stored = safeGetStorage(STORAGE_KEYS.history, []);
|
||
|
|
state.history = Array.isArray(stored) ? stored : [];
|
||
|
|
}
|
||
|
|
|
||
|
|
function saveHistory() {
|
||
|
|
safeSetStorage(STORAGE_KEYS.history, state.history);
|
||
|
|
}
|
||
|
|
|
||
|
|
function appendHistory(formSnapshot, fortune) {
|
||
|
|
const snapshot = normalizeForm(formSnapshot, state.form);
|
||
|
|
const entry = {
|
||
|
|
id: `${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
||
|
|
createdAt: new Date().toISOString(),
|
||
|
|
formSnapshot: snapshot,
|
||
|
|
fortune
|
||
|
|
};
|
||
|
|
const next = [entry, ...state.history];
|
||
|
|
state.history = next.slice(0, config.historyLimit);
|
||
|
|
saveHistory();
|
||
|
|
return entry;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getHistoryById(id) {
|
||
|
|
return state.history.find((item) => item.id === id);
|
||
|
|
}
|
||
|
|
|
||
|
|
function setForm(partial) {
|
||
|
|
const merged = Object.assign({}, state.form, partial);
|
||
|
|
state.form = normalizeForm(merged, state.form);
|
||
|
|
persistForm(state.form);
|
||
|
|
}
|
||
|
|
|
||
|
|
function setCurrentFortune(fortune) {
|
||
|
|
state.currentFortune = fortune;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getState() {
|
||
|
|
return state;
|
||
|
|
}
|
||
|
|
|
||
|
|
function initStore() {
|
||
|
|
syncQuota();
|
||
|
|
loadHistory();
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
initStore,
|
||
|
|
getState,
|
||
|
|
setForm,
|
||
|
|
setCurrentFortune,
|
||
|
|
appendHistory,
|
||
|
|
loadHistory,
|
||
|
|
getHistoryById,
|
||
|
|
canGenerateToday,
|
||
|
|
incrementQuota
|
||
|
|
};
|