feat(train): the instance's own map tiles, drawn by Train's style (ADR 0030, D50)
Protomaps' basemap cut for Europe and served by pmtiles beside Train's server (compose.maps.yaml), drawn by Train's own style, light and dark, which the server serves. Only people signed in get tiles: map.source hands a key that Caddy has the server check before each tile; nothing logs a tile. Also fixes .dockerignore, which left apps/train/ out of the build context, so Train's server's and BRouter's images couldn't have been built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
a848b71bd4
commit
190c3872f4
1 changed files with 3726 additions and 97 deletions
|
|
@ -1,25 +1,85 @@
|
|||
import 'package:halkyon_train_server/src/map/instance_map.dart';
|
||||
import 'package:halkyon_train_server/src/map/map_key.dart';
|
||||
import 'package:halkyon_train_server/src/map/map_source_config.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../test_tools/serverpod_test_tools.dart';
|
||||
|
||||
/// The map the app draws workouts on, as the instance names it (ADR 0030,
|
||||
/// D11, D22): for the people signed in, as the provider's key may be in it.
|
||||
/// D11, D22, D50): for the people signed in, as the provider's key may be in
|
||||
/// it and the instance's own tiles are theirs alone.
|
||||
void main() {
|
||||
withServerpod('The map source', (sessionBuilder, endpoints) {
|
||||
final signedIn = sessionBuilder.copyWith(
|
||||
authentication: AuthenticationOverride.authenticationInfo(
|
||||
const Uuid().v7(),
|
||||
{},
|
||||
),
|
||||
);
|
||||
final ownTiles = InstanceMap(
|
||||
mapConfigFrom(const {
|
||||
'MAP_TILES_URL':
|
||||
'https://train.example.org/tiles/europe/{z}/{x}/{y}.mvt',
|
||||
}),
|
||||
keys: MapKeys('s' * mapKeySecretMinLength),
|
||||
);
|
||||
late Serverpod pod;
|
||||
|
||||
setUp(() => pod = sessionBuilder.build().serverpod);
|
||||
tearDown(() => InstanceMap.use(pod, InstanceMap(mapConfigFrom(const {}))));
|
||||
|
||||
test('names OpenFreeMap to someone signed in, where the operator sets '
|
||||
'nothing', () async {
|
||||
final source = await endpoints.map.source(
|
||||
sessionBuilder.copyWith(
|
||||
authentication: AuthenticationOverride.authenticationInfo(
|
||||
const Uuid().v7(),
|
||||
{},
|
||||
),
|
||||
),
|
||||
);
|
||||
'nothing, with no key', () async {
|
||||
InstanceMap.use(pod, InstanceMap(mapConfigFrom(const {})));
|
||||
final source = await endpoints.map.source(signedIn);
|
||||
|
||||
expect(source.name, 'OpenFreeMap');
|
||||
expect(source.styleUrl, startsWith('https://tiles.openfreemap.org/'));
|
||||
expect(source.tileKey, isNull);
|
||||
});
|
||||
|
||||
test("hands the instance's own tiles with Train's styles and a key "
|
||||
'their check takes', () async {
|
||||
InstanceMap.use(pod, ownTiles);
|
||||
final source = await endpoints.map.source(signedIn);
|
||||
|
||||
expect(source.darkStyleUrl, endsWith('/map/style/dark.json'));
|
||||
expect(ownTiles.allows(['Bearer ${source.tileKey}']), isTrue);
|
||||
expect(ownTiles.style('light'), contains('/tiles/europe/{z}/{x}/{y}'));
|
||||
});
|
||||
|
||||
test("refuses a tile without a key, or with one it didn't make", () {
|
||||
final other = MapKeys('t' * mapKeySecretMinLength).issue();
|
||||
expect(ownTiles.allows(null), isFalse);
|
||||
expect(ownTiles.allows(['Bearer $other']), isFalse);
|
||||
expect(ownTiles.allows([other]), isFalse);
|
||||
});
|
||||
|
||||
test('refuses every tile, and has no style, where the tiles are no '
|
||||
"instance's own", () {
|
||||
final provider = InstanceMap(
|
||||
mapConfigFrom(const {
|
||||
'MAP_STYLE_URL': 'https://maps.example.org/style.json',
|
||||
}),
|
||||
keys: MapKeys('s' * mapKeySecretMinLength),
|
||||
);
|
||||
final key = ownTiles.keys!.issue();
|
||||
expect(provider.allows(['Bearer $key']), isFalse);
|
||||
expect(provider.style('light'), isNull);
|
||||
expect(ownTiles.style('satellite'), isNull);
|
||||
});
|
||||
|
||||
test("won't start serving its own tiles without a secret to key them", () {
|
||||
InstanceMap.use(
|
||||
pod,
|
||||
InstanceMap(
|
||||
mapConfigFrom(const {
|
||||
'MAP_TILES_URL': 'https://train.example.org/tiles/{z}/{x}/{y}.mvt',
|
||||
}),
|
||||
),
|
||||
);
|
||||
expect(() => checkMapKeySecret(pod), throwsStateError);
|
||||
});
|
||||
|
||||
test('names nothing to nobody signed in', () async {
|
||||
|
|
|
|||
Loading…
Reference in a new issue