feat(train): the instance names its map source (ADR 0030, D11, D22)
Train's first endpoint of its own, map.source, names the map workouts are drawn on, for people signed in only, as a provider's key may be in its style's address: OpenFreeMap's Liberty style where the operator sets nothing, else MAP_STYLE_URL (HTTPS only) with MAP_SOURCE_NAME, MAP_ATTRIBUTION and MAP_ABOUT_URL. A model without a table: no migration. D22 is recorded in ADR 0030. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
7df1cb1740
commit
dd778cb2cd
7 changed files with 574 additions and 4 deletions
|
|
@ -21,11 +21,36 @@ import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
|
|||
as _iais;
|
||||
import 'package:serverpod_offline_sync_server/serverpod_offline_sync_server.dart'
|
||||
as _izehhkf5;
|
||||
import '../map/map_endpoint.dart' as _i35zr6r3;
|
||||
export 'future_calls.dart' show ServerpodFutureCallsGetter;
|
||||
|
||||
class Endpoints extends _is.EndpointDispatch {
|
||||
@override
|
||||
void initializeEndpoints(_is.Server server) {
|
||||
var endpoints = <String, _is.Endpoint>{
|
||||
'map': _i35zr6r3.MapEndpoint()
|
||||
..initialize(
|
||||
server,
|
||||
'map',
|
||||
null,
|
||||
),
|
||||
};
|
||||
connectors['map'] = _is.EndpointConnector(
|
||||
name: 'map',
|
||||
endpoint: endpoints['map']!,
|
||||
methodConnectors: {
|
||||
'source': _is.MethodConnector(
|
||||
name: 'source',
|
||||
params: {},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['map'] as _i35zr6r3.MapEndpoint).source(session),
|
||||
),
|
||||
},
|
||||
);
|
||||
modules['halkyon_base'] = _ik8molhd.Endpoints()
|
||||
..initializeEndpoints(server);
|
||||
modules['serverpod_auth_idp'] = _iais.Endpoints()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
/// The map this instance's workouts are drawn on (ADR 0030, D11, D22), as
|
||||
/// its operator sets it: a MapLibre style, its source's name and the credit
|
||||
/// it asks for, and where the source says what it sees. OpenFreeMap where
|
||||
/// nothing is set.
|
||||
abstract class MapSource
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
MapSource._({
|
||||
required this.name,
|
||||
required this.styleUrl,
|
||||
required this.attribution,
|
||||
this.aboutUrl,
|
||||
});
|
||||
|
||||
factory MapSource({
|
||||
required String name,
|
||||
required String styleUrl,
|
||||
required String attribution,
|
||||
String? aboutUrl,
|
||||
}) = _MapSourceImpl;
|
||||
|
||||
factory MapSource.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return MapSource(
|
||||
name: jsonSerialization['name'] as String,
|
||||
styleUrl: jsonSerialization['styleUrl'] as String,
|
||||
attribution: jsonSerialization['attribution'] as String,
|
||||
aboutUrl: jsonSerialization['aboutUrl'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// What the app names as drawing the map, as `OpenFreeMap`.
|
||||
String name;
|
||||
|
||||
/// The MapLibre (Mapbox GL) style the vector tiles are drawn by; a
|
||||
/// provider's key may be in it, so only people signed in get it.
|
||||
String styleUrl;
|
||||
|
||||
/// The credit the map shows, as the source's terms ask.
|
||||
String attribution;
|
||||
|
||||
/// Where the source says what it keeps of those viewing its maps; null
|
||||
/// where the operator names nothing.
|
||||
String? aboutUrl;
|
||||
|
||||
/// Returns a shallow copy of this [MapSource]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
MapSource copyWith({
|
||||
String? name,
|
||||
String? styleUrl,
|
||||
String? attribution,
|
||||
String? aboutUrl,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'MapSource',
|
||||
'name': name,
|
||||
'styleUrl': styleUrl,
|
||||
'attribution': attribution,
|
||||
if (aboutUrl != null) 'aboutUrl': aboutUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'MapSource',
|
||||
'name': name,
|
||||
'styleUrl': styleUrl,
|
||||
'attribution': attribution,
|
||||
if (aboutUrl != null) 'aboutUrl': aboutUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _MapSourceImpl extends MapSource {
|
||||
_MapSourceImpl({
|
||||
required String name,
|
||||
required String styleUrl,
|
||||
required String attribution,
|
||||
String? aboutUrl,
|
||||
}) : super._(
|
||||
name: name,
|
||||
styleUrl: styleUrl,
|
||||
attribution: attribution,
|
||||
aboutUrl: aboutUrl,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [MapSource]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
MapSource copyWith({
|
||||
String? name,
|
||||
String? styleUrl,
|
||||
String? attribution,
|
||||
Object? aboutUrl = _Undefined,
|
||||
}) {
|
||||
return MapSource(
|
||||
name: name ?? this.name,
|
||||
styleUrl: styleUrl ?? this.styleUrl,
|
||||
attribution: attribution ?? this.attribution,
|
||||
aboutUrl: aboutUrl is String? ? aboutUrl : this.aboutUrl,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,9 +21,11 @@ import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
|
|||
as _iais;
|
||||
import 'package:serverpod_offline_sync_server/serverpod_offline_sync_server.dart'
|
||||
as _izehhkf5;
|
||||
import 'map/map_source.dart' as _ikw0qg8x;
|
||||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'workout/workout.dart' as _i5b9b17z;
|
||||
import 'workout/workout_series.dart' as _i92mefto;
|
||||
export 'map/map_source.dart';
|
||||
export 'privacy/content_key.dart';
|
||||
export 'workout/workout.dart';
|
||||
export 'workout/workout_series.dart';
|
||||
|
|
@ -297,6 +299,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
}
|
||||
}
|
||||
|
||||
if (t == _ikw0qg8x.MapSource) {
|
||||
return _ikw0qg8x.MapSource.fromJson(data) as T;
|
||||
}
|
||||
if (t == _imed3x5d.ContentKey) {
|
||||
return _imed3x5d.ContentKey.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -306,6 +311,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _i92mefto.WorkoutSeries) {
|
||||
return _i92mefto.WorkoutSeries.fromJson(data) as T;
|
||||
}
|
||||
if (t == _is.getType<_ikw0qg8x.MapSource?>()) {
|
||||
return (data != null ? _ikw0qg8x.MapSource.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_imed3x5d.ContentKey?>()) {
|
||||
return (data != null ? _imed3x5d.ContentKey.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -339,6 +347,7 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
|
||||
static String? getClassNameForType(Type type) {
|
||||
return switch (type) {
|
||||
_ikw0qg8x.MapSource => 'MapSource',
|
||||
_imed3x5d.ContentKey => 'ContentKey',
|
||||
_i5b9b17z.Workout => 'Workout',
|
||||
_i92mefto.WorkoutSeries => 'WorkoutSeries',
|
||||
|
|
@ -359,6 +368,8 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
}
|
||||
|
||||
switch (data) {
|
||||
case _ikw0qg8x.MapSource():
|
||||
return 'MapSource';
|
||||
case _imed3x5d.ContentKey():
|
||||
return 'ContentKey';
|
||||
case _i5b9b17z.Workout():
|
||||
|
|
@ -405,6 +416,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (dataClassName is! String) {
|
||||
return super.deserializeByClassName(data);
|
||||
}
|
||||
if (dataClassName == 'MapSource') {
|
||||
return deserialize<_ikw0qg8x.MapSource>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'ContentKey') {
|
||||
return deserialize<_imed3x5d.ContentKey>(data['data']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
map:
|
||||
- source:
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import 'package:halkyon_train_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_train_server/src/map/map_source_config.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
|
||||
/// The map workouts are drawn on (ADR 0030, D11, D22), for the people signed
|
||||
/// in: an operator's provider may have its key in the style's address.
|
||||
class MapEndpoint extends Endpoint {
|
||||
@override
|
||||
bool get requireLogin => true;
|
||||
|
||||
/// The instance's map source, as its operator sets it.
|
||||
Future<MapSource> source(Session session) async => mapSource();
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
### The map this instance's workouts are drawn on (ADR 0030, D11, D22), as
|
||||
### its operator sets it: a MapLibre style, its source's name and the credit
|
||||
### it asks for, and where the source says what it sees. OpenFreeMap where
|
||||
### nothing is set.
|
||||
class: MapSource
|
||||
fields:
|
||||
### What the app names as drawing the map, as `OpenFreeMap`.
|
||||
name: String
|
||||
### The MapLibre (Mapbox GL) style the vector tiles are drawn by; a
|
||||
### provider's key may be in it, so only people signed in get it.
|
||||
styleUrl: String
|
||||
### The credit the map shows, as the source's terms ask.
|
||||
attribution: String
|
||||
### Where the source says what it keeps of those viewing its maps; null
|
||||
### where the operator names nothing.
|
||||
aboutUrl: String?
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:halkyon_train_server/src/generated/protocol.dart';
|
||||
|
||||
/// The map source where the operator sets none (ADR 0030, D11): OpenFreeMap,
|
||||
/// which takes no key and makes no contract, its Liberty style.
|
||||
final openFreeMap = MapSource(
|
||||
name: 'OpenFreeMap',
|
||||
styleUrl: 'https://tiles.openfreemap.org/styles/liberty',
|
||||
attribution: 'OpenFreeMap © OpenMapTiles Data from OpenStreetMap',
|
||||
aboutUrl: 'https://openfreemap.org',
|
||||
);
|
||||
|
||||
/// The instance's map source, as its operator's environment sets it.
|
||||
MapSource mapSource() => mapSourceFrom(Platform.environment);
|
||||
|
||||
/// The map source [environment] sets: `MAP_STYLE_URL`, a MapLibre style over
|
||||
/// HTTPS, with `MAP_SOURCE_NAME` (else the style's host), `MAP_ATTRIBUTION`
|
||||
/// (else OpenStreetMap's credit) and `MAP_ABOUT_URL` (HTTPS, else none);
|
||||
/// [openFreeMap] without a style, or with one that isn't an HTTPS address.
|
||||
MapSource mapSourceFrom(Map<String, String> environment) {
|
||||
final style = _https(environment['MAP_STYLE_URL']);
|
||||
if (style == null) {
|
||||
return openFreeMap;
|
||||
}
|
||||
String? set(String name) => switch (environment[name]?.trim()) {
|
||||
final value? when value.isNotEmpty => value,
|
||||
_ => null,
|
||||
};
|
||||
return MapSource(
|
||||
name: set('MAP_SOURCE_NAME') ?? style.host,
|
||||
styleUrl: style.toString(),
|
||||
attribution: set('MAP_ATTRIBUTION') ?? '© OpenStreetMap contributors',
|
||||
aboutUrl: _https(environment['MAP_ABOUT_URL'])?.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
/// [value] as an HTTPS address with a host; null for anything else.
|
||||
Uri? _https(String? value) {
|
||||
final uri = Uri.tryParse(value?.trim() ?? '');
|
||||
return uri != null && uri.isScheme('https') && uri.host.isNotEmpty
|
||||
? uri
|
||||
: null;
|
||||
}
|
||||
Loading…
Reference in a new issue