Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ flutter build ios

The app uses a Cloudflare Worker proxy to access Cambridge Beer Festival data:

- Base URL: `https://cbf-data-proxy.richard-alcock.workers.dev`
- Base URL: `https://data.cambeerfestival.app`
- Endpoints: `/{festivalId}/{category}.json` (e.g., `/cbf2025/beer.json`)
- Categories: `beer`, `cider`, `perry`, `mead`, `wine`, `international-beer`, `low-no`

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ Tests go in `test/` mirroring `lib/` structure:

## API Details

**Base URL**: `https://cbf-data-proxy.richard-alcock.workers.dev`
**Base URL**: `https://data.cambeerfestival.app`

**Endpoints**:
- `/{festivalId}/beer.json` - Beers
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ Coverage fails if it drops below 70% overall, helping maintain code quality.
## Data API

This app uses the Cambridge Beer Festival data API via a Cloudflare Worker proxy:
- Base URL: `https://cbf-data-proxy.richard-alcock.workers.dev`
- Example: `https://cbf-data-proxy.richard-alcock.workers.dev/cbf2025/beer.json`
- Base URL: `https://data.cambeerfestival.app`
- Example: `https://data.cambeerfestival.app/cbf2025/beer.json`

### Documentation

Expand Down
2 changes: 1 addition & 1 deletion cloudflare-worker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ curl https://cbf-data-proxy.<your-subdomain>.workers.dev/festivals.json
Once deployed, update `lib/models/festival.dart` to use the proxy URL:

```dart
dataBaseUrl: 'https://cbf-data-proxy.<your-subdomain>.workers.dev/cbf2025',
dataBaseUrl: 'https://data.cambeerfestival.app/cbf2025',
```

Or configure it dynamically based on the platform (see `lib/services/beer_api_service.dart`).
6 changes: 3 additions & 3 deletions data/festivals.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"apple-juice",
"low-no"
],
"data_base_url": "https://cbf-data-proxy.richard-alcock.workers.dev/cbf2025",
"data_base_url": "/cbf2025",
"is_active": true
},
{
Expand All @@ -55,7 +55,7 @@
"beer",
"low-no"
],
"data_base_url": "https://cbf-data-proxy.richard-alcock.workers.dev/cbfw2025",
"data_base_url": "/cbfw2025",
"is_active": false
},
{
Expand Down Expand Up @@ -87,7 +87,7 @@
"wine",
"low-no"
],
"data_base_url": "https://cbf-data-proxy.richard-alcock.workers.dev/cbf2024",
"data_base_url": "/cbf2024",
"is_active": false
}
],
Expand Down
4 changes: 2 additions & 2 deletions docs/CICD.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ Deploys Cloudflare Worker to production.
4. Copy `festivals.json` to worker directory
5. Deploy using `wrangler` via `cloudflare/wrangler-action@v3`

**Worker URL**: `https://cbf-data-proxy.richard-alcock.workers.dev`
**Worker URL**: `https://data.cambeerfestival.app`

**Key Features:**
- CORS proxy for Cambridge Beer Festival API
Expand Down Expand Up @@ -343,7 +343,7 @@ See [GITHUB_SECRETS.md](GITHUB_SECRETS.md) for setup instructions.
| **Staging** | App CI/CD | Push to `main` | `main.cambeerfestival.pages.dev` | Stable staging environment |
| **PR Preview** | App CI/CD | Pull request | `<branch>.cambeerfestival.pages.dev` | Test PRs before merge |
| **Development** | App CI/CD | Push to `main` | `richardthe3rd.github.io/...` | Alternative dev environment |
| **Worker** | Worker | Push to `main` | `cbf-data-proxy.richard-alcock.workers.dev` | API proxy |
| **Worker** | Worker | Push to `main` | `data.cambeerfestival.app` | API proxy |

---

