mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-08 03:00:20 +00:00
refactor username
Changed username to be the first name "_" user id
This commit is contained in:
1 parent
53fddc8220
commit
07b12cdd0f
3 files changed
+337
-243
No files matched your search
+265
-189
@@ -1,4 +1,4 @@
|
||||
import {useCallback, useMemo} from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import API from "../components/API/API";
|
||||
import globalApiConfig from "../components/API/api.json";
|
||||
import {
|
||||
@@ -9,10 +9,14 @@ import {
|
||||
} from "../utils/globalDataCache";
|
||||
|
||||
const GLOBAL_BASE = String(
|
||||
process.env.EXPO_PUBLIC_GLOBAL_API_BASE || globalApiConfig.GLOBAL_API_BASE_URL || "",
|
||||
process.env.EXPO_PUBLIC_GLOBAL_API_BASE ||
|
||||
globalApiConfig.GLOBAL_API_BASE_URL ||
|
||||
"",
|
||||
).trim();
|
||||
const GLOBAL_KEY = String(
|
||||
process.env.EXPO_PUBLIC_GLOBAL_API_KEY || globalApiConfig.GLOBAL_API_KEY || "",
|
||||
process.env.EXPO_PUBLIC_GLOBAL_API_KEY ||
|
||||
globalApiConfig.GLOBAL_API_KEY ||
|
||||
"",
|
||||
).trim();
|
||||
const DEFAULT_GLOBAL_PROFILE_IMAGE_URL =
|
||||
String(globalApiConfig.DEFAULT_GLOBAL_PROFILE_IMAGE_URL || "").trim() ||
|
||||
@@ -90,10 +94,10 @@ const withMinLength = (value, min, fallback) => {
|
||||
|
||||
const splitUsername = (username) => {
|
||||
const cleaned = String(username || "").trim();
|
||||
if (!cleaned) return {first: "Global", last: "User"};
|
||||
if (!cleaned) return { first: "Global", last: "User" };
|
||||
const parts = cleaned.split(/[\s._-]+/).filter(Boolean);
|
||||
if (parts.length === 1) {
|
||||
return {first: parts[0], last: "User"};
|
||||
return { first: parts[0], last: "User" };
|
||||
}
|
||||
return {
|
||||
first: parts[0],
|
||||
@@ -104,11 +108,16 @@ const splitUsername = (username) => {
|
||||
const buildGlobalUserPayload = (userId, privateUser = null) => {
|
||||
const privateUsername = firstNonEmpty(privateUser?.username);
|
||||
const nameParts = splitUsername(privateUsername);
|
||||
const syncedUsername = `mad_uid_${userId}`;
|
||||
const firstName = firstNonEmpty(
|
||||
privateUser?.firstName,
|
||||
nameParts.first,
|
||||
"Global",
|
||||
);
|
||||
const syncedUsername = `${firstName}_${userId}`;
|
||||
|
||||
return {
|
||||
UserID: userId,
|
||||
UserFirstname: firstNonEmpty(privateUser?.firstName, nameParts.first, "Global"),
|
||||
UserFirstname: firstName,
|
||||
UserLastname: firstNonEmpty(privateUser?.lastName, nameParts.last, "User"),
|
||||
// The live global API validates phone length >= 12.
|
||||
UserPhone: withMinLength(privateUser?.phone, 12, "000000000000"),
|
||||
@@ -146,215 +155,282 @@ const useGlobalHook = () => {
|
||||
// Handlers ----------------------------
|
||||
|
||||
// Events
|
||||
const getPublicEvents = useCallback(async (options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.publicEvents,
|
||||
async () => {
|
||||
const response = await API.get(withKey(endpoints.events));
|
||||
if (!response.isSuccess) return [];
|
||||
const rows = unwrapList(response.result);
|
||||
return rows.filter((e) => e.EventIspublic);
|
||||
},
|
||||
{ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh},
|
||||
);
|
||||
}, [endpoints.events]);
|
||||
const getPublicEvents = useCallback(
|
||||
async (options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.publicEvents,
|
||||
async () => {
|
||||
const response = await API.get(withKey(endpoints.events));
|
||||
if (!response.isSuccess) return [];
|
||||
const rows = unwrapList(response.result);
|
||||
return rows.filter((e) => e.EventIspublic);
|
||||
},
|
||||
{ ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh },
|
||||
);
|
||||
},
|
||||
[endpoints.events],
|
||||
);
|
||||
|
||||
const getEvent = useCallback(async (eventId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.event(eventId),
|
||||
async () => {
|
||||
const response = await API.get(withKey(`${endpoints.events}/${eventId}`));
|
||||
if (!response.isSuccess) return null;
|
||||
return unwrapSingle(response.result);
|
||||
},
|
||||
{ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh},
|
||||
);
|
||||
}, [endpoints.events]);
|
||||
const getEvent = useCallback(
|
||||
async (eventId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.event(eventId),
|
||||
async () => {
|
||||
const response = await API.get(
|
||||
withKey(`${endpoints.events}/${eventId}`),
|
||||
);
|
||||
if (!response.isSuccess) return null;
|
||||
return unwrapSingle(response.result);
|
||||
},
|
||||
{ ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh },
|
||||
);
|
||||
},
|
||||
[endpoints.events],
|
||||
);
|
||||
|
||||
// Players
|
||||
const getPlayersByEvent = useCallback(async (eventId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.playersByEvent(eventId),
|
||||
async () => {
|
||||
const response = await API.get(withKey(`${endpoints.players}/events/${eventId}`));
|
||||
if (!response.isSuccess) return [];
|
||||
return unwrapList(response.result);
|
||||
},
|
||||
{ttlMs: CACHE_TTL.players, forceRefresh: options.forceRefresh},
|
||||
);
|
||||
}, [endpoints.players]);
|
||||
const getPlayersByEvent = useCallback(
|
||||
async (eventId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.playersByEvent(eventId),
|
||||
async () => {
|
||||
const response = await API.get(
|
||||
withKey(`${endpoints.players}/events/${eventId}`),
|
||||
);
|
||||
if (!response.isSuccess) return [];
|
||||
return unwrapList(response.result);
|
||||
},
|
||||
{ ttlMs: CACHE_TTL.players, forceRefresh: options.forceRefresh },
|
||||
);
|
||||
},
|
||||
[endpoints.players],
|
||||
);
|
||||
|
||||
const joinEvent = useCallback(async (data) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
const response = await API.post(withKey(endpoints.players), data);
|
||||
if (!response.isSuccess) return null;
|
||||
const joined = unwrapSingle(response.result);
|
||||
const eventId = data?.PlayerEventID ?? joined?.PlayerEventID;
|
||||
if (eventId !== undefined && eventId !== null) {
|
||||
clearGlobalDataCache(CACHE_KEY.playersByEvent(eventId));
|
||||
}
|
||||
return joined;
|
||||
}, [endpoints.players]);
|
||||
const joinEvent = useCallback(
|
||||
async (data) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
const response = await API.post(withKey(endpoints.players), data);
|
||||
if (!response.isSuccess) return null;
|
||||
const joined = unwrapSingle(response.result);
|
||||
const eventId = data?.PlayerEventID ?? joined?.PlayerEventID;
|
||||
if (eventId !== undefined && eventId !== null) {
|
||||
clearGlobalDataCache(CACHE_KEY.playersByEvent(eventId));
|
||||
}
|
||||
return joined;
|
||||
},
|
||||
[endpoints.players],
|
||||
);
|
||||
|
||||
// Caches
|
||||
const getCachesByEvent = useCallback(async (eventId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.cachesByEvent(eventId),
|
||||
async () => {
|
||||
const response = await API.get(withKey(`${endpoints.caches}/events/${eventId}`));
|
||||
if (!response.isSuccess) return [];
|
||||
return unwrapList(response.result);
|
||||
},
|
||||
{ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh},
|
||||
);
|
||||
}, [endpoints.caches]);
|
||||
const getCachesByEvent = useCallback(
|
||||
async (eventId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.cachesByEvent(eventId),
|
||||
async () => {
|
||||
const response = await API.get(
|
||||
withKey(`${endpoints.caches}/events/${eventId}`),
|
||||
);
|
||||
if (!response.isSuccess) return [];
|
||||
return unwrapList(response.result);
|
||||
},
|
||||
{ ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh },
|
||||
);
|
||||
},
|
||||
[endpoints.caches],
|
||||
);
|
||||
|
||||
const getCache = useCallback(async (cacheId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.cacheById(cacheId),
|
||||
async () => {
|
||||
const response = await API.get(withKey(`${endpoints.caches}/${cacheId}`));
|
||||
if (!response.isSuccess) return null;
|
||||
return unwrapSingle(response.result);
|
||||
},
|
||||
{ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh},
|
||||
);
|
||||
}, [endpoints.caches]);
|
||||
const getCache = useCallback(
|
||||
async (cacheId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.cacheById(cacheId),
|
||||
async () => {
|
||||
const response = await API.get(
|
||||
withKey(`${endpoints.caches}/${cacheId}`),
|
||||
);
|
||||
if (!response.isSuccess) return null;
|
||||
return unwrapSingle(response.result);
|
||||
},
|
||||
{ ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh },
|
||||
);
|
||||
},
|
||||
[endpoints.caches],
|
||||
);
|
||||
|
||||
// Finds
|
||||
const getFindsByEvent = useCallback(async (eventId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.findsByEvent(eventId),
|
||||
async () => {
|
||||
const response = await API.get(withKey(`${endpoints.finds}/events/${eventId}`));
|
||||
if (!response.isSuccess) return [];
|
||||
return unwrapList(response.result);
|
||||
},
|
||||
{ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh},
|
||||
);
|
||||
}, [endpoints.finds]);
|
||||
const getFindsByEvent = useCallback(
|
||||
async (eventId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.findsByEvent(eventId),
|
||||
async () => {
|
||||
const response = await API.get(
|
||||
withKey(`${endpoints.finds}/events/${eventId}`),
|
||||
);
|
||||
if (!response.isSuccess) return [];
|
||||
return unwrapList(response.result);
|
||||
},
|
||||
{ ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh },
|
||||
);
|
||||
},
|
||||
[endpoints.finds],
|
||||
);
|
||||
|
||||
const getFindsByPlayer = useCallback(async (playerId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.findsByPlayer(playerId),
|
||||
async () => {
|
||||
const response = await API.get(withKey(`${endpoints.finds}/players/${playerId}`));
|
||||
if (!response.isSuccess) return [];
|
||||
return unwrapList(response.result);
|
||||
},
|
||||
{ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh},
|
||||
);
|
||||
}, [endpoints.finds]);
|
||||
const getFindsByPlayer = useCallback(
|
||||
async (playerId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.findsByPlayer(playerId),
|
||||
async () => {
|
||||
const response = await API.get(
|
||||
withKey(`${endpoints.finds}/players/${playerId}`),
|
||||
);
|
||||
if (!response.isSuccess) return [];
|
||||
return unwrapList(response.result);
|
||||
},
|
||||
{ ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh },
|
||||
);
|
||||
},
|
||||
[endpoints.finds],
|
||||
);
|
||||
|
||||
const logFind = useCallback(async (data) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
const response = await API.post(withKey(endpoints.finds), data);
|
||||
if (!response.isSuccess) return null;
|
||||
const find = unwrapSingle(response.result);
|
||||
if (data?.FindPlayerID !== undefined && data?.FindPlayerID !== null) {
|
||||
clearGlobalDataCache(CACHE_KEY.findsByPlayer(data.FindPlayerID));
|
||||
}
|
||||
// Event ID is not always present in payload, so clear all event-level find caches.
|
||||
clearGlobalDataCacheByPrefix("global:finds:event:");
|
||||
return find;
|
||||
}, [endpoints.finds]);
|
||||
const logFind = useCallback(
|
||||
async (data) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
const response = await API.post(withKey(endpoints.finds), data);
|
||||
if (!response.isSuccess) return null;
|
||||
const find = unwrapSingle(response.result);
|
||||
if (data?.FindPlayerID !== undefined && data?.FindPlayerID !== null) {
|
||||
clearGlobalDataCache(CACHE_KEY.findsByPlayer(data.FindPlayerID));
|
||||
}
|
||||
// Event ID is not always present in payload, so clear all event-level find caches.
|
||||
clearGlobalDataCacheByPrefix("global:finds:event:");
|
||||
return find;
|
||||
},
|
||||
[endpoints.finds],
|
||||
);
|
||||
|
||||
// Users
|
||||
const getUsers = useCallback(async (options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.users,
|
||||
async () => {
|
||||
const response = await API.get(withKey(endpoints.users));
|
||||
if (!response.isSuccess) return [];
|
||||
return unwrapList(response.result);
|
||||
},
|
||||
{ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh},
|
||||
);
|
||||
}, [endpoints.users]);
|
||||
const getUsers = useCallback(
|
||||
async (options = {}) => {
|
||||
if (!isGlobalApiReady()) return [];
|
||||
return getOrFetchGlobalData(
|
||||
CACHE_KEY.users,
|
||||
async () => {
|
||||
const response = await API.get(withKey(endpoints.users));
|
||||
if (!response.isSuccess) return [];
|
||||
return unwrapList(response.result);
|
||||
},
|
||||
{ ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh },
|
||||
);
|
||||
},
|
||||
[endpoints.users],
|
||||
);
|
||||
|
||||
const getUser = useCallback(async (userId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
const getUser = useCallback(
|
||||
async (userId, options = {}) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
|
||||
const key = CACHE_KEY.userById(userId);
|
||||
return getOrFetchGlobalData(
|
||||
key,
|
||||
async () => {
|
||||
// Some global API deployments expose list-only user routes.
|
||||
const byIdResponse = await API.get(withKey(`${endpoints.users}/${userId}`));
|
||||
if (byIdResponse.isSuccess) {
|
||||
const single = unwrapSingle(byIdResponse.result);
|
||||
if (single) return single;
|
||||
}
|
||||
const key = CACHE_KEY.userById(userId);
|
||||
return getOrFetchGlobalData(
|
||||
key,
|
||||
async () => {
|
||||
// Some global API deployments expose list-only user routes.
|
||||
const byIdResponse = await API.get(
|
||||
withKey(`${endpoints.users}/${userId}`),
|
||||
);
|
||||
if (byIdResponse.isSuccess) {
|
||||
const single = unwrapSingle(byIdResponse.result);
|
||||
if (single) return single;
|
||||
}
|
||||
|
||||
const users = await getUsers({forceRefresh: options.forceRefresh});
|
||||
return users.find((user) => String(user.UserID) === String(userId)) || null;
|
||||
},
|
||||
{ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh},
|
||||
);
|
||||
}, [endpoints.users, getUsers]);
|
||||
const users = await getUsers({ forceRefresh: options.forceRefresh });
|
||||
return (
|
||||
users.find((user) => String(user.UserID) === String(userId)) || null
|
||||
);
|
||||
},
|
||||
{ ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh },
|
||||
);
|
||||
},
|
||||
[endpoints.users, getUsers],
|
||||
);
|
||||
|
||||
const createUser = useCallback(async (payload) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
const response = await API.post(withKey(endpoints.users), payload);
|
||||
if (!response.isSuccess) return null;
|
||||
const created = unwrapSingle(response.result);
|
||||
const createdUserId = created?.UserID ?? payload?.UserID;
|
||||
if (createdUserId !== undefined && createdUserId !== null) {
|
||||
const createUser = useCallback(
|
||||
async (payload) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
const response = await API.post(withKey(endpoints.users), payload);
|
||||
if (!response.isSuccess) return null;
|
||||
const created = unwrapSingle(response.result);
|
||||
const createdUserId = created?.UserID ?? payload?.UserID;
|
||||
if (createdUserId !== undefined && createdUserId !== null) {
|
||||
writeGlobalDataCache(
|
||||
CACHE_KEY.userById(createdUserId),
|
||||
created || payload,
|
||||
CACHE_TTL.users,
|
||||
);
|
||||
}
|
||||
clearGlobalDataCache(CACHE_KEY.users);
|
||||
return created;
|
||||
},
|
||||
[endpoints.users],
|
||||
);
|
||||
|
||||
const updateUser = useCallback(
|
||||
async (userId, payload) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
const response = await API.put(
|
||||
withKey(`${endpoints.users}/${userId}`),
|
||||
payload,
|
||||
);
|
||||
if (!response.isSuccess) return null;
|
||||
const updated = unwrapSingle(response.result) || payload;
|
||||
writeGlobalDataCache(
|
||||
CACHE_KEY.userById(createdUserId),
|
||||
created || payload,
|
||||
CACHE_KEY.userById(userId),
|
||||
updated,
|
||||
CACHE_TTL.users,
|
||||
);
|
||||
}
|
||||
clearGlobalDataCache(CACHE_KEY.users);
|
||||
return created;
|
||||
}, [endpoints.users]);
|
||||
|
||||
const updateUser = useCallback(async (userId, payload) => {
|
||||
if (!isGlobalApiReady()) return null;
|
||||
const response = await API.put(withKey(`${endpoints.users}/${userId}`), payload);
|
||||
if (!response.isSuccess) return null;
|
||||
const updated = unwrapSingle(response.result) || payload;
|
||||
writeGlobalDataCache(CACHE_KEY.userById(userId), updated, CACHE_TTL.users);
|
||||
clearGlobalDataCache(CACHE_KEY.users);
|
||||
return updated;
|
||||
}, [endpoints.users]);
|
||||
clearGlobalDataCache(CACHE_KEY.users);
|
||||
return updated;
|
||||
},
|
||||
[endpoints.users],
|
||||
);
|
||||
|
||||
// Only called by Join Global Game flow to mirror a private user into global API schema.
|
||||
const ensureGlobalUser = useCallback(async (userId, privateUser = null) => {
|
||||
if (!userId || !isGlobalApiReady()) return null;
|
||||
const ensureGlobalUser = useCallback(
|
||||
async (userId, privateUser = null) => {
|
||||
if (!userId || !isGlobalApiReady()) return null;
|
||||
|
||||
const seed = buildGlobalUserPayload(userId, privateUser);
|
||||
const seed = buildGlobalUserPayload(userId, privateUser);
|
||||
|
||||
// Primary lookup by deterministic username bridge.
|
||||
const users = await getUsers();
|
||||
const bySyncedUsername = users.find(
|
||||
(user) => String(user.UserUsername) === String(seed.UserUsername),
|
||||
);
|
||||
if (bySyncedUsername) return bySyncedUsername;
|
||||
// Primary lookup by deterministic username bridge.
|
||||
const users = await getUsers();
|
||||
const bySyncedUsername = users.find(
|
||||
(user) => String(user.UserUsername) === String(seed.UserUsername),
|
||||
);
|
||||
if (bySyncedUsername) return bySyncedUsername;
|
||||
|
||||
const existing = await getUser(userId);
|
||||
if (existing) return existing;
|
||||
const existing = await getUser(userId);
|
||||
if (existing) return existing;
|
||||
|
||||
const created = await createUser(seed);
|
||||
if (created) return created;
|
||||
const created = await createUser(seed);
|
||||
if (created) return created;
|
||||
|
||||
// Final fallback: check list endpoint again after attempted creation.
|
||||
const refreshed = await getUsers({forceRefresh: true});
|
||||
return (
|
||||
refreshed.find((user) => String(user.UserUsername) === String(seed.UserUsername)) ||
|
||||
refreshed.find((user) => String(user.UserID) === String(userId)) ||
|
||||
null
|
||||
);
|
||||
}, [createUser, getUser, getUsers]);
|
||||
// Final fallback: check list endpoint again after attempted creation.
|
||||
const refreshed = await getUsers({ forceRefresh: true });
|
||||
return (
|
||||
refreshed.find(
|
||||
(user) => String(user.UserUsername) === String(seed.UserUsername),
|
||||
) ||
|
||||
refreshed.find((user) => String(user.UserID) === String(userId)) ||
|
||||
null
|
||||
);
|
||||
},
|
||||
[createUser, getUser, getUsers],
|
||||
);
|
||||
|
||||
// Return --------------------------------
|
||||
|
||||
|
||||
Reference in new issue
Block a user