feat(todo): the server finds places through the instance's Photon (ADR 0033, D32)
Todo's locations search through the person's own instance, as Train's planner does, not a public service: `SearchEndpoint` on `PlaceSearch`, for those signed in, 60 searches a minute, nothing kept or logged. Its `Place` carries the address's parts and the object in OpenStreetMap, and a shop's car park or charger is left out, which came first for `Lindau EDEKA`. `compose.todo-search.yaml` puts Todo's server on compose.search.yaml's Photon. A file of its own: without it Todo starts as it did, and offers no search. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
88f1cb953d
commit
c9d3eb4dd1
26 changed files with 1880 additions and 12 deletions
File diff suppressed because one or more lines are too long
|
|
@ -13,6 +13,10 @@
|
|||
import 'dart:async' as _ida;
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart' as _ihk4p5t7;
|
||||
import 'package:halkyon_oidc_client/halkyon_oidc_client.dart' as _ibkpwzgl;
|
||||
import 'package:halkyon_todo_client/src/protocol/search/place.dart'
|
||||
as _i15767wh;
|
||||
import 'package:halkyon_todo_client/src/protocol/search/place_point.dart'
|
||||
as _icyq0v3n;
|
||||
import 'package:http/http.dart' as _i85jenna;
|
||||
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
|
||||
as _iacc;
|
||||
|
|
@ -26,6 +30,44 @@ import 'protocol.dart' as _il2as5qe;
|
|||
import 'sync_tables.dart' as _ii3f3u05;
|
||||
import 'package:halkyon_todo_client/migrations/migration_registry.dart';
|
||||
|
||||
/// Places found by the instance's Photon (ADR 0033, D32), for those signed
|
||||
/// in: Photon has no sign-in of its own, so the server alone reaches it.
|
||||
/// Neither the query nor the places are kept or logged, and a task's location
|
||||
/// stays in its sealed payload. Refusals are `SearchRefused`.
|
||||
/// {@category Endpoint}
|
||||
class EndpointSearch extends _isc.EndpointRef {
|
||||
EndpointSearch(_isc.EndpointCaller caller) : super(caller);
|
||||
|
||||
@override
|
||||
String get name => 'search';
|
||||
|
||||
/// Whether the instance searches for places.
|
||||
_ida.Future<bool> offered() => caller.callServerEndpoint<bool>(
|
||||
'search',
|
||||
'offered',
|
||||
{},
|
||||
);
|
||||
|
||||
/// The places [query] finds, nearest [near] first (the map's centre, or
|
||||
/// the person), named in [language] (`en`, `de`; else each its own name).
|
||||
/// Refused where the instance has no Photon or it fails, for a query of
|
||||
/// fewer than `PlaceSearch.minQuery` or more than `PlaceSearch.maxQuery`
|
||||
/// characters, and beyond `PlaceSearch.perMinute` searches a minute.
|
||||
_ida.Future<List<_i15767wh.Place>> places(
|
||||
String query, {
|
||||
_icyq0v3n.PlacePoint? near,
|
||||
String? language,
|
||||
}) => caller.callServerEndpoint<List<_i15767wh.Place>>(
|
||||
'search',
|
||||
'places',
|
||||
{
|
||||
'query': query,
|
||||
'near': near,
|
||||
'language': language,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class Modules {
|
||||
Modules(Client client) {
|
||||
base = _ihk4p5t7.Caller(client);
|
||||
|
|
@ -73,13 +115,16 @@ class Client extends _isc.ServerpodClientShared {
|
|||
disconnectStreamsOnLostInternetConnection,
|
||||
httpClientOverride: httpClientOverride,
|
||||
) {
|
||||
search = EndpointSearch(this);
|
||||
modules = Modules(this);
|
||||
}
|
||||
|
||||
late final EndpointSearch search;
|
||||
|
||||
late final Modules modules;
|
||||
|
||||
@override
|
||||
Map<String, _isc.EndpointRef> get endpointRefLookup => {};
|
||||
Map<String, _isc.EndpointRef> get endpointRefLookup => {'search': search};
|
||||
|
||||
@override
|
||||
Map<String, _isc.ModuleEndpointCaller> get moduleLookup => {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart' as _ihk4p5t7;
|
||||
import 'package:halkyon_oidc_client/halkyon_oidc_client.dart' as _ibkpwzgl;
|
||||
import 'package:halkyon_todo_client/src/protocol/search/place.dart'
|
||||
as _i15767wh;
|
||||
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
|
||||
as _iacc;
|
||||
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
|
||||
|
|
@ -25,12 +27,18 @@ import 'label/label.dart' as _ivak08z4;
|
|||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'project/project.dart' as _i9c44spp;
|
||||
import 'project/section.dart' as _imadkvi9;
|
||||
import 'search/place.dart' as _iadh29yw;
|
||||
import 'search/place_point.dart' as _itf0ur2u;
|
||||
import 'search/search_refused.dart' as _ihbxkwke;
|
||||
import 'settings/todo_settings.dart' as _icak60vd;
|
||||
import 'task/task.dart' as _ib4f8uyw;
|
||||
export 'label/label.dart';
|
||||
export 'privacy/content_key.dart';
|
||||
export 'project/project.dart';
|
||||
export 'project/section.dart';
|
||||
export 'search/place.dart';
|
||||
export 'search/place_point.dart';
|
||||
export 'search/search_refused.dart';
|
||||
export 'settings/todo_settings.dart';
|
||||
export 'task/task.dart';
|
||||
export 'client.dart';
|
||||
|
|
@ -501,6 +509,15 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _imadkvi9.Section) {
|
||||
return _imadkvi9.Section.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iadh29yw.Place) {
|
||||
return _iadh29yw.Place.fromJson(data) as T;
|
||||
}
|
||||
if (t == _itf0ur2u.PlacePoint) {
|
||||
return _itf0ur2u.PlacePoint.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ihbxkwke.SearchRefused) {
|
||||
return _ihbxkwke.SearchRefused.fromJson(data) as T;
|
||||
}
|
||||
if (t == _icak60vd.TodoSettings) {
|
||||
return _icak60vd.TodoSettings.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -519,12 +536,26 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _isc.getType<_imadkvi9.Section?>()) {
|
||||
return (data != null ? _imadkvi9.Section.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_iadh29yw.Place?>()) {
|
||||
return (data != null ? _iadh29yw.Place.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_itf0ur2u.PlacePoint?>()) {
|
||||
return (data != null ? _itf0ur2u.PlacePoint.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_ihbxkwke.SearchRefused?>()) {
|
||||
return (data != null ? _ihbxkwke.SearchRefused.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_icak60vd.TodoSettings?>()) {
|
||||
return (data != null ? _icak60vd.TodoSettings.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_ib4f8uyw.Task?>()) {
|
||||
return (data != null ? _ib4f8uyw.Task.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == List<_i15767wh.Place>) {
|
||||
return (data as List).map((e) => deserialize<_i15767wh.Place>(e)).toList()
|
||||
as T;
|
||||
}
|
||||
try {
|
||||
return _ihk4p5t7.Protocol().deserialize<T>(data, t);
|
||||
} on _isc.DeserializationTypeNotFoundException catch (_) {}
|
||||
|
|
@ -549,6 +580,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
_imed3x5d.ContentKey => 'ContentKey',
|
||||
_i9c44spp.Project => 'Project',
|
||||
_imadkvi9.Section => 'Section',
|
||||
_iadh29yw.Place => 'Place',
|
||||
_itf0ur2u.PlacePoint => 'PlacePoint',
|
||||
_ihbxkwke.SearchRefused => 'SearchRefused',
|
||||
_icak60vd.TodoSettings => 'TodoSettings',
|
||||
_ib4f8uyw.Task => 'Task',
|
||||
_ => null,
|
||||
|
|
@ -576,6 +610,12 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return 'Project';
|
||||
case _imadkvi9.Section():
|
||||
return 'Section';
|
||||
case _iadh29yw.Place():
|
||||
return 'Place';
|
||||
case _itf0ur2u.PlacePoint():
|
||||
return 'PlacePoint';
|
||||
case _ihbxkwke.SearchRefused():
|
||||
return 'SearchRefused';
|
||||
case _icak60vd.TodoSettings():
|
||||
return 'TodoSettings';
|
||||
case _ib4f8uyw.Task():
|
||||
|
|
@ -628,6 +668,15 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (dataClassName == 'Section') {
|
||||
return deserialize<_imadkvi9.Section>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'Place') {
|
||||
return deserialize<_iadh29yw.Place>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'PlacePoint') {
|
||||
return deserialize<_itf0ur2u.PlacePoint>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SearchRefused') {
|
||||
return deserialize<_ihbxkwke.SearchRefused>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'TodoSettings') {
|
||||
return deserialize<_icak60vd.TodoSettings>(data['data']);
|
||||
}
|
||||
|
|
|
|||
226
apps/todo/halkyon_todo_client/lib/src/protocol/search/place.dart
Normal file
226
apps/todo/halkyon_todo_client/lib/src/protocol/search/place.dart
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
/* 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_client/serverpod_client.dart' as _isc;
|
||||
|
||||
/// A place found by the instance's search (ADR 0033, D32): what the location
|
||||
/// sheet lists and a task's location is made of. Never stored by the server:
|
||||
/// a task's location is in its sealed payload.
|
||||
abstract class Place
|
||||
implements _isc.SerializableModel, _isc.ProtocolSerialization {
|
||||
Place._({
|
||||
required this.name,
|
||||
this.detail,
|
||||
required this.kind,
|
||||
this.osmValue,
|
||||
this.osmType,
|
||||
this.osmId,
|
||||
this.street,
|
||||
this.housenumber,
|
||||
this.district,
|
||||
this.city,
|
||||
required this.lat,
|
||||
required this.lon,
|
||||
});
|
||||
|
||||
factory Place({
|
||||
required String name,
|
||||
String? detail,
|
||||
required String kind,
|
||||
String? osmValue,
|
||||
String? osmType,
|
||||
int? osmId,
|
||||
String? street,
|
||||
String? housenumber,
|
||||
String? district,
|
||||
String? city,
|
||||
required double lat,
|
||||
required double lon,
|
||||
}) = _PlaceImpl;
|
||||
|
||||
factory Place.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return Place(
|
||||
name: jsonSerialization['name'] as String,
|
||||
detail: jsonSerialization['detail'] as String?,
|
||||
kind: jsonSerialization['kind'] as String,
|
||||
osmValue: jsonSerialization['osmValue'] as String?,
|
||||
osmType: jsonSerialization['osmType'] as String?,
|
||||
osmId: jsonSerialization['osmId'] as int?,
|
||||
street: jsonSerialization['street'] as String?,
|
||||
housenumber: jsonSerialization['housenumber'] as String?,
|
||||
district: jsonSerialization['district'] as String?,
|
||||
city: jsonSerialization['city'] as String?,
|
||||
lat: (jsonSerialization['lat'] as num).toDouble(),
|
||||
lon: (jsonSerialization['lon'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Its name, or its street and number for an address.
|
||||
String name;
|
||||
|
||||
/// Where it is, as the list shows under the name: its street, its town and
|
||||
/// its country, those the name doesn't say already; null where none.
|
||||
String? detail;
|
||||
|
||||
/// What kind of place, as Photon names it (`city`, `street`, `house`,
|
||||
/// `locality`, `other`, …).
|
||||
String kind;
|
||||
|
||||
/// What it is in OpenStreetMap (`osm_value`: `supermarket`, `restaurant`,
|
||||
/// …); null where Photon doesn't say.
|
||||
String? osmValue;
|
||||
|
||||
/// Its object in OpenStreetMap, as Photon gives it: `N`, `W` or `R`, and
|
||||
/// its ID; what its opening hours are looked up and its page opened by.
|
||||
String? osmType;
|
||||
|
||||
int? osmId;
|
||||
|
||||
/// Its address, each part where Photon has it.
|
||||
String? street;
|
||||
|
||||
String? housenumber;
|
||||
|
||||
String? district;
|
||||
|
||||
String? city;
|
||||
|
||||
double lat;
|
||||
|
||||
double lon;
|
||||
|
||||
/// Returns a shallow copy of this [Place]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
Place copyWith({
|
||||
String? name,
|
||||
String? detail,
|
||||
String? kind,
|
||||
String? osmValue,
|
||||
String? osmType,
|
||||
int? osmId,
|
||||
String? street,
|
||||
String? housenumber,
|
||||
String? district,
|
||||
String? city,
|
||||
double? lat,
|
||||
double? lon,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'Place',
|
||||
'name': name,
|
||||
if (detail != null) 'detail': detail,
|
||||
'kind': kind,
|
||||
if (osmValue != null) 'osmValue': osmValue,
|
||||
if (osmType != null) 'osmType': osmType,
|
||||
if (osmId != null) 'osmId': osmId,
|
||||
if (street != null) 'street': street,
|
||||
if (housenumber != null) 'housenumber': housenumber,
|
||||
if (district != null) 'district': district,
|
||||
if (city != null) 'city': city,
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'Place',
|
||||
'name': name,
|
||||
if (detail != null) 'detail': detail,
|
||||
'kind': kind,
|
||||
if (osmValue != null) 'osmValue': osmValue,
|
||||
if (osmType != null) 'osmType': osmType,
|
||||
if (osmId != null) 'osmId': osmId,
|
||||
if (street != null) 'street': street,
|
||||
if (housenumber != null) 'housenumber': housenumber,
|
||||
if (district != null) 'district': district,
|
||||
if (city != null) 'city': city,
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _PlaceImpl extends Place {
|
||||
_PlaceImpl({
|
||||
required String name,
|
||||
String? detail,
|
||||
required String kind,
|
||||
String? osmValue,
|
||||
String? osmType,
|
||||
int? osmId,
|
||||
String? street,
|
||||
String? housenumber,
|
||||
String? district,
|
||||
String? city,
|
||||
required double lat,
|
||||
required double lon,
|
||||
}) : super._(
|
||||
name: name,
|
||||
detail: detail,
|
||||
kind: kind,
|
||||
osmValue: osmValue,
|
||||
osmType: osmType,
|
||||
osmId: osmId,
|
||||
street: street,
|
||||
housenumber: housenumber,
|
||||
district: district,
|
||||
city: city,
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [Place]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
Place copyWith({
|
||||
String? name,
|
||||
Object? detail = _Undefined,
|
||||
String? kind,
|
||||
Object? osmValue = _Undefined,
|
||||
Object? osmType = _Undefined,
|
||||
Object? osmId = _Undefined,
|
||||
Object? street = _Undefined,
|
||||
Object? housenumber = _Undefined,
|
||||
Object? district = _Undefined,
|
||||
Object? city = _Undefined,
|
||||
double? lat,
|
||||
double? lon,
|
||||
}) {
|
||||
return Place(
|
||||
name: name ?? this.name,
|
||||
detail: detail is String? ? detail : this.detail,
|
||||
kind: kind ?? this.kind,
|
||||
osmValue: osmValue is String? ? osmValue : this.osmValue,
|
||||
osmType: osmType is String? ? osmType : this.osmType,
|
||||
osmId: osmId is int? ? osmId : this.osmId,
|
||||
street: street is String? ? street : this.street,
|
||||
housenumber: housenumber is String? ? housenumber : this.housenumber,
|
||||
district: district is String? ? district : this.district,
|
||||
city: city is String? ? city : this.city,
|
||||
lat: lat ?? this.lat,
|
||||
lon: lon ?? this.lon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/* 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_client/serverpod_client.dart' as _isc;
|
||||
|
||||
/// A point on the earth, in degrees (WGS 84): what a search's places are
|
||||
/// nearest to first (ADR 0033, D32).
|
||||
abstract class PlacePoint
|
||||
implements _isc.SerializableModel, _isc.ProtocolSerialization {
|
||||
PlacePoint._({
|
||||
required this.lat,
|
||||
required this.lon,
|
||||
});
|
||||
|
||||
factory PlacePoint({
|
||||
required double lat,
|
||||
required double lon,
|
||||
}) = _PlacePointImpl;
|
||||
|
||||
factory PlacePoint.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return PlacePoint(
|
||||
lat: (jsonSerialization['lat'] as num).toDouble(),
|
||||
lon: (jsonSerialization['lon'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
double lat;
|
||||
|
||||
double lon;
|
||||
|
||||
/// Returns a shallow copy of this [PlacePoint]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
PlacePoint copyWith({
|
||||
double? lat,
|
||||
double? lon,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'PlacePoint',
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'PlacePoint',
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlacePointImpl extends PlacePoint {
|
||||
_PlacePointImpl({
|
||||
required double lat,
|
||||
required double lon,
|
||||
}) : super._(
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [PlacePoint]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
PlacePoint copyWith({
|
||||
double? lat,
|
||||
double? lon,
|
||||
}) {
|
||||
return PlacePoint(
|
||||
lat: lat ?? this.lat,
|
||||
lon: lon ?? this.lon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/* 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_client/serverpod_client.dart' as _isc;
|
||||
|
||||
/// Why the instance searches nothing (ADR 0033, D32): `off` where it has no
|
||||
/// Photon, `invalid` for a query shorter than `PlaceSearch.minQuery` or
|
||||
/// longer than `PlaceSearch.maxQuery` characters or a place off the earth,
|
||||
/// `tooOften` beyond `PlaceSearch.perMinute` searches a minute,
|
||||
/// `unreachable` where Photon didn't answer.
|
||||
abstract class SearchRefused
|
||||
implements
|
||||
_isc.SerializableException,
|
||||
_isc.SerializableModel,
|
||||
_isc.ProtocolSerialization {
|
||||
SearchRefused._({required this.reason});
|
||||
|
||||
factory SearchRefused({required String reason}) = _SearchRefusedImpl;
|
||||
|
||||
factory SearchRefused.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return SearchRefused(reason: jsonSerialization['reason'] as String);
|
||||
}
|
||||
|
||||
String reason;
|
||||
|
||||
/// Returns a shallow copy of this [SearchRefused]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
SearchRefused copyWith({String? reason});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'SearchRefused',
|
||||
'reason': reason,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'SearchRefused',
|
||||
'reason': reason,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SearchRefused(reason: $reason)';
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchRefusedImpl extends SearchRefused {
|
||||
_SearchRefusedImpl({required String reason}) : super._(reason: reason);
|
||||
|
||||
/// Returns a shallow copy of this [SearchRefused]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
SearchRefused copyWith({String? reason}) {
|
||||
return SearchRefused(reason: reason ?? this.reason);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ import 'package:halkyon_base_server/halkyon_base_server.dart' as _ik8molhd;
|
|||
import 'package:halkyon_oidc_server/halkyon_oidc_server.dart' as _ipoqfiqr;
|
||||
import 'package:halkyon_todo_server/src/generated/future_calls.dart'
|
||||
as _i772x17y;
|
||||
import 'package:halkyon_todo_server/src/generated/search/place_point.dart'
|
||||
as _iywv9say;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
||||
as _iacs;
|
||||
|
|
@ -21,11 +23,67 @@ 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 '../search/search_endpoint.dart' as _i2f0v2ey;
|
||||
export 'future_calls.dart' show ServerpodFutureCallsGetter;
|
||||
|
||||
class Endpoints extends _is.EndpointDispatch {
|
||||
@override
|
||||
void initializeEndpoints(_is.Server server) {
|
||||
var endpoints = <String, _is.Endpoint>{
|
||||
'search': _i2f0v2ey.SearchEndpoint()
|
||||
..initialize(
|
||||
server,
|
||||
'search',
|
||||
null,
|
||||
),
|
||||
};
|
||||
connectors['search'] = _is.EndpointConnector(
|
||||
name: 'search',
|
||||
endpoint: endpoints['search']!,
|
||||
methodConnectors: {
|
||||
'offered': _is.MethodConnector(
|
||||
name: 'offered',
|
||||
params: {},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async => (endpoints['search'] as _i2f0v2ey.SearchEndpoint)
|
||||
.offered(session),
|
||||
),
|
||||
'places': _is.MethodConnector(
|
||||
name: 'places',
|
||||
params: {
|
||||
'query': _is.ParameterDescription(
|
||||
name: 'query',
|
||||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
'near': _is.ParameterDescription(
|
||||
name: 'near',
|
||||
type: _is.getType<_iywv9say.PlacePoint?>(),
|
||||
nullable: true,
|
||||
),
|
||||
'language': _is.ParameterDescription(
|
||||
name: 'language',
|
||||
type: _is.getType<String?>(),
|
||||
nullable: true,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['search'] as _i2f0v2ey.SearchEndpoint).places(
|
||||
session,
|
||||
params['query'],
|
||||
near: params['near'],
|
||||
language: params['language'],
|
||||
),
|
||||
),
|
||||
},
|
||||
);
|
||||
modules['halkyon_base'] = _ik8molhd.Endpoints()
|
||||
..initializeEndpoints(server);
|
||||
modules['serverpod_auth_idp'] = _iais.Endpoints()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:halkyon_base_server/halkyon_base_server.dart' as _ik8molhd;
|
||||
import 'package:halkyon_oidc_server/halkyon_oidc_server.dart' as _ipoqfiqr;
|
||||
import 'package:halkyon_todo_server/src/generated/search/place.dart'
|
||||
as _ih6de0l6;
|
||||
import 'package:serverpod/protocol.dart' as _isp;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
||||
|
|
@ -25,12 +27,18 @@ import 'label/label.dart' as _ivak08z4;
|
|||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'project/project.dart' as _i9c44spp;
|
||||
import 'project/section.dart' as _imadkvi9;
|
||||
import 'search/place.dart' as _iadh29yw;
|
||||
import 'search/place_point.dart' as _itf0ur2u;
|
||||
import 'search/search_refused.dart' as _ihbxkwke;
|
||||
import 'settings/todo_settings.dart' as _icak60vd;
|
||||
import 'task/task.dart' as _ib4f8uyw;
|
||||
export 'label/label.dart';
|
||||
export 'privacy/content_key.dart';
|
||||
export 'project/project.dart';
|
||||
export 'project/section.dart';
|
||||
export 'search/place.dart';
|
||||
export 'search/place_point.dart';
|
||||
export 'search/search_refused.dart';
|
||||
export 'settings/todo_settings.dart';
|
||||
export 'task/task.dart';
|
||||
export 'sync_tables.dart';
|
||||
|
|
@ -486,6 +494,15 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _imadkvi9.Section) {
|
||||
return _imadkvi9.Section.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iadh29yw.Place) {
|
||||
return _iadh29yw.Place.fromJson(data) as T;
|
||||
}
|
||||
if (t == _itf0ur2u.PlacePoint) {
|
||||
return _itf0ur2u.PlacePoint.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ihbxkwke.SearchRefused) {
|
||||
return _ihbxkwke.SearchRefused.fromJson(data) as T;
|
||||
}
|
||||
if (t == _icak60vd.TodoSettings) {
|
||||
return _icak60vd.TodoSettings.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -504,12 +521,26 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _is.getType<_imadkvi9.Section?>()) {
|
||||
return (data != null ? _imadkvi9.Section.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_iadh29yw.Place?>()) {
|
||||
return (data != null ? _iadh29yw.Place.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_itf0ur2u.PlacePoint?>()) {
|
||||
return (data != null ? _itf0ur2u.PlacePoint.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_ihbxkwke.SearchRefused?>()) {
|
||||
return (data != null ? _ihbxkwke.SearchRefused.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_icak60vd.TodoSettings?>()) {
|
||||
return (data != null ? _icak60vd.TodoSettings.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_ib4f8uyw.Task?>()) {
|
||||
return (data != null ? _ib4f8uyw.Task.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == List<_ih6de0l6.Place>) {
|
||||
return (data as List).map((e) => deserialize<_ih6de0l6.Place>(e)).toList()
|
||||
as T;
|
||||
}
|
||||
try {
|
||||
return _ik8molhd.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
|
|
@ -537,6 +568,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
_imed3x5d.ContentKey => 'ContentKey',
|
||||
_i9c44spp.Project => 'Project',
|
||||
_imadkvi9.Section => 'Section',
|
||||
_iadh29yw.Place => 'Place',
|
||||
_itf0ur2u.PlacePoint => 'PlacePoint',
|
||||
_ihbxkwke.SearchRefused => 'SearchRefused',
|
||||
_icak60vd.TodoSettings => 'TodoSettings',
|
||||
_ib4f8uyw.Task => 'Task',
|
||||
_ => null,
|
||||
|
|
@ -564,6 +598,12 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return 'Project';
|
||||
case _imadkvi9.Section():
|
||||
return 'Section';
|
||||
case _iadh29yw.Place():
|
||||
return 'Place';
|
||||
case _itf0ur2u.PlacePoint():
|
||||
return 'PlacePoint';
|
||||
case _ihbxkwke.SearchRefused():
|
||||
return 'SearchRefused';
|
||||
case _icak60vd.TodoSettings():
|
||||
return 'TodoSettings';
|
||||
case _ib4f8uyw.Task():
|
||||
|
|
@ -620,6 +660,15 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (dataClassName == 'Section') {
|
||||
return deserialize<_imadkvi9.Section>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'Place') {
|
||||
return deserialize<_iadh29yw.Place>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'PlacePoint') {
|
||||
return deserialize<_itf0ur2u.PlacePoint>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SearchRefused') {
|
||||
return deserialize<_ihbxkwke.SearchRefused>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'TodoSettings') {
|
||||
return deserialize<_icak60vd.TodoSettings>(data['data']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
search:
|
||||
- offered:
|
||||
- places:
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
/* 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;
|
||||
|
||||
/// A place found by the instance's search (ADR 0033, D32): what the location
|
||||
/// sheet lists and a task's location is made of. Never stored by the server:
|
||||
/// a task's location is in its sealed payload.
|
||||
abstract class Place
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
Place._({
|
||||
required this.name,
|
||||
this.detail,
|
||||
required this.kind,
|
||||
this.osmValue,
|
||||
this.osmType,
|
||||
this.osmId,
|
||||
this.street,
|
||||
this.housenumber,
|
||||
this.district,
|
||||
this.city,
|
||||
required this.lat,
|
||||
required this.lon,
|
||||
});
|
||||
|
||||
factory Place({
|
||||
required String name,
|
||||
String? detail,
|
||||
required String kind,
|
||||
String? osmValue,
|
||||
String? osmType,
|
||||
int? osmId,
|
||||
String? street,
|
||||
String? housenumber,
|
||||
String? district,
|
||||
String? city,
|
||||
required double lat,
|
||||
required double lon,
|
||||
}) = _PlaceImpl;
|
||||
|
||||
factory Place.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return Place(
|
||||
name: jsonSerialization['name'] as String,
|
||||
detail: jsonSerialization['detail'] as String?,
|
||||
kind: jsonSerialization['kind'] as String,
|
||||
osmValue: jsonSerialization['osmValue'] as String?,
|
||||
osmType: jsonSerialization['osmType'] as String?,
|
||||
osmId: jsonSerialization['osmId'] as int?,
|
||||
street: jsonSerialization['street'] as String?,
|
||||
housenumber: jsonSerialization['housenumber'] as String?,
|
||||
district: jsonSerialization['district'] as String?,
|
||||
city: jsonSerialization['city'] as String?,
|
||||
lat: (jsonSerialization['lat'] as num).toDouble(),
|
||||
lon: (jsonSerialization['lon'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Its name, or its street and number for an address.
|
||||
String name;
|
||||
|
||||
/// Where it is, as the list shows under the name: its street, its town and
|
||||
/// its country, those the name doesn't say already; null where none.
|
||||
String? detail;
|
||||
|
||||
/// What kind of place, as Photon names it (`city`, `street`, `house`,
|
||||
/// `locality`, `other`, …).
|
||||
String kind;
|
||||
|
||||
/// What it is in OpenStreetMap (`osm_value`: `supermarket`, `restaurant`,
|
||||
/// …); null where Photon doesn't say.
|
||||
String? osmValue;
|
||||
|
||||
/// Its object in OpenStreetMap, as Photon gives it: `N`, `W` or `R`, and
|
||||
/// its ID; what its opening hours are looked up and its page opened by.
|
||||
String? osmType;
|
||||
|
||||
int? osmId;
|
||||
|
||||
/// Its address, each part where Photon has it.
|
||||
String? street;
|
||||
|
||||
String? housenumber;
|
||||
|
||||
String? district;
|
||||
|
||||
String? city;
|
||||
|
||||
double lat;
|
||||
|
||||
double lon;
|
||||
|
||||
/// Returns a shallow copy of this [Place]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
Place copyWith({
|
||||
String? name,
|
||||
String? detail,
|
||||
String? kind,
|
||||
String? osmValue,
|
||||
String? osmType,
|
||||
int? osmId,
|
||||
String? street,
|
||||
String? housenumber,
|
||||
String? district,
|
||||
String? city,
|
||||
double? lat,
|
||||
double? lon,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'Place',
|
||||
'name': name,
|
||||
if (detail != null) 'detail': detail,
|
||||
'kind': kind,
|
||||
if (osmValue != null) 'osmValue': osmValue,
|
||||
if (osmType != null) 'osmType': osmType,
|
||||
if (osmId != null) 'osmId': osmId,
|
||||
if (street != null) 'street': street,
|
||||
if (housenumber != null) 'housenumber': housenumber,
|
||||
if (district != null) 'district': district,
|
||||
if (city != null) 'city': city,
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'Place',
|
||||
'name': name,
|
||||
if (detail != null) 'detail': detail,
|
||||
'kind': kind,
|
||||
if (osmValue != null) 'osmValue': osmValue,
|
||||
if (osmType != null) 'osmType': osmType,
|
||||
if (osmId != null) 'osmId': osmId,
|
||||
if (street != null) 'street': street,
|
||||
if (housenumber != null) 'housenumber': housenumber,
|
||||
if (district != null) 'district': district,
|
||||
if (city != null) 'city': city,
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _PlaceImpl extends Place {
|
||||
_PlaceImpl({
|
||||
required String name,
|
||||
String? detail,
|
||||
required String kind,
|
||||
String? osmValue,
|
||||
String? osmType,
|
||||
int? osmId,
|
||||
String? street,
|
||||
String? housenumber,
|
||||
String? district,
|
||||
String? city,
|
||||
required double lat,
|
||||
required double lon,
|
||||
}) : super._(
|
||||
name: name,
|
||||
detail: detail,
|
||||
kind: kind,
|
||||
osmValue: osmValue,
|
||||
osmType: osmType,
|
||||
osmId: osmId,
|
||||
street: street,
|
||||
housenumber: housenumber,
|
||||
district: district,
|
||||
city: city,
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [Place]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
Place copyWith({
|
||||
String? name,
|
||||
Object? detail = _Undefined,
|
||||
String? kind,
|
||||
Object? osmValue = _Undefined,
|
||||
Object? osmType = _Undefined,
|
||||
Object? osmId = _Undefined,
|
||||
Object? street = _Undefined,
|
||||
Object? housenumber = _Undefined,
|
||||
Object? district = _Undefined,
|
||||
Object? city = _Undefined,
|
||||
double? lat,
|
||||
double? lon,
|
||||
}) {
|
||||
return Place(
|
||||
name: name ?? this.name,
|
||||
detail: detail is String? ? detail : this.detail,
|
||||
kind: kind ?? this.kind,
|
||||
osmValue: osmValue is String? ? osmValue : this.osmValue,
|
||||
osmType: osmType is String? ? osmType : this.osmType,
|
||||
osmId: osmId is int? ? osmId : this.osmId,
|
||||
street: street is String? ? street : this.street,
|
||||
housenumber: housenumber is String? ? housenumber : this.housenumber,
|
||||
district: district is String? ? district : this.district,
|
||||
city: city is String? ? city : this.city,
|
||||
lat: lat ?? this.lat,
|
||||
lon: lon ?? this.lon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/* 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;
|
||||
|
||||
/// A point on the earth, in degrees (WGS 84): what a search's places are
|
||||
/// nearest to first (ADR 0033, D32).
|
||||
abstract class PlacePoint
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
PlacePoint._({
|
||||
required this.lat,
|
||||
required this.lon,
|
||||
});
|
||||
|
||||
factory PlacePoint({
|
||||
required double lat,
|
||||
required double lon,
|
||||
}) = _PlacePointImpl;
|
||||
|
||||
factory PlacePoint.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return PlacePoint(
|
||||
lat: (jsonSerialization['lat'] as num).toDouble(),
|
||||
lon: (jsonSerialization['lon'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
double lat;
|
||||
|
||||
double lon;
|
||||
|
||||
/// Returns a shallow copy of this [PlacePoint]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
PlacePoint copyWith({
|
||||
double? lat,
|
||||
double? lon,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'PlacePoint',
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'PlacePoint',
|
||||
'lat': lat,
|
||||
'lon': lon,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlacePointImpl extends PlacePoint {
|
||||
_PlacePointImpl({
|
||||
required double lat,
|
||||
required double lon,
|
||||
}) : super._(
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [PlacePoint]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
PlacePoint copyWith({
|
||||
double? lat,
|
||||
double? lon,
|
||||
}) {
|
||||
return PlacePoint(
|
||||
lat: lat ?? this.lat,
|
||||
lon: lon ?? this.lon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/* 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;
|
||||
|
||||
/// Why the instance searches nothing (ADR 0033, D32): `off` where it has no
|
||||
/// Photon, `invalid` for a query shorter than `PlaceSearch.minQuery` or
|
||||
/// longer than `PlaceSearch.maxQuery` characters or a place off the earth,
|
||||
/// `tooOften` beyond `PlaceSearch.perMinute` searches a minute,
|
||||
/// `unreachable` where Photon didn't answer.
|
||||
abstract class SearchRefused
|
||||
implements
|
||||
_is.SerializableException,
|
||||
_is.SerializableModel,
|
||||
_is.ProtocolSerialization {
|
||||
SearchRefused._({required this.reason});
|
||||
|
||||
factory SearchRefused({required String reason}) = _SearchRefusedImpl;
|
||||
|
||||
factory SearchRefused.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return SearchRefused(reason: jsonSerialization['reason'] as String);
|
||||
}
|
||||
|
||||
String reason;
|
||||
|
||||
/// Returns a shallow copy of this [SearchRefused]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
SearchRefused copyWith({String? reason});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'SearchRefused',
|
||||
'reason': reason,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'SearchRefused',
|
||||
'reason': reason,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SearchRefused(reason: $reason)';
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchRefusedImpl extends SearchRefused {
|
||||
_SearchRefusedImpl({required String reason}) : super._(reason: reason);
|
||||
|
||||
/// Returns a shallow copy of this [SearchRefused]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
SearchRefused copyWith({String? reason}) {
|
||||
return SearchRefused(reason: reason ?? this.reason);
|
||||
}
|
||||
}
|
||||
138
apps/todo/halkyon_todo_server/lib/src/search/photon.dart
Normal file
138
apps/todo/halkyon_todo_server/lib/src/search/photon.dart
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:halkyon_todo_server/src/generated/protocol.dart';
|
||||
|
||||
/// Finds places for a query (ADR 0033, D32): the instance's Photon, or a
|
||||
/// fake in tests. Answers Photon's GeoJSON, else throws [PhotonFailed].
|
||||
abstract interface class PlaceFinder {
|
||||
Future<Map<String, Object?>> search(
|
||||
String query, {
|
||||
PlacePoint? near,
|
||||
String? language,
|
||||
required int limit,
|
||||
});
|
||||
}
|
||||
|
||||
/// Photon didn't answer, or not with GeoJSON.
|
||||
final class const PhotonFailed(final String reason) implements Exception {
|
||||
@override
|
||||
String toString() => 'PhotonFailed: $reason';
|
||||
}
|
||||
|
||||
/// Photon (v1.3.0) at [address], asked over plain HTTP on the network it
|
||||
/// shares with Todo's server alone: `/api` with the query, the point its
|
||||
/// results are nearest to first, the language of their names.
|
||||
class HttpPhoton(final Uri address) implements PlaceFinder {
|
||||
static const _timeout = Duration(seconds: 10);
|
||||
|
||||
@override
|
||||
Future<Map<String, Object?>> search(
|
||||
String query, {
|
||||
PlacePoint? near,
|
||||
String? language,
|
||||
required int limit,
|
||||
}) async {
|
||||
final client = HttpClient()..connectionTimeout = _timeout;
|
||||
try {
|
||||
final uri = address
|
||||
.resolve('api')
|
||||
.replace(
|
||||
queryParameters: {
|
||||
'q': query,
|
||||
'limit': '$limit',
|
||||
if (near != null) ...{
|
||||
'lat': '${near.lat}',
|
||||
'lon': '${near.lon}',
|
||||
},
|
||||
'lang': ?language,
|
||||
},
|
||||
);
|
||||
final HttpClientResponse response;
|
||||
final String body;
|
||||
try {
|
||||
response = await (await client.getUrl(uri)).close().timeout(_timeout);
|
||||
body = await utf8.decodeStream(response).timeout(_timeout);
|
||||
} on Object catch (error) {
|
||||
throw PhotonFailed(error.runtimeType.toString());
|
||||
}
|
||||
if (response.statusCode != HttpStatus.ok) {
|
||||
throw PhotonFailed('HTTP ${response.statusCode}');
|
||||
}
|
||||
try {
|
||||
return jsonDecode(body) as Map<String, Object?>;
|
||||
} on Object {
|
||||
throw const PhotonFailed('no GeoJSON');
|
||||
}
|
||||
} finally {
|
||||
client.close(force: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What OpenStreetMap calls what is no place to go, however a shop's car park
|
||||
/// or charger is named: searching for `EDEKA` should find the shop.
|
||||
const _noDestination = {
|
||||
'charging_station',
|
||||
'parking',
|
||||
'parking_entrance',
|
||||
'parking_space',
|
||||
};
|
||||
|
||||
/// The places in Photon's [geoJson], in its order: each feature a point
|
||||
/// with a name, or a street (with its number) for an address; what is no
|
||||
/// place to go and the rest left out.
|
||||
List<Place> placesOf(Map<String, Object?> geoJson) => [
|
||||
for (final feature in switch (geoJson['features']) {
|
||||
final List<Object?> features => features,
|
||||
_ => const <Object?>[],
|
||||
})
|
||||
if (feature case {
|
||||
'geometry': {
|
||||
'type': 'Point',
|
||||
'coordinates': [final num lon, final num lat, ...],
|
||||
},
|
||||
'properties': final Map<String, Object?> properties,
|
||||
})
|
||||
?_placeOf(properties, lat.toDouble(), lon.toDouble()),
|
||||
];
|
||||
|
||||
Place? _placeOf(Map<String, Object?> properties, double lat, double lon) {
|
||||
String? text(String key) => switch (properties[key]) {
|
||||
final String value when value.trim().isNotEmpty => value.trim(),
|
||||
_ => null,
|
||||
};
|
||||
final street = switch ((text('street'), text('housenumber'))) {
|
||||
(final street?, final number?) => '$street $number',
|
||||
(final street?, null) => street,
|
||||
_ => null,
|
||||
};
|
||||
final name = text('name') ?? street;
|
||||
final osmValue = text('osm_value');
|
||||
if (name == null || _noDestination.contains(osmValue)) {
|
||||
return null;
|
||||
}
|
||||
final town = text('city') ?? text('district') ?? text('county');
|
||||
final detail = <String>[
|
||||
if (street != null && street != name) street,
|
||||
if (town != null && town != name) town,
|
||||
if (text('country') case final country? when country != name) country,
|
||||
];
|
||||
return Place(
|
||||
name: name,
|
||||
detail: detail.isEmpty ? null : detail.join(', '),
|
||||
kind: text('type') ?? 'other',
|
||||
osmValue: osmValue,
|
||||
osmType: text('osm_type'),
|
||||
osmId: switch (properties['osm_id']) {
|
||||
final num id => id.toInt(),
|
||||
_ => null,
|
||||
},
|
||||
street: text('street'),
|
||||
housenumber: text('housenumber'),
|
||||
district: text('district'),
|
||||
city: text('city'),
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
);
|
||||
}
|
||||
27
apps/todo/halkyon_todo_server/lib/src/search/place.spy.yaml
Normal file
27
apps/todo/halkyon_todo_server/lib/src/search/place.spy.yaml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
### A place found by the instance's search (ADR 0033, D32): what the location
|
||||
### sheet lists and a task's location is made of. Never stored by the server:
|
||||
### a task's location is in its sealed payload.
|
||||
class: Place
|
||||
fields:
|
||||
### Its name, or its street and number for an address.
|
||||
name: String
|
||||
### Where it is, as the list shows under the name: its street, its town and
|
||||
### its country, those the name doesn't say already; null where none.
|
||||
detail: String?
|
||||
### What kind of place, as Photon names it (`city`, `street`, `house`,
|
||||
### `locality`, `other`, …).
|
||||
kind: String
|
||||
### What it is in OpenStreetMap (`osm_value`: `supermarket`, `restaurant`,
|
||||
### …); null where Photon doesn't say.
|
||||
osmValue: String?
|
||||
### Its object in OpenStreetMap, as Photon gives it: `N`, `W` or `R`, and
|
||||
### its ID; what its opening hours are looked up and its page opened by.
|
||||
osmType: String?
|
||||
osmId: int?
|
||||
### Its address, each part where Photon has it.
|
||||
street: String?
|
||||
housenumber: String?
|
||||
district: String?
|
||||
city: String?
|
||||
lat: double
|
||||
lon: double
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
### A point on the earth, in degrees (WGS 84): what a search's places are
|
||||
### nearest to first (ADR 0033, D32).
|
||||
class: PlacePoint
|
||||
fields:
|
||||
lat: double
|
||||
lon: double
|
||||
112
apps/todo/halkyon_todo_server/lib/src/search/place_search.dart
Normal file
112
apps/todo/halkyon_todo_server/lib/src/search/place_search.dart
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:halkyon_todo_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_todo_server/src/search/photon.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
|
||||
/// Searching for places on this instance (ADR 0033, D32), through its Photon
|
||||
/// where it has one, for a person as often as [perMinute] allows. Neither
|
||||
/// the query nor the places are kept or logged; the counts live in memory
|
||||
/// alone.
|
||||
class PlaceSearch {
|
||||
PlaceSearch({this.finder});
|
||||
|
||||
/// The search of [pod], made the first time it is asked for, from the
|
||||
/// operator's `PHOTON_URL` (empty: none). Tests set their own.
|
||||
factory PlaceSearch.of(Serverpod pod) => _ofPod[pod] ??= PlaceSearch(
|
||||
finder: switch (Platform.environment['PHOTON_URL']?.trim()) {
|
||||
final url? when url.isNotEmpty => HttpPhoton(Uri.parse(url)),
|
||||
_ => null,
|
||||
},
|
||||
);
|
||||
static final _ofPod = Expando<PlaceSearch>('PlaceSearch');
|
||||
|
||||
/// Makes [search] the one of [pod].
|
||||
static void use(Serverpod pod, PlaceSearch search) => _ofPod[pod] = search;
|
||||
|
||||
/// The instance's Photon; none: it finds nothing.
|
||||
final PlaceFinder? finder;
|
||||
|
||||
/// The fewest and the most characters a query has.
|
||||
static const minQuery = 2;
|
||||
static const maxQuery = 200;
|
||||
|
||||
/// The most places an answer lists.
|
||||
static const limit = 8;
|
||||
|
||||
/// The most asked of Photon, as what is no place to go is left out of it.
|
||||
static const _askedOfPhoton = 12;
|
||||
|
||||
/// The most searches a person may make in a minute: the location sheet asks
|
||||
/// as they type, a pause after each word.
|
||||
static const perMinute = 60;
|
||||
|
||||
/// The languages Photon names places in; any other gets its own names.
|
||||
static const languages = {'en', 'de'};
|
||||
|
||||
final _asked = HashMap<UuidValue, Queue<DateTime>>();
|
||||
|
||||
/// The places [query] finds for [person], those nearest [near] first, named
|
||||
/// in [language]. Refused ([SearchRefused]) without a Photon, for a query
|
||||
/// or a point it doesn't take, beyond [perMinute], where Photon doesn't
|
||||
/// answer.
|
||||
Future<List<Place>> places(
|
||||
UuidValue person,
|
||||
String query, {
|
||||
PlacePoint? near,
|
||||
String? language,
|
||||
}) async {
|
||||
final finder = this.finder;
|
||||
if (finder == null) {
|
||||
throw SearchRefused(reason: 'off');
|
||||
}
|
||||
final trimmed = query.trim();
|
||||
if (trimmed.length < minQuery ||
|
||||
trimmed.length > maxQuery ||
|
||||
(near != null && !_onEarth(near))) {
|
||||
throw SearchRefused(reason: 'invalid');
|
||||
}
|
||||
_count(person);
|
||||
try {
|
||||
return placesOf(
|
||||
await finder.search(
|
||||
trimmed,
|
||||
near: near,
|
||||
language: languages.contains(language) ? language : null,
|
||||
limit: _askedOfPhoton,
|
||||
),
|
||||
).take(limit).toList();
|
||||
} on PhotonFailed {
|
||||
throw SearchRefused(reason: 'unreachable');
|
||||
}
|
||||
}
|
||||
|
||||
static bool _onEarth(PlacePoint point) =>
|
||||
point.lat.isFinite &&
|
||||
point.lon.isFinite &&
|
||||
point.lat.abs() <= 90 &&
|
||||
point.lon.abs() <= 180;
|
||||
|
||||
/// Counts a search of [person]'s, refusing it beyond [perMinute] in the
|
||||
/// last minute.
|
||||
void _count(UuidValue person) {
|
||||
final now = clock.now();
|
||||
final asked = _asked.putIfAbsent(person, Queue.new);
|
||||
while (asked.isNotEmpty &&
|
||||
now.difference(asked.first) >= const Duration(minutes: 1)) {
|
||||
asked.removeFirst();
|
||||
}
|
||||
if (asked.length >= perMinute) {
|
||||
throw SearchRefused(reason: 'tooOften');
|
||||
}
|
||||
asked.addLast(now);
|
||||
// Those who searched nothing for a minute are forgotten.
|
||||
_asked.removeWhere(
|
||||
(_, times) =>
|
||||
times.isEmpty ||
|
||||
now.difference(times.last) >= const Duration(minutes: 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:halkyon_todo_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_todo_server/src/search/place_search.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_auth_idp_server/core.dart';
|
||||
|
||||
/// Places found by the instance's Photon (ADR 0033, D32), for those signed
|
||||
/// in: Photon has no sign-in of its own, so the server alone reaches it.
|
||||
/// Neither the query nor the places are kept or logged, and a task's location
|
||||
/// stays in its sealed payload. Refusals are `SearchRefused`.
|
||||
class SearchEndpoint extends Endpoint {
|
||||
@override
|
||||
bool get requireLogin => true;
|
||||
|
||||
/// Whether the instance searches for places.
|
||||
Future<bool> offered(Session session) async =>
|
||||
PlaceSearch.of(session.serverpod).finder != null;
|
||||
|
||||
/// The places [query] finds, nearest [near] first (the map's centre, or
|
||||
/// the person), named in [language] (`en`, `de`; else each its own name).
|
||||
/// Refused where the instance has no Photon or it fails, for a query of
|
||||
/// fewer than `PlaceSearch.minQuery` or more than `PlaceSearch.maxQuery`
|
||||
/// characters, and beyond `PlaceSearch.perMinute` searches a minute.
|
||||
Future<List<Place>> places(
|
||||
Session session,
|
||||
String query, {
|
||||
PlacePoint? near,
|
||||
String? language,
|
||||
}) => PlaceSearch.of(session.serverpod).places(
|
||||
session.authenticated!.authUserId,
|
||||
query,
|
||||
near: near,
|
||||
language: language,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
### Why the instance searches nothing (ADR 0033, D32): `off` where it has no
|
||||
### Photon, `invalid` for a query shorter than `PlaceSearch.minQuery` or
|
||||
### longer than `PlaceSearch.maxQuery` characters or a place off the earth,
|
||||
### `tooOften` beyond `PlaceSearch.perMinute` searches a minute,
|
||||
### `unreachable` where Photon didn't answer.
|
||||
exception: SearchRefused
|
||||
fields:
|
||||
reason: String
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
import 'package:clock/clock.dart';
|
||||
import 'package:halkyon_todo_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_todo_server/src/search/photon.dart';
|
||||
import 'package:halkyon_todo_server/src/search/place_search.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../unit/search/photon_test.dart' show photonAnswer;
|
||||
import '../test_tools/serverpod_test_tools.dart';
|
||||
|
||||
/// Photon as the tests want it: what it answers, and what it was asked.
|
||||
class _FakePhoton implements PlaceFinder {
|
||||
final asked = <({String query, PlacePoint? near, String? language})>[];
|
||||
final limits = <int>[];
|
||||
bool failing = false;
|
||||
|
||||
/// What Photon finds.
|
||||
List<Map<String, Object?>> answer = const [
|
||||
{
|
||||
'lat': 52.47,
|
||||
'lon': 13.4,
|
||||
'name': 'Tempelhofer Feld',
|
||||
'type': 'other',
|
||||
'city': 'Berlin',
|
||||
'country': 'Germany',
|
||||
},
|
||||
];
|
||||
|
||||
@override
|
||||
Future<Map<String, Object?>> search(
|
||||
String query, {
|
||||
PlacePoint? near,
|
||||
String? language,
|
||||
required int limit,
|
||||
}) async {
|
||||
asked.add((query: query, near: near, language: language));
|
||||
limits.add(limit);
|
||||
if (failing) {
|
||||
throw const PhotonFailed('down');
|
||||
}
|
||||
return photonAnswer(answer);
|
||||
}
|
||||
}
|
||||
|
||||
Matcher _refused(String reason) =>
|
||||
throwsA(isA<SearchRefused>().having((r) => r.reason, 'reason', reason));
|
||||
|
||||
/// Places found by the instance's Photon (ADR 0033, D32): for the people
|
||||
/// signed in, where the instance has one, within a limit a minute.
|
||||
void main() {
|
||||
withServerpod('Searching for places', (sessionBuilder, endpoints) {
|
||||
late _FakePhoton photon;
|
||||
final alice = sessionBuilder.copyWith(
|
||||
authentication: AuthenticationOverride.authenticationInfo(
|
||||
const Uuid().v7(),
|
||||
{},
|
||||
),
|
||||
);
|
||||
final bob = sessionBuilder.copyWith(
|
||||
authentication: AuthenticationOverride.authenticationInfo(
|
||||
const Uuid().v7(),
|
||||
{},
|
||||
),
|
||||
);
|
||||
|
||||
void useSearch(PlaceSearch search) =>
|
||||
PlaceSearch.use(sessionBuilder.build().serverpod, search);
|
||||
|
||||
setUp(() {
|
||||
photon = _FakePhoton();
|
||||
useSearch(PlaceSearch(finder: photon));
|
||||
});
|
||||
|
||||
test('finds places for someone signed in, nearest the map first, in '
|
||||
'their language', () async {
|
||||
expect(await endpoints.search.offered(alice), isTrue);
|
||||
final places = await endpoints.search.places(
|
||||
alice,
|
||||
' Tempelhof ',
|
||||
near: PlacePoint(lat: 52.5, lon: 13.4),
|
||||
language: 'de',
|
||||
);
|
||||
|
||||
expect(places.single.name, 'Tempelhofer Feld');
|
||||
expect(places.single.detail, 'Berlin, Germany');
|
||||
final asked = photon.asked.single;
|
||||
expect(asked.query, 'Tempelhof');
|
||||
expect(asked.near?.lat, 52.5);
|
||||
expect(asked.language, 'de');
|
||||
});
|
||||
|
||||
test('a language Photon has no names in asks for none', () async {
|
||||
await endpoints.search.places(alice, 'Paris', language: 'fr');
|
||||
expect(photon.asked.single.language, isNull);
|
||||
});
|
||||
|
||||
test('searches nothing for nobody signed in', () async {
|
||||
final nobody = sessionBuilder.copyWith(
|
||||
authentication: AuthenticationOverride.unauthenticated(),
|
||||
);
|
||||
await expectLater(
|
||||
endpoints.search.places(nobody, 'Berlin'),
|
||||
throwsA(isA<ServerpodUnauthenticatedException>()),
|
||||
);
|
||||
await expectLater(
|
||||
endpoints.search.offered(nobody),
|
||||
throwsA(isA<ServerpodUnauthenticatedException>()),
|
||||
);
|
||||
expect(photon.asked, isEmpty);
|
||||
});
|
||||
|
||||
test('refuses every search where the instance has no Photon', () async {
|
||||
useSearch(PlaceSearch());
|
||||
expect(await endpoints.search.offered(alice), isFalse);
|
||||
await expectLater(
|
||||
endpoints.search.places(alice, 'Berlin'),
|
||||
_refused('off'),
|
||||
);
|
||||
});
|
||||
|
||||
test('refuses a query too short or too long, or a point off the earth, '
|
||||
'asking Photon nothing', () async {
|
||||
for (final query in ['', ' x ', 'x' * (PlaceSearch.maxQuery + 1)]) {
|
||||
await expectLater(
|
||||
endpoints.search.places(alice, query),
|
||||
_refused('invalid'),
|
||||
reason: query,
|
||||
);
|
||||
}
|
||||
await expectLater(
|
||||
endpoints.search.places(
|
||||
alice,
|
||||
'Berlin',
|
||||
near: PlacePoint(lat: 91, lon: 0),
|
||||
),
|
||||
_refused('invalid'),
|
||||
);
|
||||
expect(photon.asked, isEmpty);
|
||||
});
|
||||
|
||||
test('refuses a person beyond the limit a minute, not others, and '
|
||||
'takes them again a minute later', () async {
|
||||
final start = DateTime.utc(2026, 10, 29, 12);
|
||||
await withClock(Clock.fixed(start), () async {
|
||||
for (var i = 0; i < PlaceSearch.perMinute; i++) {
|
||||
await endpoints.search.places(alice, 'Berlin');
|
||||
}
|
||||
await expectLater(
|
||||
endpoints.search.places(alice, 'Berlin'),
|
||||
_refused('tooOften'),
|
||||
);
|
||||
await endpoints.search.places(bob, 'Berlin');
|
||||
});
|
||||
await withClock(
|
||||
Clock.fixed(start.add(const Duration(minutes: 1))),
|
||||
() => endpoints.search.places(alice, 'Berlin'),
|
||||
);
|
||||
});
|
||||
|
||||
test('asks Photon for more than it lists, as what is no place to go is '
|
||||
'left out, and lists no more than the limit', () async {
|
||||
photon.answer = [
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
'lat': 1,
|
||||
'lon': 2,
|
||||
'name': 'EDEKA',
|
||||
'type': 'other',
|
||||
'osm_value': 'parking',
|
||||
},
|
||||
for (var i = 0; i < PlaceSearch.limit + 2; i++)
|
||||
{'lat': 1, 'lon': 2, 'name': 'EDEKA $i', 'type': 'house'},
|
||||
];
|
||||
|
||||
final places = await endpoints.search.places(alice, 'EDEKA');
|
||||
|
||||
expect(photon.limits.single, greaterThan(PlaceSearch.limit));
|
||||
expect(places, hasLength(PlaceSearch.limit));
|
||||
expect(places.first.name, 'EDEKA 0');
|
||||
});
|
||||
|
||||
test("says Photon didn't answer where it fails", () async {
|
||||
photon.failing = true;
|
||||
await expectLater(
|
||||
endpoints.search.places(alice, 'Berlin'),
|
||||
_refused('unreachable'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -11,9 +11,14 @@
|
|||
// ignore_for_file: no_leading_underscores_for_local_identifiers
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _ida;
|
||||
import 'dart:io' as _idi;
|
||||
import 'package:halkyon_todo_server/src/generated/future_calls.dart'
|
||||
as _i772x17y;
|
||||
import 'package:halkyon_todo_server/src/generated/search/place.dart'
|
||||
as _ih6de0l6;
|
||||
import 'package:halkyon_todo_server/src/generated/search/place_point.dart'
|
||||
as _iywv9say;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_test/serverpod_test.dart' as _ist;
|
||||
import 'package:halkyon_todo_server/src/generated/protocol.dart';
|
||||
|
|
@ -150,6 +155,8 @@ void withServerpod(
|
|||
|
||||
class TestEndpoints {
|
||||
late final futureCalls = _FutureCalls();
|
||||
|
||||
late final _SearchEndpoint search;
|
||||
}
|
||||
|
||||
class _InternalTestEndpoints extends TestEndpoints
|
||||
|
|
@ -158,13 +165,94 @@ class _InternalTestEndpoints extends TestEndpoints
|
|||
void initialize(
|
||||
_is.SerializationManager serializationManager,
|
||||
_is.EndpointDispatch endpoints,
|
||||
) {}
|
||||
) {
|
||||
search = _SearchEndpoint(
|
||||
endpoints,
|
||||
serializationManager,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FutureCalls {
|
||||
late final pruning = _PruningFutureCall();
|
||||
}
|
||||
|
||||
class _SearchEndpoint {
|
||||
_SearchEndpoint(
|
||||
this._endpointDispatch,
|
||||
this._serializationManager,
|
||||
);
|
||||
|
||||
final _is.EndpointDispatch _endpointDispatch;
|
||||
|
||||
final _is.SerializationManager _serializationManager;
|
||||
|
||||
_ida.Future<bool> offered(_ist.TestSessionBuilder sessionBuilder) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'search',
|
||||
method: 'offered',
|
||||
);
|
||||
try {
|
||||
var _localCallContext = await _endpointDispatch.getMethodCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'search',
|
||||
methodName: 'offered',
|
||||
parameters: _ist.testObjectToJson({}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<bool>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_ida.Future<List<_ih6de0l6.Place>> places(
|
||||
_ist.TestSessionBuilder sessionBuilder,
|
||||
String query, {
|
||||
_iywv9say.PlacePoint? near,
|
||||
String? language,
|
||||
}) async {
|
||||
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
|
||||
var _localUniqueSession =
|
||||
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
|
||||
endpoint: 'search',
|
||||
method: 'places',
|
||||
);
|
||||
try {
|
||||
var _localCallContext = await _endpointDispatch.getMethodCallContext(
|
||||
createSessionCallback: (_) => _localUniqueSession,
|
||||
endpointPath: 'search',
|
||||
methodName: 'places',
|
||||
parameters: _ist.testObjectToJson({
|
||||
'query': query,
|
||||
'near': near,
|
||||
'language': language,
|
||||
}),
|
||||
serializationManager: _serializationManager,
|
||||
);
|
||||
var _localReturnValue =
|
||||
await (_localCallContext.method.call(
|
||||
_localUniqueSession,
|
||||
_localCallContext.arguments,
|
||||
)
|
||||
as _ida.Future<List<_ih6de0l6.Place>>);
|
||||
return _localReturnValue;
|
||||
} finally {
|
||||
await _localUniqueSession.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _PruningFutureCall {
|
||||
Future<void> pruneAccounts(_ist.TestSessionBuilder sessionBuilder) async {
|
||||
var _localUniqueSession =
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue