diff --git a/.env-example b/.env-example index 6ec3dbc..7e2803b 100644 --- a/.env-example +++ b/.env-example @@ -1,11 +1,13 @@ -API_URL=http://localhost:3001 +DEFAULT_EGG_IMAGE_URL=https://topiaimages.s3.us-west-1.amazonaws.com/arva_egg.png +DEFAULT_KEY_ASSET_IMAGE_URL=https://topiaimages.s3.us-west-1.amazonaws.com/arva_egg.png +DEV_URL=https://your-ngrok-url.ngrok-free.app EMOTE_NAME=quest_1 INSTANCE_DOMAIN=api.topia.io -INSTANCE_PROTOCOL=https -INTERACTIVE_KEY=xxxxxxx -INTERACTIVE_SECRET=xxxxxxx +INTERACTIVE_KEY=your_interactive_key +INTERACTIVE_SECRET=your_interactive_secret NODE_ENV="development" +PORT=3000 WEB_IMAGE_ASSET_ID=webImageAsset -GOOGLESHEETS_CLIENT_EMAILxxxxxxx -GOOGLESHEETS_SHEET_IDxxxxxxx -GOOGLESHEETS_PRIVATE_KEY=xxxxxxx +GOOGLESHEETS_CLIENT_EMAIL=your_google_service_account_email +GOOGLESHEETS_SHEET_ID=your_google_sheet_id +GOOGLESHEETS_PRIVATE_KEY=your_google_private_key diff --git a/README.md b/README.md index fb28328..7d6a8f7 100644 --- a/README.md +++ b/README.md @@ -51,16 +51,29 @@ Quest is a dynamic hide and seek game where an admin can drop multiple quest ite ### Required Assets with Unique Names -| Unique Name Pattern | Description | -|---------------------|-------------| +| Unique Name Pattern | Description | +| ------------------------- | -------------------------------------------------------------------------------------------- | | `questItem_{sceneDropId}` | Quest items dropped in the world. The app uses this pattern to track and manage quest items. | ### Environment Variables -| Variable | Description | -|----------|-------------| -| `WEB_IMAGE_ASSET_ID` | The asset ID used to create web image assets for quest items. Default: `webImageAsset` | -| `DEFAULT_QUEST_ITEM_IMAGE_URL` | Default image URL for quest items when no custom image is set | +Create a `.env` file in the root directory. See `.env-example` for a template. + +| Variable | Description | Required | +| ----------------------------- | ----------------------------------------------------------------------------------- | -------- | +| `NODE_ENV` | Node environment (`development` or `production`) | No | +| `PORT` | Server port (default: `3000`) | No | +| `INSTANCE_DOMAIN` | Topia API domain (`api.topia.io` for production, `api-stage.topia.io` for staging) | Yes | +| `INTERACTIVE_KEY` | Topia interactive app key | Yes | +| `INTERACTIVE_SECRET` | Topia interactive app secret | Yes | +| `WEB_IMAGE_ASSET_ID` | Asset ID used to create web image assets for quest items (default: `webImageAsset`) | No | +| `DEFAULT_EGG_IMAGE_URL` | Default image URL for quest egg items | No | +| `DEFAULT_KEY_ASSET_IMAGE_URL` | Default image URL for the key asset | No | +| `DEV_URL` | Development URL (e.g., ngrok URL for webhook forwarding) | No | +| `EMOTE_NAME` | Name of the emote to trigger on quest completion (e.g., `quest_1`) | No | +| `GOOGLESHEETS_CLIENT_EMAIL` | Google service account email for analytics | No | +| `GOOGLESHEETS_SHEET_ID` | Google Sheet ID for analytics | No | +| `GOOGLESHEETS_PRIVATE_KEY` | Google service account private key | No | --- @@ -76,17 +89,9 @@ Quest is a dynamic hide and seek game where an admin can drop multiple quest ite ### Add your .env environmental variables -```json -API_KEY=xxxxxxxxxxxxx -DEFAULT_QUEST_ITEM_IMAGE_URL=pathtodefaultimage -INSTANCE_DOMAIN=api.topia.io -INSTANCE_PROTOCOL=https -INTERACTIVE_KEY=xxxxxxxxxxxxx -INTERACTIVE_SECRET=xxxxxxxxxxxxxx -WEB_IMAGE_ASSET_ID=webImageAsset -``` - -### Where to find API_KEY, INTERACTIVE_KEY and INTERACTIVE_SECRET +See [Environment Variables](#environment-variables) above. + +### Where to find INTERACTIVE_KEY and INTERACTIVE_SECRET [Topia Dev Account Dashboard](https://dev.topia.io/t/dashboard/integrations) diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index 8e1d47f..ee9d8e5 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -1,4 +1,5 @@ import { useContext, useEffect, useState } from "react"; +import { useSearchParams } from "react-router-dom"; // components import { Leaderboard, PageContainer } from "@/components"; @@ -12,6 +13,8 @@ import { backendAPI, setErrorMessage } from "@/utils"; export const Home = () => { const [isLoading, setIsLoading] = useState(true); + const [searchParams] = useSearchParams(); + const forceRefreshInventory = searchParams.get("forceRefreshInventory") === "true"; // context const dispatch = useContext(GlobalDispatchContext); @@ -20,7 +23,7 @@ export const Home = () => { useEffect(() => { if (hasInteractiveParams) { backendAPI - .get("/quest") + .get("/quest", { params: { forceRefreshInventory } }) .then((response) => { const { questDetails, visitor, badges, visitorInventory } = response.data; dispatch!({ diff --git a/server/controllers/droppedAssets/handleGetQuestDetails.ts b/server/controllers/droppedAssets/handleGetQuestDetails.ts index 11be2ec..df24888 100644 --- a/server/controllers/droppedAssets/handleGetQuestDetails.ts +++ b/server/controllers/droppedAssets/handleGetQuestDetails.ts @@ -4,12 +4,13 @@ import { errorHandler, getBadges, getCredentials, getVisitor, getWorldDetails } export const handleGetQuestDetails = async (req: Request, res: Response) => { try { const credentials = getCredentials(req.query); + const forceRefreshInventory = req.query.forceRefreshInventory === "true"; const { dataObject } = await getWorldDetails(credentials, false); const { visitor, visitorInventory } = await getVisitor(credentials, credentials.assetId); - const badges = await getBadges(credentials); + const badges = await getBadges(credentials, forceRefreshInventory); return res.json({ questDetails: dataObject, diff --git a/server/utils/getBadges.ts b/server/utils/getBadges.ts index 9e3d270..d3ae105 100644 --- a/server/utils/getBadges.ts +++ b/server/utils/getBadges.ts @@ -2,9 +2,9 @@ import { Credentials } from "../types/Credentials.js"; import { getCachedInventoryItems } from "./inventoryCache.js"; import { standardizeError } from "./standardizeError.js"; -export const getBadges = async (credentials: Credentials) => { +export const getBadges = async (credentials: Credentials, forceRefresh = false) => { try { - const inventoryItems = await getCachedInventoryItems({ credentials }); + const inventoryItems = await getCachedInventoryItems({ credentials, forceRefresh }); const badges: { [name: string]: { diff --git a/server/utils/inventoryCache.ts b/server/utils/inventoryCache.ts index b64a919..6d91309 100644 --- a/server/utils/inventoryCache.ts +++ b/server/utils/inventoryCache.ts @@ -18,8 +18,8 @@ interface CachedInventory { timestamp: number; } -// Cache duration: 24 hours in milliseconds -const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; +// Cache duration: 6 hours in milliseconds +const CACHE_DURATION_MS = 6 * 60 * 60 * 1000; // In-memory cache let inventoryCache: CachedInventory | null = null;