Skip to content

Commit b5ebece

Browse files
committed
feat: Implement dynamic resort status API with (renegade) Epic Mix integration and refactor ResortList to display statuses for multiple resorts.
1 parent 45d9548 commit b5ebece

15 files changed

Lines changed: 881 additions & 149 deletions

File tree

doc/epic_mix_api_calls.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# epic mix API calls (extracted from mobile app)
2+
3+
## get all resorts
4+
`curl -H 'accept: application/json, text/plain, */*' \
5+
-H 'accept-language: en-US,en;q=0.9' \ -H 'user-agent: MyEpic/160000 ios' --compressed \
6+
https://digital-gw.azmtn.com/uiservice/api/v1/resorts | jq .`
7+
8+
9+
## get daily stats for park city
10+
`curl -H 'accept: application/json, text/plain, */*' \
11+
-H 'accept-language: en-US,en;q=0.9' -H 'user-agent: MyEpic/160000 ios' --compressed \
12+
https://digital-gw.azmtn.com/uiservice/api/v1/resorts/14/daily-stats | jq .`
13+
14+
15+
## get basic data for park city
16+
`curl ' -H 'accept: application/json, text/plain, */*' \
17+
-H 'accept-language: en-US,en;q=0.9' -H 'user-agent: MyEpic/160000 ios' --compressed \
18+
https://digital-gw.azmtn.com/uiservice/api/v1/resorts/14 | jq .`
19+
20+
## more details for park city
21+
`curl -H 'accept: application/json, text/plain, */*' -H 'accept-language: en-US,en;q=0.9' \
22+
-H 'user-agent: MyEpic/160000 ios' --compressed \
23+
https://digital-gw.azmtn.com/uiservice/api/v1/resorts/14/home`
24+
25+
## get maps for park city
26+
`curl -H 'cookie: ' -H 'accept: application/json, text/plain, */*' -H 'accept-language: en-US,en;q=0.9' -H 'user-agent: MyEpic/160000 ios' --compressed https://digital-gw.azmtn.com/uiservice/api/v1/resorts/14/maps`
27+
28+
## get wait times for park city
29+
`curl -H 'accept: application/json, text/plain, */*' -H 'accept-language: en-US,en;q=0.9' -H 'user-agent: MyEpic/160000 ios' --compressed https://digital-gw.azmtn.com/uiservice/api/v1/resorts/14/wait-times`
30+
31+
## get weather for park city
32+
`curl -H 'accept: application/json, text/plain, */*' -H 'accept-language: en-US,en;q=0.9' -H 'user-agent: MyEpic/160000 ios' --compressed https://digital-gw.azmtn.com/uiservice/api/v1/resorts/14/weather`
33+
34+
## get webcams for park city
35+
`curl -H 'accept: application/json, text/plain, */*' -H 'accept-language: en-US,en;q=0.9' -H 'user-agent: MyEpic/160000 ios' --compressed https://digital-gw.azmtn.com/uiservice/api/v1/resorts/14/webcam-screen`

scripts/generate_resort_map.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
import { exec } from 'child_process';
3+
import { promisify } from 'util';
4+
5+
const execAsync = promisify(exec);
6+
7+
const CURL_COMMAND = `curl -s -H 'accept: application/json, text/plain, */*' \
8+
-H 'accept-language: en-US,en;q=0.9' -H 'user-agent: MyEpic/160000 ios' --compressed \
9+
https://digital-gw.azmtn.com/uiservice/api/v1/resorts`;
10+
11+
interface Resort {
12+
resortId: string;
13+
title: string;
14+
displayName: string;
15+
// ... other fields we don't care about
16+
}
17+
18+
async function main() {
19+
try {
20+
const { stdout, stderr } = await execAsync(CURL_COMMAND);
21+
22+
if (stderr) {
23+
// curl might output progress to stderr, but -s should silence it.
24+
// If there's content it might be errors or stats. simpler to ignore unless parsing fails.
25+
}
26+
27+
const resorts: Resort[] = JSON.parse(stdout);
28+
29+
const resortMap: Record<string, string> = {};
30+
31+
resorts.forEach(resort => {
32+
// Prefer displayName, fallback to title
33+
resortMap[resort.resortId] = resort.displayName || resort.title;
34+
});
35+
36+
console.log(JSON.stringify(resortMap, null, 2));
37+
38+
} catch (error) {
39+
console.error('Error fetching or parsing resort data:', error);
40+
process.exit(1);
41+
}
42+
}
43+
44+
main();

