Skip to content

Commit edbbbf4

Browse files
Use relative URLs in festivals.json for portability
Co-authored-by: richardthe3rd <573334+richardthe3rd@users.noreply.github.com>
1 parent 161f355 commit edbbbf4

7 files changed

Lines changed: 68 additions & 16 deletions

File tree

data/festivals.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"apple-juice",
3131
"low-no"
3232
],
33-
"data_base_url": "https://data.cambeerfestival.app/cbf2025",
33+
"data_base_url": "/cbf2025",
3434
"is_active": true
3535
},
3636
{
@@ -55,7 +55,7 @@
5555
"beer",
5656
"low-no"
5757
],
58-
"data_base_url": "https://data.cambeerfestival.app/cbfw2025",
58+
"data_base_url": "/cbfw2025",
5959
"is_active": false
6060
},
6161
{
@@ -87,7 +87,7 @@
8787
"wine",
8888
"low-no"
8989
],
90-
"data_base_url": "https://data.cambeerfestival.app/cbf2024",
90+
"data_base_url": "/cbf2024",
9191
"is_active": false
9292
}
9393
],

docs/api/festival-registry-schema.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,8 @@
124124
},
125125
"data_base_url": {
126126
"type": "string",
127-
"description": "Base URL for fetching beverage data",
128-
"format": "uri",
129-
"examples": ["https://data.cambeerfestival.app/cbf2025"]
127+
"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.",
128+
"examples": ["/cbf2025", "https://data.cambeerfestival.app/cbf2025"]
130129
},
131130
"is_active": {
132131
"type": "boolean",

lib/services/festival_service.dart

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,29 @@ class FestivalsResponse {
88
final String defaultFestivalId;
99
final String version;
1010
final DateTime? lastUpdated;
11+
final String baseUrl;
1112

1213
FestivalsResponse({
1314
required this.festivals,
1415
required this.defaultFestivalId,
1516
required this.version,
1617
this.lastUpdated,
18+
required this.baseUrl,
1719
});
1820

19-
factory FestivalsResponse.fromJson(Map<String, dynamic> json) {
21+
factory FestivalsResponse.fromJson(Map<String, dynamic> json, String baseUrl) {
2022
final festivalsList = (json['festivals'] as List<dynamic>)
21-
.map((f) => Festival.fromJson(f as Map<String, dynamic>))
23+
.map((f) {
24+
final festivalJson = f as Map<String, dynamic>;
25+
// Resolve relative URLs to absolute URLs
26+
if (festivalJson['data_base_url'] != null) {
27+
final dataBaseUrl = festivalJson['data_base_url'] as String;
28+
if (dataBaseUrl.startsWith('/')) {
29+
festivalJson['data_base_url'] = baseUrl + dataBaseUrl;
30+
}
31+
}
32+
return Festival.fromJson(festivalJson);
33+
})
2234
.toList();
2335

2436
return FestivalsResponse(
@@ -28,6 +40,7 @@ class FestivalsResponse {
2840
lastUpdated: json['last_updated'] != null
2941
? DateTime.tryParse(json['last_updated'] as String)
3042
: null,
43+
baseUrl: baseUrl,
3144
);
3245
}
3346

@@ -64,7 +77,9 @@ class FestivalService {
6477

6578
if (response.statusCode == 200) {
6679
final data = json.decode(response.body) as Map<String, dynamic>;
67-
return FestivalsResponse.fromJson(data);
80+
// Extract base URL from the festivals URL (remove /festivals.json)
81+
final baseUrl = _festivalsUrl.replaceAll(RegExp(r'/festivals\.json$'), '');
82+
return FestivalsResponse.fromJson(data, baseUrl);
6883
} else {
6984
throw FestivalServiceException(
7085
'Failed to fetch festivals: ${response.statusCode}',

test/beer_provider_test.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,7 @@ void main() {
894894
),
895895
],
896896
defaultFestivalId: 'test',
897+
baseUrl: 'https://example.com',
897898
version: '1.0.0',
898899
),
899900
);
@@ -968,6 +969,7 @@ void main() {
968969
),
969970
],
970971
defaultFestivalId: 'cbf2025',
972+
baseUrl: 'https://example.com',
971973
version: '1.0.0',
972974
),
973975
);
@@ -1013,6 +1015,7 @@ void main() {
10131015
),
10141016
],
10151017
defaultFestivalId: 'cbf2025',
1018+
baseUrl: 'https://example.com',
10161019
version: '1.0.0',
10171020
),
10181021
);
@@ -1045,6 +1048,7 @@ void main() {
10451048
),
10461049
],
10471050
defaultFestivalId: 'cbf2025',
1051+
baseUrl: 'https://example.com',
10481052
version: '1.0.0',
10491053
),
10501054
);
@@ -1071,6 +1075,7 @@ void main() {
10711075
),
10721076
],
10731077
defaultFestivalId: 'cbf2025',
1078+
baseUrl: 'https://example.com',
10741079
version: '1.0.0',
10751080
),
10761081
);
@@ -1136,6 +1141,7 @@ void main() {
11361141
),
11371142
],
11381143
defaultFestivalId: 'cbf2025',
1144+
baseUrl: 'https://example.com',
11391145
version: '1.0.0',
11401146
),
11411147
);
@@ -1162,6 +1168,7 @@ void main() {
11621168
),
11631169
],
11641170
defaultFestivalId: 'cbf2025',
1171+
baseUrl: 'https://example.com',
11651172
version: '1.0.0',
11661173
),
11671174
);
@@ -1193,6 +1200,7 @@ void main() {
11931200
),
11941201
],
11951202
defaultFestivalId: 'cbf2025',
1203+
baseUrl: 'https://example.com',
11961204
version: '1.0.0',
11971205
),
11981206
);
@@ -1227,6 +1235,7 @@ void main() {
12271235
),
12281236
],
12291237
defaultFestivalId: 'cbf2025',
1238+
baseUrl: 'https://example.com',
12301239
version: '1.0.0',
12311240
),
12321241
);
@@ -1273,6 +1282,7 @@ void main() {
12731282
),
12741283
],
12751284
defaultFestivalId: 'cbf2025',
1285+
baseUrl: 'https://example.com',
12761286
version: '1.0.0',
12771287
),
12781288
);