Expand Down
4 changes: 2 additions & 2 deletions docs/CLOUDFLARE_PAGES_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ The app has three main deployment workflows:
3. GitHub Actions automatically:
- Validates festivals.json against schema
- Deploys worker to production
- Worker is live at `https://cbf-data-proxy.richard-alcock.workers.dev`
- Worker is live at `https://data.cambeerfestival.app`

**On Pull Requests**:
- Validates festivals.json
Expand Down Expand Up @@ -364,7 +364,7 @@ After deployment, verify:
Open browser console on `https://cambeerfestival.app` and run:

```javascript
fetch('https://cbf-data-proxy.richard-alcock.workers.dev/festivals.json')
fetch('https://data.cambeerfestival.app/festivals.json')
.then(r => r.json())
.then(console.log)
```
Expand Down
12 changes: 6 additions & 6 deletions docs/NETWORK_ALLOWLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This document lists all the network domains that need to be allowed for GitHub C
| `pub.dev` | Dart packages | `flutter pub get` |
| `www.gstatic.com` | Flutter CanvasKit engine | Running web app |
| `fonts.gstatic.com` | Google Fonts (Roboto) | Running web app |
| `cbf-data-proxy.richard-alcock.workers.dev` | Festival API (via Cloudflare Worker) | App runtime data |
| `data.cambeerfestival.app` | Festival API (via Cloudflare Worker) | App runtime data |
| `data.cambridgebeerfestival.com` | Upstream festival data | App runtime data (proxied) |

## Detailed Breakdown
Expand Down Expand Up @@ -68,14 +68,14 @@ Failed to load font Roboto at https://fonts.gstatic.com/s/roboto/v20/...

### 5. Festival Data API

**Domain:** `cbf-data-proxy.richard-alcock.workers.dev`
**Domain:** `data.cambeerfestival.app`

The app's Cloudflare Worker proxy that serves festival data with CORS headers enabled for web access.

```
https://cbf-data-proxy.richard-alcock.workers.dev/festivals.json
https://cbf-data-proxy.richard-alcock.workers.dev/cbf2025/beer.json
https://cbf-data-proxy.richard-alcock.workers.dev/cbf2025/cider.json
https://data.cambeerfestival.app/festivals.json
https://data.cambeerfestival.app/cbf2025/beer.json
https://data.cambeerfestival.app/cbf2025/cider.json
# ... other beverage types
```

Expand Down Expand Up @@ -123,4 +123,4 @@ For **running the web app and taking screenshots**:

For **full app functionality with live data**:

5. `cbf-data-proxy.richard-alcock.workers.dev` - Festival API
5. `data.cambeerfestival.app` - Festival API
2 changes: 1 addition & 1 deletion docs/PLAY_STORE_METADATA.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ The Cambridge Beer Festival app is designed with privacy in mind. We do not coll

- The app fetches public festival data from our Cloudflare Worker API
- These requests contain no personal information
- API endpoint: `https://cbf-data-proxy.richard-alcock.workers.dev`
- API endpoint: `https://data.cambeerfestival.app`

## Permissions

Expand Down
14 changes: 7 additions & 7 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,27 @@ node validate-festivals.js

The Cambridge Beer Festival app fetches beverage data from a public API:

- **Base URL:** `https://data.cambridgebeerfestival.com`
- **Base URL:** `https://data.cambeerfestival.app`
- **Format:** JSON
- **Authentication:** None required (public API)

### URL Pattern

```
https://data.cambridgebeerfestival.com/{festival_id}/{beverage_type}.json
https://data.cambeerfestival.app/{festival_id}/{beverage_type}.json
```

### Examples

```bash
# Beer data for CBF 2025
curl https://data.cambridgebeerfestival.com/cbf2025/beer.json
curl https://data.cambeerfestival.app/cbf2025/beer.json

# Cider data for CBF 2025
curl https://data.cambridgebeerfestival.com/cbf2025/cider.json
curl https://data.cambeerfestival.app/cbf2025/cider.json

# Winter festival beer data
curl https://data.cambridgebeerfestival.com/cbfw2025/beer.json
curl https://data.cambeerfestival.app/cbfw2025/beer.json
```

## Schemas
Expand Down Expand Up @@ -93,7 +93,7 @@ const ajv = new Ajv();
addFormats(ajv);
const validate = ajv.compile(schema);

const data = await fetch('https://data.cambridgebeerfestival.com/cbf2025/beer.json')
const data = await fetch('https://data.cambeerfestival.app/cbf2025/beer.json')
.then(r => r.json());

if (validate(data)) {
Expand All @@ -113,7 +113,7 @@ import requests
with open('beer-list-schema.json') as f:
schema = json.load(f)

data = requests.get('https://data.cambridgebeerfestival.com/cbf2025/beer.json').json()
data = requests.get('https://data.cambeerfestival.app/cbf2025/beer.json').json()

jsonschema.validate(data, schema) # Raises on error
print('Valid!')
Expand Down
2 changes: 1 addition & 1 deletion docs/api/beer-list-schema.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://data.cambridgebeerfestival.com/beer-list-schema.json",
"$id": "https://data.cambeerfestival.app/beer-list-schema.json",
"title": "Beer Festival Data API Schema",
"description": "JSON Schema for Cambridge Beer Festival beverage data API responses",
"type": "object",
Expand Down
16 changes: 8 additions & 8 deletions docs/api/data-api-reference.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Cambridge Beer Festival Data API Reference

**Last Updated:** 2025-11-29
**API Base URL:** https://data.cambridgebeerfestival.com
**API Base URL:** https://data.cambeerfestival.app
**Purpose:** Documentation for multi-festival and multi-beverage-type support

---
Expand All @@ -27,14 +27,14 @@ The Cambridge Beer Festival Data API provides structured beverage and producer d

### Pattern
```
https://data.cambridgebeerfestival.com/{festival_code}/{beverage_type}.json
https://data.cambeerfestival.app/{festival_code}/{beverage_type}.json
```

### Examples
```
https://data.cambridgebeerfestival.com/cbf2025/beer.json
https://data.cambridgebeerfestival.com/cbf2025/cider.json
https://data.cambridgebeerfestival.com/cbfw2025/beer.json
https://data.cambeerfestival.app/cbf2025/beer.json
https://data.cambeerfestival.app/cbf2025/cider.json
https://data.cambeerfestival.app/cbfw2025/beer.json
```

---
Expand Down Expand Up @@ -223,19 +223,19 @@ Common allergens:
### Fetch Beer Data for CBF 2025

```bash
curl https://data.cambridgebeerfestival.com/cbf2025/beer.json
curl https://data.cambeerfestival.app/cbf2025/beer.json
```

### Fetch Cider Data for CBF 2025

```bash
curl https://data.cambridgebeerfestival.com/cbf2025/cider.json
curl https://data.cambeerfestival.app/cbf2025/cider.json
```

### Fetch Winter Festival Beer Data

```bash
curl https://data.cambridgebeerfestival.com/cbfw2025/beer.json
curl https://data.cambeerfestival.app/cbfw2025/beer.json
```

---
Expand Down
7 changes: 3 additions & 4 deletions docs/api/festival-registry-schema.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://data.cambridgebeerfestival.com/festivals-schema.json",
"$id": "https://data.cambeerfestival.app/festivals-schema.json",
"title": "Festivals Configuration Schema",
"description": "JSON Schema for Cambridge Beer Festival app configuration (data/festivals.json)",
"type": "object",
Expand Down Expand Up @@ -124,9 +124,8 @@
},
"data_base_url": {
"type": "string",
"description": "Base URL for fetching beverage data",
"format": "uri",
"examples": ["https://cbf-data-proxy.richard-alcock.workers.dev/cbf2025"]
"description": "Base URL for fetching beverage data. Can be absolute (https://...) or relative (/festivalId). Relative URLs are resolved against the base URL where festivals.json was fetched from.",
"examples": ["/cbf2025", "https://data.cambeerfestival.app/cbf2025"]
},
"is_active": {
"type": "boolean",
Expand Down
6 changes: 3 additions & 3 deletions lib/models/festival.dart
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ class DefaultFestivals {
'wine',
'low-no',
],
dataBaseUrl: 'https://cbf-data-proxy.richard-alcock.workers.dev/cbf2025',
dataBaseUrl: 'https://data.cambeerfestival.app/cbf2025',
isActive: true,
);