scripts/resort_ids.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"2": "Afton Alps",
3+
"3": "Beaver Creek",
4+
"4": "Breckenridge",
5+
"5": "Crested Butte",
6+
"6": "Heavenly",
7+
"8": "Keystone",
8+
"9": "Kirkwood",
9+
"11": "Mount Sunapee",
10+
"12": "Northstar",
11+
"13": "Okemo",
12+
"14": "Park City",
13+
"15": "Vail",
14+
"17": "Stevens Pass",
15+
"18": "Stowe",
16+
"19": "Whistler Blackcomb",
17+
"20": "Hunter",
18+
"21": "Mount Snow",
19+
"22": "Attitash Mountain",
20+
"23": "Wildcat Mountain",
21+
"24": "Crotched Mountain",
22+
"25": "Liberty Mountain",
23+
"26": "Roundtop Mountain",
24+
"27": "Whitetail",
25+
"28": "Jack Frost Big Boulder",
26+
"29": "Alpine Valley",
27+
"30": "Boston Mills Brandywine",
28+
"31": "Mad River Mountain",
29+
"32": "Hidden Valley",
30+
"33": "Snow Creek",
31+
"34": "Paoli Peaks",
32+
"35": "Wilmot ",
33+
"36": "Mt. Brighton",
34+
"39": "Seven Springs",
35+
"40": "Hidden Valley (PA)",
36+
"41": "Laurel Mountain"
37+
}

scripts/test_epic_api.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
import { exec } from 'child_process';
3+
import { promisify } from 'util';
4+
5+
const execAsync = promisify(exec);
6+
7+
const BASE_URL = 'https://digital-gw.azmtn.com/uiservice/api/v1/resorts/14';
8+
const HEADERS = `-H 'accept: application/json, text/plain, */*' \
9+
-H 'accept-language: en-US,en;q=0.9' -H 'user-agent: MyEpic/160000 ios' --compressed`;
10+
11+
const ENDPOINTS = [
12+
{ name: 'daily-stats', url: `${BASE_URL}/daily-stats` },
13+
{ name: 'basic', url: `${BASE_URL}` }, // resorts/14
14+
{ name: 'home', url: `${BASE_URL}/home` },
15+
{ name: 'maps', url: `${BASE_URL}/maps` },
16+
{ name: 'wait-times', url: `${BASE_URL}/wait-times` },
17+
{ name: 'weather', url: `${BASE_URL}/weather` },
18+
{ name: 'webcams', url: `${BASE_URL}/webcam-screen` }, // Note: webcam-screen
19+
{ name: 'terrain', url: `${BASE_URL}/terrain-status` }, // Guessing based on pattern, but let's stick to user list + this guess
20+
{ name: 'lifts', url: `${BASE_URL}/lift-status` }, // Guessing based on common patterns
21+
];
22+
23+
async function main() {
24+
for (const ep of ENDPOINTS) {
25+
console.log(`Fetching ${ep.name}...`);
26+
try {
27+
const cmd = `curl -s ${HEADERS} '${ep.url}'`;
28+
const { stdout } = await execAsync(cmd);
29+
console.log(`--- ${ep.name} ---`);
30+
// truncate output for display
31+
console.log(stdout.substring(0, 500) + '...');
32+
33+
// Try parsing to ensure JSON
34+
JSON.parse(stdout);
35+
console.log(`(Valid JSON)`);
36+
} catch (e: any) {
37+
console.error(`Error fetching ${ep.name}:`, e.message);
38+
}
39+
}
40+
}
41+
42+
main();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
import { NextResponse } from 'next/server';
3+
import { getResortStatus } from '@/services/resort-status';
4+
import { logger } from '@/utils/logger';
5+
6+
export const dynamic = 'force-dynamic';
7+
8+
export async function GET(request: Request, props: { params: Promise<{ resortId: string }> }) {
9+
const params = await props.params;
10+
const { resortId } = params;
11+
12+
try {
13+
const status = await getResortStatus(resortId);
14+
return NextResponse.json(status);
15+
} catch (error) {
16+
logger.error('API: Internal Error', { resortId, error });
17+
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
18+
}
19+
}

src/app/api/resort-status/park-city/route.ts

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)