mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-07 19:00:20 +00:00
Refactor database interaction to use centralized API; update game type retrieval and cache management logic (minor changes lol)
This commit is contained in:
1 parent
17ed120ce8
commit
d67522a71d
8 files changed
+104
-641
No files matched your search
@@ -16,11 +16,11 @@ expo start --android
|
|||||||
- `src/components/layout`: shared screen wrapper.
|
- `src/components/layout`: shared screen wrapper.
|
||||||
- `src/components/UI`: shared UI components.
|
- `src/components/UI`: shared UI components.
|
||||||
- `src/components/screens`: app screens.
|
- `src/components/screens`: app screens.
|
||||||
- `src/hooks`: app state, local DB link, and feature hooks.
|
- `src/hooks`: app state, API link, and feature hooks.
|
||||||
- `DB`: local JSON data files.
|
- `DB`: local JSON data files.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- The app uses local JSON data from `DB` through `src/hooks/DbController.js`.
|
- The app accesses game/auth data through the centralized API endpoint via `src/hooks/dbLink.js`.
|
||||||
- Navigation is handled with React Navigation native stack.
|
- Navigation is handled with React Navigation native stack.
|
||||||
|
|
||||||
@@ -1,50 +1,68 @@
|
|||||||
import React from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {Pressable, StyleSheet, Text, View} from 'react-native';
|
import {Pressable, StyleSheet, Text, View} from 'react-native';
|
||||||
import useGameHook from '../../hooks/useGameHook';
|
import useGameHook from '../../hooks/useGameHook';
|
||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
|
|
||||||
const GameTypeScreen = ({ navigation }) => {
|
const GameTypeScreen = ({ navigation }) => {
|
||||||
const { getGameTypes } = useGameHook();
|
const { getGameTypes } = useGameHook();
|
||||||
const gameTypes = getGameTypes();
|
const [gameTypes, setGameTypes] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
const types = await getGameTypes();
|
||||||
|
if (isMounted) {
|
||||||
|
setGameTypes(types || []);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
load();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
};
|
||||||
|
}, [getGameTypes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen style={styles.container}>
|
||||||
<Text style={styles.title}>Select Game Type</Text>
|
<Text style={styles.title}>Select Game Type</Text>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
{gameTypes && gameTypes.map((type) => (
|
{gameTypes && gameTypes.map((type) => (
|
||||||
<Pressable key={type} style={styles.button} onPress={() => navigation.navigate('GameLobbyScreen')}>
|
<Pressable key={type} style={styles.button} onPress={() => navigation.navigate('GameLobbyScreen')}>
|
||||||
<Text style={styles.buttonText}>{type}</Text>
|
<Text style={styles.buttonText}>{type}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
gap: 12,
|
gap: 12,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
backgroundColor: '#2563eb',
|
backgroundColor: '#2563eb',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
buttonText: {
|
buttonText: {
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default GameTypeScreen;
|
export default GameTypeScreen;
|
||||||
|
|
||||||
@@ -14,12 +14,13 @@ const ManageGameScreen = () => {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [cacheRecords, setCacheRecords] = useState([]);
|
const [cacheRecords, setCacheRecords] = useState([]);
|
||||||
|
|
||||||
const loadCaches = useCallback(() => {
|
const loadCaches = useCallback(async () => {
|
||||||
if (!session.currentGid) {
|
if (!session.currentGid) {
|
||||||
setCacheRecords([]);
|
setCacheRecords([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCacheRecords(getCaches(session.currentGid, null));
|
const rows = await getCaches(session.currentGid, null);
|
||||||
|
setCacheRecords(rows || []);
|
||||||
}, [getCaches, session.currentGid]);
|
}, [getCaches, session.currentGid]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -63,11 +64,11 @@ const ManageGameScreen = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleAddCache = (coordinate) => {
|
const handleAddCache = async (coordinate) => {
|
||||||
if (!session.currentGid) {
|
if (!session.currentGid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
upsertCache({
|
await upsertCache({
|
||||||
gid: session.currentGid,
|
gid: session.currentGid,
|
||||||
latitude: coordinate.latitude,
|
latitude: coordinate.latitude,
|
||||||
longitude: coordinate.longitude,
|
longitude: coordinate.longitude,
|
||||||
@@ -75,10 +76,10 @@ const ManageGameScreen = () => {
|
|||||||
clue: `Cache ${cacheRecords.length + 1}`,
|
clue: `Cache ${cacheRecords.length + 1}`,
|
||||||
subgroupId: 1,
|
subgroupId: 1,
|
||||||
});
|
});
|
||||||
loadCaches();
|
await loadCaches();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMoveCache = (cacheId, coordinate) => {
|
const handleMoveCache = async (cacheId, coordinate) => {
|
||||||
if (!session.currentGid) {
|
if (!session.currentGid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -86,7 +87,7 @@ const ManageGameScreen = () => {
|
|||||||
if (!current) {
|
if (!current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
upsertCache({
|
await upsertCache({
|
||||||
gid: session.currentGid,
|
gid: session.currentGid,
|
||||||
cacheId,
|
cacheId,
|
||||||
latitude: coordinate.latitude,
|
latitude: coordinate.latitude,
|
||||||
@@ -95,7 +96,7 @@ const ManageGameScreen = () => {
|
|||||||
clue: current.clue,
|
clue: current.clue,
|
||||||
subgroupId: current.subgroupId,
|
subgroupId: current.subgroupId,
|
||||||
});
|
});
|
||||||
loadCaches();
|
await loadCaches();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!session.currentGid) {
|
if (!session.currentGid) {
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ const MapScreen = () => {
|
|||||||
const subgroupFilter = session.currentSGid;
|
const subgroupFilter = session.currentSGid;
|
||||||
const { visibleCache, isClaiming, setIsClaiming } = usePlayerGame(userLocation, heading, activeCaches);
|
const { visibleCache, isClaiming, setIsClaiming } = usePlayerGame(userLocation, heading, activeCaches);
|
||||||
|
|
||||||
const loadCaches = useCallback(() => {
|
const loadCaches = useCallback(async () => {
|
||||||
if (!session.currentGid) {
|
if (!session.currentGid) {
|
||||||
setCacheRecords([]);
|
setCacheRecords([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rows = getCaches(session.currentGid, subgroupFilter);
|
const rows = await getCaches(session.currentGid, subgroupFilter);
|
||||||
setCacheRecords(rows);
|
setCacheRecords(rows || []);
|
||||||
}, [getCaches, session.currentGid, subgroupFilter]);
|
}, [getCaches, session.currentGid, subgroupFilter]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -99,18 +99,18 @@ const MapScreen = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClaim = (cacheId) => {
|
const handleClaim = async (cacheId) => {
|
||||||
if (!session.currentGid) {
|
if (!session.currentGid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
claimCache({
|
await claimCache({
|
||||||
gid: session.currentGid,
|
gid: session.currentGid,
|
||||||
cacheId,
|
cacheId,
|
||||||
uid: session.currentUid,
|
uid: session.currentUid,
|
||||||
tid: session.currentTid,
|
tid: session.currentTid,
|
||||||
});
|
});
|
||||||
setIsClaiming(false);
|
setIsClaiming(false);
|
||||||
loadCaches();
|
await loadCaches();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!session.currentGid) {
|
if (!session.currentGid) {
|
||||||
|
|||||||
@@ -1,495 +0,0 @@
|
|||||||
import buildDbLink from './dbLink';
|
|
||||||
|
|
||||||
class DbController {
|
|
||||||
constructor(db = buildDbLink()) {
|
|
||||||
this.db = db;
|
|
||||||
}
|
|
||||||
|
|
||||||
hashPassword(value) {
|
|
||||||
let hash = 0;
|
|
||||||
for (let i = 0; i < value.length; i += 1) {
|
|
||||||
hash = (hash << 5) - hash + value.charCodeAt(i);
|
|
||||||
hash |= 0;
|
|
||||||
}
|
|
||||||
return `H_${Math.abs(hash)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
toMinuteIso(date = new Date()) {
|
|
||||||
return `${date.toISOString().slice(0, 16)}Z`;
|
|
||||||
}
|
|
||||||
|
|
||||||
generateJoinCode(length = 5) {
|
|
||||||
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
||||||
let code = '';
|
|
||||||
for (let i = 0; i < length; i += 1) {
|
|
||||||
code += charset[Math.floor(Math.random() * charset.length)];
|
|
||||||
}
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
generateUniqueJoinCode(collection, key) {
|
|
||||||
let code = this.generateJoinCode(Math.random() > 0.5 ? 5 : 6);
|
|
||||||
while (collection.some((item) => item[key] === code)) {
|
|
||||||
code = this.generateJoinCode(Math.random() > 0.5 ? 5 : 6);
|
|
||||||
}
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextId(counterKey) {
|
|
||||||
const keyToCollection = {
|
|
||||||
user: { collection: 'users', field: 'Uid' },
|
|
||||||
group: { collection: 'groups', field: 'Gid' },
|
|
||||||
team: { collection: 'teams', field: 'Tid' },
|
|
||||||
};
|
|
||||||
|
|
||||||
const config = keyToCollection[counterKey];
|
|
||||||
if (!config) {
|
|
||||||
throw new Error(`Unsupported counter key: ${counterKey}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = this.db[config.collection] || [];
|
|
||||||
const maxId = rows.reduce((max, row) => {
|
|
||||||
const value = Number(row[config.field] || 0);
|
|
||||||
return value > max ? value : max;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
return maxId + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUserByEmail(email) {
|
|
||||||
return this.db.users.find((u) => u.email.toLowerCase() === email.toLowerCase()) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUserById(uid) {
|
|
||||||
return this.db.users.find((u) => u.Uid === Number(uid)) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getGroupById(gid) {
|
|
||||||
return this.db.groups.find((group) => group.Gid === Number(gid)) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
register(payload) {
|
|
||||||
const { username, email, password, confirmPassword, accountType } = payload;
|
|
||||||
|
|
||||||
if (!username || !email || !password || !confirmPassword) {
|
|
||||||
throw new Error('Missing required registration fields');
|
|
||||||
}
|
|
||||||
if (password !== confirmPassword) {
|
|
||||||
throw new Error('Passwords do not match');
|
|
||||||
}
|
|
||||||
if (this.getUserByEmail(email)) {
|
|
||||||
throw new Error('Email already in use');
|
|
||||||
}
|
|
||||||
|
|
||||||
const uid = this.nextId('user');
|
|
||||||
const user = {
|
|
||||||
Uid: uid,
|
|
||||||
username,
|
|
||||||
email,
|
|
||||||
passwordHash: this.hashPassword(password),
|
|
||||||
isBusiness: accountType === 'Business/School',
|
|
||||||
IsAcceptedAdmin: false,
|
|
||||||
Gid: null,
|
|
||||||
SGid: null,
|
|
||||||
TGid: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.db.users.push(user);
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
login(payload) {
|
|
||||||
const { email, password } = payload;
|
|
||||||
const user = this.getUserByEmail(email);
|
|
||||||
if (!user) {
|
|
||||||
throw new Error('Email not found');
|
|
||||||
}
|
|
||||||
const incomingHash = this.hashPassword(password);
|
|
||||||
if (incomingHash !== user.passwordHash) {
|
|
||||||
throw new Error('Invalid password');
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
getGameTypes() {
|
|
||||||
return this.db.game_types;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSubgroupByJoinCode(joinCode) {
|
|
||||||
return this.db.subgroups.find(
|
|
||||||
(subgroup) => subgroup.JoinCode && subgroup.JoinCode === joinCode.toUpperCase(),
|
|
||||||
) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureMembership(uid, gid, sgid, isAcceptedAdmin) {
|
|
||||||
const existing = this.db.subgroup_memberships.find(
|
|
||||||
(membership) => membership.Uid === uid && membership.Gid === gid,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
existing.SGid = sgid;
|
|
||||||
existing.IsAcceptedAdmin = isAcceptedAdmin;
|
|
||||||
return existing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const membership = {
|
|
||||||
Uid: uid,
|
|
||||||
Gid: gid,
|
|
||||||
SGid: sgid,
|
|
||||||
IsAcceptedAdmin: isAcceptedAdmin,
|
|
||||||
};
|
|
||||||
this.db.subgroup_memberships.push(membership);
|
|
||||||
return membership;
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureGroupGameData(gid) {
|
|
||||||
const existing = this.db.game_data.find((entry) => entry.Gid === gid);
|
|
||||||
if (existing) {
|
|
||||||
return existing;
|
|
||||||
}
|
|
||||||
const payload = { Gid: gid, GeoCaches: [] };
|
|
||||||
this.db.game_data.push(payload);
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasActiveAdmin(gid) {
|
|
||||||
return this.db.subgroup_memberships.some(
|
|
||||||
(membership) => membership.Gid === gid && membership.SGid === 0 && membership.IsAcceptedAdmin,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
canJoinTeamForSubgroup(group, sgid) {
|
|
||||||
return group.TeamsEnabled && sgid > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
createPrivateGame(payload) {
|
|
||||||
const { userId, groupName, businessOrSchoolName, gameType = 'private', isBusiness = false } = payload;
|
|
||||||
|
|
||||||
const user = this.getUserById(Number(userId));
|
|
||||||
if (!user) {
|
|
||||||
throw new Error('User not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = this.db.groups.find(
|
|
||||||
(group) => group.CreatedByUid === Number(userId) && group.GameType === 'private',
|
|
||||||
);
|
|
||||||
if (existing) {
|
|
||||||
return existing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gid = this.nextId('group');
|
|
||||||
const isBusinessGroup = Boolean(isBusiness);
|
|
||||||
const group = {
|
|
||||||
Gid: gid,
|
|
||||||
GroupName: groupName,
|
|
||||||
BusinessOrSchoolName: businessOrSchoolName || groupName,
|
|
||||||
GameType: gameType,
|
|
||||||
AdminJoinCode: this.generateUniqueJoinCode(this.db.groups, 'AdminJoinCode'),
|
|
||||||
CreatedByUid: user.Uid,
|
|
||||||
MaxMemberSubgroups: isBusinessGroup ? 999 : 1,
|
|
||||||
TeamsEnabled: true,
|
|
||||||
IsBusinessGroup: isBusinessGroup,
|
|
||||||
ApprovedAdmins: isBusinessGroup ? [user.Uid] : [],
|
|
||||||
CreatedAt: this.toMinuteIso(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.db.groups.push(group);
|
|
||||||
this.ensureGroupGameData(gid);
|
|
||||||
|
|
||||||
this.db.subgroups.push({
|
|
||||||
SGid: 0,
|
|
||||||
SubGroupName: 'Admins',
|
|
||||||
Gid: group.Gid,
|
|
||||||
JoinCode: null,
|
|
||||||
IsAdminGroup: true,
|
|
||||||
IsGameStarted: false,
|
|
||||||
CacheTriggerMeters: 20,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isBusinessGroup) {
|
|
||||||
const memberSGid = this.db.subgroups.filter((sg) => sg.Gid === gid).length;
|
|
||||||
this.db.subgroups.push({
|
|
||||||
SGid: memberSGid,
|
|
||||||
SubGroupName: 'Members',
|
|
||||||
Gid: gid,
|
|
||||||
JoinCode: this.generateUniqueJoinCode(this.db.subgroups, 'JoinCode'),
|
|
||||||
IsAdminGroup: false,
|
|
||||||
IsGameStarted: false,
|
|
||||||
CacheTriggerMeters: 20,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ownerSgid = isBusinessGroup ? 0 : 1;
|
|
||||||
this.ensureMembership(user.Uid, gid, ownerSgid, isBusinessGroup);
|
|
||||||
user.Gid = gid;
|
|
||||||
user.SGid = ownerSgid;
|
|
||||||
user.IsAcceptedAdmin = isBusinessGroup;
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
joinPrivateGame(payload) {
|
|
||||||
const { userId, joinCode } = payload;
|
|
||||||
const user = this.getUserById(Number(userId));
|
|
||||||
const subgroup = this.getSubgroupByJoinCode(joinCode);
|
|
||||||
|
|
||||||
if (!user || !subgroup || subgroup.IsAdminGroup) {
|
|
||||||
throw new Error('Invalid subgroup join code');
|
|
||||||
}
|
|
||||||
|
|
||||||
const group = this.getGroupById(subgroup.Gid);
|
|
||||||
if (!group) {
|
|
||||||
throw new Error('Group not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ensureMembership(user.Uid, subgroup.Gid, subgroup.SGid, false);
|
|
||||||
user.Gid = group.Gid;
|
|
||||||
user.SGid = subgroup.SGid;
|
|
||||||
user.IsAcceptedAdmin = false;
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
joinAsAdmin(payload) {
|
|
||||||
const { userId, adminJoinCode } = payload;
|
|
||||||
const user = this.getUserById(Number(userId));
|
|
||||||
const group = this.db.groups.find((g) => g.AdminJoinCode === adminJoinCode);
|
|
||||||
|
|
||||||
if (!user || !group) {
|
|
||||||
throw new Error('Invalid admin join code or user not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasActiveAdmin(group.Gid)) {
|
|
||||||
this.db.admin_waitlist.push({
|
|
||||||
Uid: user.Uid,
|
|
||||||
Gid: group.Gid,
|
|
||||||
RequestedAt: this.toMinuteIso(),
|
|
||||||
});
|
|
||||||
return { status: 'pending', message: 'Admin approval pending' };
|
|
||||||
}
|
|
||||||
|
|
||||||
group.ApprovedAdmins.push(user.Uid);
|
|
||||||
this.ensureMembership(user.Uid, group.Gid, 0, true);
|
|
||||||
user.Gid = group.Gid;
|
|
||||||
user.SGid = 0;
|
|
||||||
user.IsAcceptedAdmin = true;
|
|
||||||
|
|
||||||
return { status: 'approved', group };
|
|
||||||
}
|
|
||||||
|
|
||||||
getCreatedPrivateGameByUser(uid) {
|
|
||||||
return this.db.groups.find(
|
|
||||||
(group) => group.CreatedByUid === Number(uid) && group.GameType === 'private',
|
|
||||||
) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
createSubgroup(payload) {
|
|
||||||
const { gid, subgroupName, cacheTriggerMeters = 20 } = payload;
|
|
||||||
const group = this.getGroupById(gid);
|
|
||||||
if (!group) {
|
|
||||||
throw new Error('Group not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingCount = this.db.subgroups.filter((sg) => sg.Gid === gid).length;
|
|
||||||
if (existingCount >= group.MaxMemberSubgroups + 1) {
|
|
||||||
throw new Error('Max subgroups reached');
|
|
||||||
}
|
|
||||||
|
|
||||||
const sgid = existingCount;
|
|
||||||
const newSubgroup = {
|
|
||||||
SGid: sgid,
|
|
||||||
SubGroupName: subgroupName,
|
|
||||||
Gid: gid,
|
|
||||||
JoinCode: this.generateUniqueJoinCode(this.db.subgroups, 'JoinCode'),
|
|
||||||
IsAdminGroup: false,
|
|
||||||
IsGameStarted: false,
|
|
||||||
CacheTriggerMeters: cacheTriggerMeters,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.db.subgroups.push(newSubgroup);
|
|
||||||
return newSubgroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSubgroups(gid) {
|
|
||||||
return this.db.subgroups.filter((sg) => sg.Gid === gid);
|
|
||||||
}
|
|
||||||
|
|
||||||
getVisibleSubgroups(gid) {
|
|
||||||
return this.db.subgroups.filter((sg) => sg.Gid === gid && sg.SGid > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSubgroupCacheSettings(payload) {
|
|
||||||
const { gid, sgid, cacheTriggerMeters } = payload;
|
|
||||||
const subgroup = this.db.subgroups.find((sg) => sg.Gid === gid && sg.SGid === sgid);
|
|
||||||
if (!subgroup) {
|
|
||||||
throw new Error('Subgroup not found');
|
|
||||||
}
|
|
||||||
subgroup.CacheTriggerMeters = cacheTriggerMeters;
|
|
||||||
return subgroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
createTeam(payload) {
|
|
||||||
const { gid, sgid, teamName, leaderId } = payload;
|
|
||||||
const tid = this.nextId('team');
|
|
||||||
const team = {
|
|
||||||
Tid: tid,
|
|
||||||
Gid: gid,
|
|
||||||
SGid: sgid,
|
|
||||||
TeamName: teamName,
|
|
||||||
LeaderId: leaderId,
|
|
||||||
JoinCode: this.generateUniqueJoinCode(this.db.teams, 'JoinCode'),
|
|
||||||
CreatedAt: this.toMinuteIso(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.db.teams.push(team);
|
|
||||||
this.db.team_members.push({ Uid: leaderId, Tid: tid });
|
|
||||||
|
|
||||||
return team;
|
|
||||||
}
|
|
||||||
|
|
||||||
joinTeamByCode(payload) {
|
|
||||||
const { userId, joinCode } = payload;
|
|
||||||
const team = this.db.teams.find((t) => t.JoinCode === joinCode.toUpperCase());
|
|
||||||
|
|
||||||
if (!team) {
|
|
||||||
throw new Error('Team join code not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = this.db.team_members.find((tm) => tm.Uid === userId && tm.Tid === team.Tid);
|
|
||||||
if (existing) {
|
|
||||||
throw new Error('User already in team');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.db.team_members.push({ Uid: userId, Tid: team.Tid });
|
|
||||||
return team;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLobby(gid) {
|
|
||||||
const group = this.getGroupById(gid);
|
|
||||||
const subgroups = this.getSubgroups(gid);
|
|
||||||
const teams = this.db.teams.filter((t) => t.Gid === gid);
|
|
||||||
const teamMembers = this.db.team_members;
|
|
||||||
|
|
||||||
return { group, subgroups, teams, teamMembers };
|
|
||||||
}
|
|
||||||
|
|
||||||
getTeam(tid) {
|
|
||||||
return this.db.teams.find((t) => t.Tid === tid);
|
|
||||||
}
|
|
||||||
|
|
||||||
getMapPoints(gid, sgid = null) {
|
|
||||||
const gameData = this.db.game_data.find((entry) => entry.Gid === Number(gid));
|
|
||||||
if (!gameData) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
if (sgid === null || sgid === undefined) {
|
|
||||||
return gameData.GeoCaches;
|
|
||||||
}
|
|
||||||
return gameData.GeoCaches.filter((cache) => cache.SGid === Number(sgid));
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizeCache(cache, gid) {
|
|
||||||
return {
|
|
||||||
id: cache.CacheId ?? cache.id,
|
|
||||||
coordinates: {
|
|
||||||
latitude: Number(cache.Latitude ?? cache.coordinates?.latitude ?? 0),
|
|
||||||
longitude: Number(cache.Longitude ?? cache.coordinates?.longitude ?? 0),
|
|
||||||
},
|
|
||||||
radius: Number(cache.TriggerMeters ?? cache.radius ?? 20),
|
|
||||||
clue: cache.Title ?? cache.clue ?? 'Cache',
|
|
||||||
groupId: Number(gid),
|
|
||||||
subgroupId: Number(cache.SGid ?? cache.subgroupId ?? 1),
|
|
||||||
claimedBy: cache.ClaimedBy ?? null,
|
|
||||||
claimedAt: cache.ClaimedAt ?? null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getCaches(gid, sgid = null) {
|
|
||||||
const gameData = this.db.game_data.find((entry) => entry.Gid === Number(gid));
|
|
||||||
if (!gameData) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const caches = gameData.GeoCaches || [];
|
|
||||||
const filtered = sgid === null || sgid === undefined
|
|
||||||
? caches
|
|
||||||
: caches.filter((cache) => Number(cache.SGid) === Number(sgid));
|
|
||||||
return filtered.map((cache) => this.normalizeCache(cache, gid));
|
|
||||||
}
|
|
||||||
|
|
||||||
upsertCache(payload) {
|
|
||||||
const {
|
|
||||||
gid,
|
|
||||||
cacheId,
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
radius = 20,
|
|
||||||
clue = 'Cache',
|
|
||||||
subgroupId = 1,
|
|
||||||
} = payload;
|
|
||||||
|
|
||||||
const groupId = Number(gid);
|
|
||||||
const gameData = this.ensureGroupGameData(groupId);
|
|
||||||
const id = cacheId || `C${Date.now()}`;
|
|
||||||
const existingIndex = (gameData.GeoCaches || []).findIndex(
|
|
||||||
(cache) => (cache.CacheId ?? cache.id) === id,
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextCache = {
|
|
||||||
CacheId: id,
|
|
||||||
Title: clue,
|
|
||||||
Latitude: Number(latitude),
|
|
||||||
Longitude: Number(longitude),
|
|
||||||
TriggerMeters: Number(radius),
|
|
||||||
SGid: Number(subgroupId),
|
|
||||||
ClaimedBy: existingIndex >= 0 ? gameData.GeoCaches[existingIndex].ClaimedBy ?? null : null,
|
|
||||||
ClaimedAt: existingIndex >= 0 ? gameData.GeoCaches[existingIndex].ClaimedAt ?? null : null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!gameData.GeoCaches) {
|
|
||||||
gameData.GeoCaches = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingIndex >= 0) {
|
|
||||||
gameData.GeoCaches[existingIndex] = nextCache;
|
|
||||||
} else {
|
|
||||||
gameData.GeoCaches.push(nextCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.normalizeCache(nextCache, groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
claimCache(payload) {
|
|
||||||
const { gid, cacheId, uid = null, tid = null } = payload;
|
|
||||||
const gameData = this.db.game_data.find((entry) => entry.Gid === Number(gid));
|
|
||||||
if (!gameData || !gameData.GeoCaches) {
|
|
||||||
throw new Error('Game data not found');
|
|
||||||
}
|
|
||||||
const target = gameData.GeoCaches.find((cache) => (cache.CacheId ?? cache.id) === cacheId);
|
|
||||||
if (!target) {
|
|
||||||
throw new Error('Cache not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
target.ClaimedBy = tid || uid;
|
|
||||||
target.ClaimedAt = this.toMinuteIso();
|
|
||||||
|
|
||||||
return this.normalizeCache(target, gid);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGroupSettings(payload) {
|
|
||||||
const { gid, ...settings } = payload;
|
|
||||||
const group = this.getGroupById(gid);
|
|
||||||
if (!group) {
|
|
||||||
throw new Error('Group not found');
|
|
||||||
}
|
|
||||||
Object.assign(group, settings);
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
getState() {
|
|
||||||
return this.db;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dbController = new DbController();
|
|
||||||
|
|
||||||
export { DbController };
|
|
||||||
export default dbController;
|
|
||||||
+19
-21
@@ -1,24 +1,22 @@
|
|||||||
import usersSeed from '../../DB/users.json';
|
import Constants from 'expo-constants';
|
||||||
import groupsSeed from '../../DB/groups.json';
|
|
||||||
import subgroupsSeed from '../../DB/subgroups.json';
|
|
||||||
import subgroupMembershipsSeed from '../../DB/subgroup-memberships.json';
|
|
||||||
import adminWaitlistSeed from '../../DB/admin-waitlist.json';
|
|
||||||
import teamsSeed from '../../DB/teams.json';
|
|
||||||
import teamMembersSeed from '../../DB/team-members.json';
|
|
||||||
import gameDataSeed from '../../DB/game-data.json';
|
|
||||||
import gameTypesSeed from '../../DB/game-types.json';
|
|
||||||
|
|
||||||
const buildDbLink = () => ({
|
const hostFromExpo = Constants.expoConfig?.hostUri?.split(':')[0] || null;
|
||||||
users: usersSeed,
|
const apiBaseUrl = 'http://localhost:3000'; // Shirish, i need to deploy the database first so... wait until that... sorry buddy...
|
||||||
groups: groupsSeed,
|
// if you want you can run your own db locally i can send you the files for it just ask :(((
|
||||||
subgroups: subgroupsSeed,
|
|
||||||
subgroup_memberships: subgroupMembershipsSeed,
|
|
||||||
admin_waitlist: adminWaitlistSeed,
|
|
||||||
teams: teamsSeed,
|
|
||||||
team_members: teamMembersSeed,
|
|
||||||
game_data: gameDataSeed,
|
|
||||||
game_types: gameTypesSeed,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default buildDbLink;
|
const postJson = async (path, payload) => {
|
||||||
|
const response = await fetch(`${apiBaseUrl}${path}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.message || 'Request failed');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const postDb = async (action, payload = {}) => {
|
||||||
|
return postJson('/db', { action, payload });
|
||||||
|
};
|
||||||
@@ -1,24 +1,7 @@
|
|||||||
import {useState} from 'react';
|
import {useState} from 'react';
|
||||||
import Constants from 'expo-constants';
|
import {postDb} from './dbLink';
|
||||||
import dbController from './DbController';
|
|
||||||
import {clearSession, setSessionUser} from './SessionStore';
|
import {clearSession, setSessionUser} from './SessionStore';
|
||||||
|
|
||||||
const hostFromExpo = Constants.expoConfig?.hostUri?.split(':')[0] || null;
|
|
||||||
const apiBaseUrl = process.env.EXPO_PUBLIC_API_BASE_URL || (hostFromExpo ? `http://${hostFromExpo}:3000` : 'http://localhost:3000');
|
|
||||||
|
|
||||||
const postJson = async (path, payload) => {
|
|
||||||
const response = await fetch(`${apiBaseUrl}${path}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
const result = await response.json();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(result.message || 'Request failed');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useAuthHook = () => {
|
const useAuthHook = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
@@ -32,21 +15,14 @@ const useAuthHook = () => {
|
|||||||
setError('');
|
setError('');
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const user = await postJson('/auth/login', { email: email.trim(), password });
|
const user = await postDb('login', { email: email.trim(), password });
|
||||||
setSessionUser(user);
|
setSessionUser(user);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return user;
|
return user;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
setError(e.message);
|
||||||
const fallbackUser = dbController.login({ email: email.trim(), password });
|
setIsLoading(false);
|
||||||
setSessionUser(fallbackUser);
|
return null;
|
||||||
setIsLoading(false);
|
|
||||||
return fallbackUser;
|
|
||||||
} catch {
|
|
||||||
setError(e.message);
|
|
||||||
setIsLoading(false);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -54,7 +30,7 @@ const useAuthHook = () => {
|
|||||||
setError('');
|
setError('');
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const user = await postJson('/auth/register', {
|
const user = await postDb('register', {
|
||||||
username: username.trim(),
|
username: username.trim(),
|
||||||
email: email.trim().toLowerCase(),
|
email: email.trim().toLowerCase(),
|
||||||
password,
|
password,
|
||||||
@@ -65,22 +41,9 @@ const useAuthHook = () => {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return user;
|
return user;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
setError(e.message);
|
||||||
const fallbackUser = dbController.register({
|
setIsLoading(false);
|
||||||
username: username.trim(),
|
return null;
|
||||||
email: email.trim().toLowerCase(),
|
|
||||||
password,
|
|
||||||
confirmPassword,
|
|
||||||
accountType,
|
|
||||||
});
|
|
||||||
setSessionUser(fallbackUser);
|
|
||||||
setIsLoading(false);
|
|
||||||
return fallbackUser;
|
|
||||||
} catch {
|
|
||||||
setError(e.message);
|
|
||||||
setIsLoading(false);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+16
-38
@@ -1,55 +1,33 @@
|
|||||||
import dbController from './DbController';
|
import {postDb} from './dbLink';
|
||||||
|
|
||||||
const useGameHook = () => {
|
const useGameHook = () => {
|
||||||
const getGameTypes = () => dbController.getGameTypes();
|
const request = async (action, payload = {}) => postDb(action, payload);
|
||||||
|
|
||||||
const getCreatedPrivateGame = (userId) => {
|
const getGameTypes = async () => request('getGameTypes');
|
||||||
return dbController.getCreatedPrivateGameByUser(userId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createPrivateGame = (payload) => {
|
const getCreatedPrivateGame = async (userId) => request('getCreatedPrivateGameByUser', { userId });
|
||||||
return dbController.createPrivateGame(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const joinPrivateGame = (payload) => {
|
const createPrivateGame = async (payload) => request('createPrivateGame', payload);
|
||||||
return dbController.joinPrivateGame(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const joinAsAdmin = (payload) => {
|
const joinPrivateGame = async (payload) => request('joinPrivateGame', payload);
|
||||||
return dbController.joinAsAdmin(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getLobby = (gid) => {
|
const joinAsAdmin = async (payload) => request('joinAsAdmin', payload);
|
||||||
return dbController.getLobby(gid);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTeam = (tid) => {
|
const getLobby = async (gid) => request('getLobby', { gid });
|
||||||
return dbController.getTeam(tid);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createTeam = (payload) => {
|
const getTeam = async (tid) => request('getTeam', { tid });
|
||||||
return dbController.createTeam(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const joinTeamByCode = (payload) => {
|
const createTeam = async (payload) => request('createTeam', payload);
|
||||||
return dbController.joinTeamByCode(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMapPoints = (gid, sgid = null) => {
|
const joinTeamByCode = async (payload) => request('joinTeamByCode', payload);
|
||||||
return dbController.getMapPoints(gid, sgid);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCaches = (gid, sgid = null) => {
|
const getMapPoints = async (gid, sgid = null) => request('getMapPoints', { gid, sgid });
|
||||||
return dbController.getCaches(gid, sgid);
|
|
||||||
};
|
|
||||||
|
|
||||||
const upsertCache = (payload) => {
|
const getCaches = async (gid, sgid = null) => request('getCaches', { gid, sgid });
|
||||||
return dbController.upsertCache(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
const claimCache = (payload) => {
|
const upsertCache = async (payload) => request('upsertCache', payload);
|
||||||
return dbController.claimCache(payload);
|
|
||||||
};
|
const claimCache = async (payload) => request('claimCache', payload);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getGameTypes,
|
getGameTypes,
|
||||||
|
|||||||
Reference in new issue
Block a user