Expand All @@ -276,7 +276,7 @@ class DefaultFestivals {
endDate: DateTime(2025, 12, 13),
location: 'Cambridge Corn Exchange, Cambridge',
availableBeverageTypes: ['beer', 'international-beer', 'cider', 'perry', 'low-no'],
dataBaseUrl: 'https://cbf-data-proxy.richard-alcock.workers.dev/cbfw2025',
dataBaseUrl: 'https://data.cambeerfestival.app/cbfw2025',
isActive: false,
);

Expand All @@ -298,7 +298,7 @@ class DefaultFestivals {
'wine',
'low-no',
],
dataBaseUrl: 'https://cbf-data-proxy.richard-alcock.workers.dev/cbf2024',
dataBaseUrl: 'https://data.cambeerfestival.app/cbf2024',
isActive: false,
);

Expand Down
23 changes: 19 additions & 4 deletions lib/services/festival_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,29 @@ class FestivalsResponse {
final String defaultFestivalId;
final String version;
final DateTime? lastUpdated;
final String baseUrl;

FestivalsResponse({
required this.festivals,
required this.defaultFestivalId,
required this.version,
this.lastUpdated,
required this.baseUrl,
});

factory FestivalsResponse.fromJson(Map<String, dynamic> json) {
factory FestivalsResponse.fromJson(Map<String, dynamic> json, String baseUrl) {
final festivalsList = (json['festivals'] as List<dynamic>)
.map((f) => Festival.fromJson(f as Map<String, dynamic>))
.map((f) {
final festivalJson = Map<String, dynamic>.from(f as Map<String, dynamic>);
// Resolve relative URLs to absolute URLs
if (festivalJson['data_base_url'] != null) {
final dataBaseUrl = festivalJson['data_base_url'] as String;
if (dataBaseUrl.startsWith('/')) {
festivalJson['data_base_url'] = baseUrl + dataBaseUrl;
}
}
return Festival.fromJson(festivalJson);
})
.toList();

return FestivalsResponse(
Expand All @@ -28,6 +40,7 @@ class FestivalsResponse {
lastUpdated: json['last_updated'] != null
? DateTime.tryParse(json['last_updated'] as String)
: null,
baseUrl: baseUrl,
);
}

Expand All @@ -47,7 +60,7 @@ class FestivalsResponse {
/// Service for fetching festival metadata
class FestivalService {
static const String _festivalsUrl =
'https://cbf-data-proxy.richard-alcock.workers.dev/festivals.json';
'https://data.cambeerfestival.app/festivals.json';

final http.Client _client;
final Duration timeout;
Expand All @@ -64,7 +77,9 @@ class FestivalService {

if (response.statusCode == 200) {
final data = json.decode(response.body) as Map<String, dynamic>;
return FestivalsResponse.fromJson(data);
// Extract base URL from the festivals URL (remove /festivals.json)
final baseUrl = _festivalsUrl.replaceAll(RegExp(r'/festivals\.json$'), '');
return FestivalsResponse.fromJson(data, baseUrl);
} else {
throw FestivalServiceException(
'Failed to fetch festivals: ${response.statusCode}',
Expand Down
2 changes: 1 addition & 1 deletion test-e2e/screenshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const VIEWPORT = {
};

// Cambridge Beer Festival API
const API_BASE_URL = 'https://cbf-data-proxy.richard-alcock.workers.dev';
const API_BASE_URL = 'https://data.cambeerfestival.app';
const FESTIVAL_ID = process.env.FESTIVAL_ID || 'cbf2025'; // Default to current festival
const API_TIMEOUT_MS = 10000; // 10 second timeout for API calls

Expand Down
Loading
Loading