test/brewery_screen_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ void main() {
6060
final festivalsResponse = FestivalsResponse(
6161
festivals: [testFestival],
6262
defaultFestivalId: 'cbf2025',
63+
baseUrl: 'https://example.com',
6364
version: '1.0.0',
6465
);
6566
when(mockFestivalService.fetchFestivals())

test/main_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ void main() {
4141
],
4242
defaultFestivalId: 'cbf2025',
4343
version: '1.0.0',
44+
baseUrl: 'https://example.com',
4445
),
4546
);
4647

test/services_test.dart

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,38 @@ void main() {
7676
'last_updated': '2025-01-15T12:00:00.000Z',
7777
};
7878

79-
final response = FestivalsResponse.fromJson(json);
79+
final response = FestivalsResponse.fromJson(json, 'https://example.com');
8080

8181
expect(response.festivals.length, 2);
8282
expect(response.defaultFestivalId, 'cbf2025');
8383
expect(response.version, '1.0.0');
8484
expect(response.lastUpdated, isNotNull);
85+
expect(response.baseUrl, 'https://example.com');
86+
});
87+
88+
test('fromJson resolves relative URLs to absolute', () {
89+
final json = {
90+
'festivals': [
91+
{
92+
'id': 'cbf2025',
93+
'name': 'Cambridge Beer Festival 2025',
94+
'data_base_url': '/cbf2025',
95+
'is_active': true,
96+
},
97+
{
98+
'id': 'cbfw2025',
99+
'name': 'Cambridge Winter Beer Festival 2025',
100+
'data_base_url': '/cbfw2025',
101+
'is_active': false,
102+
},
103+
],
104+
'default_festival_id': 'cbf2025',
105+
};
106+
107+
final response = FestivalsResponse.fromJson(json, 'https://data.cambeerfestival.app');
108+
109+
expect(response.festivals[0].dataBaseUrl, 'https://data.cambeerfestival.app/cbf2025');
110+
expect(response.festivals[1].dataBaseUrl, 'https://data.cambeerfestival.app/cbfw2025');
85111
});
86112

87113
test('fromJson handles missing optional fields', () {
@@ -96,7 +122,7 @@ void main() {
96122
'default_festival_id': 'test',
97123
};
98124

99-
final response = FestivalsResponse.fromJson(json);
125+
final response = FestivalsResponse.fromJson(json, 'https://example.com');
100126

101127
expect(response.festivals.length, 1);
102128
expect(response.version, '1.0.0');
@@ -120,7 +146,7 @@ void main() {
120146
'default_festival_id': 'cbfw2025',
121147
};
122148

123-
final response = FestivalsResponse.fromJson(json);
149+
final response = FestivalsResponse.fromJson(json, 'https://example.com');
124150

125151
expect(response.defaultFestival, isNotNull);
126152
expect(response.defaultFestival!.id, 'cbfw2025');
@@ -138,7 +164,7 @@ void main() {
138164
'default_festival_id': 'nonexistent',
139165
};
140166

141-
final response = FestivalsResponse.fromJson(json);
167+
final response = FestivalsResponse.fromJson(json, 'https://example.com');
142168

143169
expect(response.defaultFestival, isNotNull);
144170
expect(response.defaultFestival!.id, 'cbf2025');
@@ -150,7 +176,7 @@ void main() {
150176
'default_festival_id': 'cbf2025',
151177
};
152178

153-
final response = FestivalsResponse.fromJson(json);
179+
final response = FestivalsResponse.fromJson(json, 'https://example.com');
154180

155181
expect(response.defaultFestival, isNull);
156182
});
@@ -180,7 +206,7 @@ void main() {
180206
'default_festival_id': 'cbf2025',
181207
};
182208

183-
final response = FestivalsResponse.fromJson(json);
209+
final response = FestivalsResponse.fromJson(json, 'https://example.com');
184210
final activeFestivals = response.activeFestivals;
185211

186212
expect(activeFestivals.length, 2);
@@ -202,7 +228,7 @@ void main() {
202228
'default_festival_id': 'cbf2024',
203229
};
204230

205-
final response = FestivalsResponse.fromJson(json);
231+
final response = FestivalsResponse.fromJson(json, 'https://example.com');
206232

207233
expect(response.activeFestivals, isEmpty);
208234
});

0 commit comments

Comments
 (0)