mirror of https://github.com/flutter/samples.git
Compass App: Add "server" dart shelf-app and "shared" dart package (#2359)
This PR introduces two new subprojects: - `compass_server` under `compass_app/server`. - `compass_model` under `compass_app/model`. **`compass_server`** - Dart server implemented using `shelf`. - Created with the `dart create -t server-shelf` template. - Implements two REST endpoints: - `GET /continent`: Returns the list of `Continent` as JSON. - `GET /destination`: Returns the list of `Destination` as JSON. - Generated Docker files have been removed. - Implemented tests. - TODO: Implement some basic auth. **`compass_model`** - Dart package to share data model classes between the `server` and `app`. - Contains the data model classes (`Continent`, `Destination`). - Generated JSON from/To methods and data classes using `freezed`. - The sole purpose of this package is to host the data model. Other shared code should go in a different package. **Other changes** - Created an API Client to connect to the local dart server. - Created "remote" repositories, which also implement a basic in-memory caching strategy. - Created two dependency configurations, one with local repositories and one with remote repos. - Created two application main targets to select configuration: - `lib/main_development.dart` which starts the app with the "local" data configuration. - `lib/main_staging.dart` which starts the app with the "remove" (local dart server) configuration. - `lib/main.dart` still works as default entry point. - Implemented tests for remote repositories. ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.mdpull/2389/head
parent
496b467485
commit
be0b3dc0d1
@ -1,12 +0,0 @@
|
||||
class Continent {
|
||||
/// e.g. 'Europe'
|
||||
final String name;
|
||||
|
||||
/// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT'
|
||||
final String imageUrl;
|
||||
|
||||
Continent({
|
||||
required this.name,
|
||||
required this.imageUrl,
|
||||
});
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
/// Model class for Destination data
|
||||
class Destination {
|
||||
Destination({
|
||||
required this.ref,
|
||||
required this.name,
|
||||
required this.country,
|
||||
required this.continent,
|
||||
required this.knownFor,
|
||||
required this.tags,
|
||||
required this.imageUrl,
|
||||
});
|
||||
|
||||
/// e.g. 'alaska'
|
||||
final String ref;
|
||||
|
||||
/// e.g. 'Alaska'
|
||||
final String name;
|
||||
|
||||
/// e.g. 'United States'
|
||||
final String country;
|
||||
|
||||
/// e.g. 'North America'
|
||||
final String continent;
|
||||
|
||||
/// e.g. 'Alaska is a haven for outdoor enthusiasts ...'
|
||||
final String knownFor;
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
final List<String> tags;
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg'
|
||||
final String imageUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Destination{ref: $ref, name: $name, country: $country, continent: $continent, knownFor: $knownFor, tags: $tags, imageUrl: $imageUrl}';
|
||||
}
|
||||
|
||||
factory Destination.fromJson(Map<String, dynamic> json) {
|
||||
return Destination(
|
||||
ref: json['ref'] as String,
|
||||
name: json['name'] as String,
|
||||
country: json['country'] as String,
|
||||
continent: json['continent'] as String,
|
||||
knownFor: json['knownFor'] as String,
|
||||
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
imageUrl: json['imageUrl'] as String,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/api_client.dart';
|
||||
import 'continent_repository.dart';
|
||||
|
||||
/// Remote data source for [Continent].
|
||||
/// Implements basic local caching.
|
||||
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
|
||||
class ContinentRepositoryRemote implements ContinentRepository {
|
||||
ContinentRepositoryRemote({
|
||||
required ApiClient apiClient,
|
||||
}) : _apiClient = apiClient;
|
||||
|
||||
final ApiClient _apiClient;
|
||||
|
||||
List<Continent>? _cachedData;
|
||||
|
||||
@override
|
||||
Future<Result<List<Continent>>> getContinents() async {
|
||||
if (_cachedData == null) {
|
||||
// No cached data, request continents
|
||||
final result = await _apiClient.getContinents();
|
||||
if (result is Ok) {
|
||||
// Store value if result Ok
|
||||
_cachedData = result.asOk.value;
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// Return cached data if available
|
||||
return Result.ok(_cachedData!);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import 'package:compass_model/model.dart';
|
||||
|
||||
import '../../../utils/result.dart';
|
||||
import '../../services/api_client.dart';
|
||||
import 'destination_repository.dart';
|
||||
|
||||
/// Remote data source for [Destination].
|
||||
/// Implements basic local caching.
|
||||
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
|
||||
class DestinationRepositoryRemote implements DestinationRepository {
|
||||
DestinationRepositoryRemote({
|
||||
required ApiClient apiClient,
|
||||
}) : _apiClient = apiClient;
|
||||
|
||||
final ApiClient _apiClient;
|
||||
|
||||
List<Destination>? _cachedData;
|
||||
|
||||
@override
|
||||
Future<Result<List<Destination>>> getDestinations() async {
|
||||
if (_cachedData == null) {
|
||||
// No cached data, request destinations
|
||||
final result = await _apiClient.getDestinations();
|
||||
if (result is Ok) {
|
||||
// Store value if result Ok
|
||||
_cachedData = result.asOk.value;
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// Return cached data if available
|
||||
return Result.ok(_cachedData!);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:compass_model/model.dart';
|
||||
|
||||
import '../../utils/result.dart';
|
||||
|
||||
// TODO: Basic auth request
|
||||
// TODO: Configurable baseurl/host/port
|
||||
class ApiClient {
|
||||
Future<Result<List<Continent>>> getContinents() async {
|
||||
final client = HttpClient();
|
||||
try {
|
||||
final request = await client.get('localhost', 8080, '/continent');
|
||||
final response = await request.close();
|
||||
if (response.statusCode == 200) {
|
||||
final stringData = await response.transform(utf8.decoder).join();
|
||||
final json = jsonDecode(stringData) as List<dynamic>;
|
||||
return Result.ok(
|
||||
json.map((element) => Continent.fromJson(element)).toList());
|
||||
} else {
|
||||
return Result.error(const HttpException("Invalid response"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<List<Destination>>> getDestinations() async {
|
||||
final client = HttpClient();
|
||||
try {
|
||||
final request = await client.get('localhost', 8080, '/destination');
|
||||
final response = await request.close();
|
||||
if (response.statusCode == 200) {
|
||||
final stringData = await response.transform(utf8.decoder).join();
|
||||
final json = jsonDecode(stringData) as List<dynamic>;
|
||||
return Result.ok(
|
||||
json.map((element) => Destination.fromJson(element)).toList());
|
||||
} else {
|
||||
return Result.error(const HttpException("Invalid response"));
|
||||
}
|
||||
} on Exception catch (error) {
|
||||
return Result.error(error);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'config/dependencies.dart';
|
||||
import 'main.dart';
|
||||
|
||||
/// Development config entry point.
|
||||
/// Launch with `flutter run --target lib/main_development.dart`.
|
||||
/// Uses local data.
|
||||
void main() {
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: providersLocal,
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'config/dependencies.dart';
|
||||
import 'main.dart';
|
||||
|
||||
/// Staging config entry point.
|
||||
/// Launch with `flutter run --target lib/main_staging.dart`.
|
||||
/// Uses remote data from a server.
|
||||
void main() {
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: providersRemote,
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import 'package:compass_app/data/repositories/continent/continent_repository.dart';
|
||||
import 'package:compass_app/data/repositories/continent/continent_repository_remote.dart';
|
||||
import 'package:compass_app/utils/result.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../util/fakes/services/fake_api_client.dart';
|
||||
|
||||
void main() {
|
||||
group('ContinentRepositoryRemote tests', () {
|
||||
late FakeApiClient apiClient;
|
||||
late ContinentRepository repository;
|
||||
|
||||
setUp(() {
|
||||
apiClient = FakeApiClient();
|
||||
repository = ContinentRepositoryRemote(apiClient: apiClient);
|
||||
});
|
||||
|
||||
test('should get continents', () async {
|
||||
final result = await repository.getContinents();
|
||||
expect(result, isA<Ok>());
|
||||
|
||||
final list = result.asOk.value;
|
||||
expect(list.length, 3);
|
||||
|
||||
final destination = list.first;
|
||||
expect(destination.name, 'CONTINENT');
|
||||
|
||||
// Only one request happened
|
||||
expect(apiClient.requestCount, 1);
|
||||
});
|
||||
|
||||
test('should get continents from cache', () async {
|
||||
// Request continents once
|
||||
var result = await repository.getContinents();
|
||||
expect(result, isA<Ok>());
|
||||
|
||||
// Request continents another time
|
||||
result = await repository.getContinents();
|
||||
expect(result, isA<Ok>());
|
||||
|
||||
// Only one request happened
|
||||
expect(apiClient.requestCount, 1);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import 'package:compass_app/data/repositories/destination/destination_repository.dart';
|
||||
import 'package:compass_app/data/repositories/destination/destination_repository_remote.dart';
|
||||
import 'package:compass_app/utils/result.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../util/fakes/services/fake_api_client.dart';
|
||||
|
||||
void main() {
|
||||
group('DestinationRepositoryRemote tests', () {
|
||||
late FakeApiClient apiClient;
|
||||
late DestinationRepository repository;
|
||||
|
||||
setUp(() {
|
||||
apiClient = FakeApiClient();
|
||||
repository = DestinationRepositoryRemote(apiClient: apiClient);
|
||||
});
|
||||
|
||||
test('should get destinations', () async {
|
||||
final result = await repository.getDestinations();
|
||||
expect(result, isA<Ok>());
|
||||
|
||||
final list = result.asOk.value;
|
||||
expect(list.length, 2);
|
||||
|
||||
final destination = list.first;
|
||||
expect(destination.name, 'name1');
|
||||
|
||||
// Only one request happened
|
||||
expect(apiClient.requestCount, 1);
|
||||
});
|
||||
|
||||
test('should get destinations from cache', () async {
|
||||
// Request destination once
|
||||
var result = await repository.getDestinations();
|
||||
expect(result, isA<Ok>());
|
||||
|
||||
// Request destination another time
|
||||
result = await repository.getDestinations();
|
||||
expect(result, isA<Ok>());
|
||||
|
||||
// Only one request happened
|
||||
expect(apiClient.requestCount, 1);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import 'package:compass_app/data/services/api_client.dart';
|
||||
import 'package:compass_app/utils/result.dart';
|
||||
import 'package:compass_model/model.dart';
|
||||
|
||||
class FakeApiClient implements ApiClient {
|
||||
// Should not increase when using cached data
|
||||
int requestCount = 0;
|
||||
|
||||
@override
|
||||
Future<Result<List<Continent>>> getContinents() async {
|
||||
requestCount++;
|
||||
return Result.ok([
|
||||
const Continent(name: 'CONTINENT', imageUrl: 'URL'),
|
||||
const Continent(name: 'CONTINENT2', imageUrl: 'URL'),
|
||||
const Continent(name: 'CONTINENT3', imageUrl: 'URL'),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Result<List<Destination>>> getDestinations() async {
|
||||
requestCount++;
|
||||
return Result.ok(
|
||||
[
|
||||
const Destination(
|
||||
ref: 'ref1',
|
||||
name: 'name1',
|
||||
country: 'country1',
|
||||
continent: 'Europe',
|
||||
knownFor: 'knownFor1',
|
||||
tags: ['tags1'],
|
||||
imageUrl: 'imageUrl1',
|
||||
),
|
||||
const Destination(
|
||||
ref: 'ref2',
|
||||
name: 'name2',
|
||||
country: 'country2',
|
||||
continent: 'Europe',
|
||||
knownFor: 'knownFor2',
|
||||
tags: ['tags2'],
|
||||
imageUrl: 'imageUrl2',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
# https://dart.dev/guides/libraries/private-files
|
||||
# Created by `dart pub`
|
||||
.dart_tool/
|
||||
|
||||
# Avoid committing pubspec.lock for library packages; see
|
||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
pubspec.lock
|
@ -0,0 +1,3 @@
|
||||
# compass_model
|
||||
|
||||
Shared Data Model for the `compass_app` example.
|
@ -0,0 +1,30 @@
|
||||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
# linter:
|
||||
# rules:
|
||||
# - camel_case_types
|
||||
|
||||
# analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
||||
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
@ -0,0 +1,4 @@
|
||||
library;
|
||||
|
||||
export 'src/model/continent/continent.dart';
|
||||
export 'src/model/destination/destination.dart';
|
@ -0,0 +1,19 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'continent.freezed.dart';
|
||||
|
||||
part 'continent.g.dart';
|
||||
|
||||
@freezed
|
||||
class Continent with _$Continent {
|
||||
const factory Continent({
|
||||
/// e.g. 'Europe'
|
||||
required String name,
|
||||
|
||||
/// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT'
|
||||
required String imageUrl,
|
||||
}) = _Continent;
|
||||
|
||||
factory Continent.fromJson(Map<String, Object?> json) =>
|
||||
_$ContinentFromJson(json);
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'continent.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
Continent _$ContinentFromJson(Map<String, dynamic> json) {
|
||||
return _Continent.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Continent {
|
||||
/// e.g. 'Europe'
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT'
|
||||
String get imageUrl => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this Continent to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of Continent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$ContinentCopyWith<Continent> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ContinentCopyWith<$Res> {
|
||||
factory $ContinentCopyWith(Continent value, $Res Function(Continent) then) =
|
||||
_$ContinentCopyWithImpl<$Res, Continent>;
|
||||
@useResult
|
||||
$Res call({String name, String imageUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ContinentCopyWithImpl<$Res, $Val extends Continent>
|
||||
implements $ContinentCopyWith<$Res> {
|
||||
_$ContinentCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of Continent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? imageUrl = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
imageUrl: null == imageUrl
|
||||
? _value.imageUrl
|
||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$ContinentImplCopyWith<$Res>
|
||||
implements $ContinentCopyWith<$Res> {
|
||||
factory _$$ContinentImplCopyWith(
|
||||
_$ContinentImpl value, $Res Function(_$ContinentImpl) then) =
|
||||
__$$ContinentImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({String name, String imageUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$ContinentImplCopyWithImpl<$Res>
|
||||
extends _$ContinentCopyWithImpl<$Res, _$ContinentImpl>
|
||||
implements _$$ContinentImplCopyWith<$Res> {
|
||||
__$$ContinentImplCopyWithImpl(
|
||||
_$ContinentImpl _value, $Res Function(_$ContinentImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of Continent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? name = null,
|
||||
Object? imageUrl = null,
|
||||
}) {
|
||||
return _then(_$ContinentImpl(
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
imageUrl: null == imageUrl
|
||||
? _value.imageUrl
|
||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$ContinentImpl implements _Continent {
|
||||
const _$ContinentImpl({required this.name, required this.imageUrl});
|
||||
|
||||
factory _$ContinentImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$ContinentImplFromJson(json);
|
||||
|
||||
/// e.g. 'Europe'
|
||||
@override
|
||||
final String name;
|
||||
|
||||
/// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT'
|
||||
@override
|
||||
final String imageUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Continent(name: $name, imageUrl: $imageUrl)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ContinentImpl &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.imageUrl, imageUrl) ||
|
||||
other.imageUrl == imageUrl));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, name, imageUrl);
|
||||
|
||||
/// Create a copy of Continent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$ContinentImplCopyWith<_$ContinentImpl> get copyWith =>
|
||||
__$$ContinentImplCopyWithImpl<_$ContinentImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$ContinentImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Continent implements Continent {
|
||||
const factory _Continent(
|
||||
{required final String name,
|
||||
required final String imageUrl}) = _$ContinentImpl;
|
||||
|
||||
factory _Continent.fromJson(Map<String, dynamic> json) =
|
||||
_$ContinentImpl.fromJson;
|
||||
|
||||
/// e.g. 'Europe'
|
||||
@override
|
||||
String get name;
|
||||
|
||||
/// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT'
|
||||
@override
|
||||
String get imageUrl;
|
||||
|
||||
/// Create a copy of Continent
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$ContinentImplCopyWith<_$ContinentImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'continent.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$ContinentImpl _$$ContinentImplFromJson(Map<String, dynamic> json) =>
|
||||
_$ContinentImpl(
|
||||
name: json['name'] as String,
|
||||
imageUrl: json['imageUrl'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ContinentImplToJson(_$ContinentImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'name': instance.name,
|
||||
'imageUrl': instance.imageUrl,
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'destination.freezed.dart';
|
||||
|
||||
part 'destination.g.dart';
|
||||
|
||||
@freezed
|
||||
class Destination with _$Destination {
|
||||
const factory Destination({
|
||||
/// e.g. 'alaska'
|
||||
required String ref,
|
||||
|
||||
/// e.g. 'Alaska'
|
||||
required String name,
|
||||
|
||||
/// e.g. 'United States'
|
||||
required String country,
|
||||
|
||||
/// e.g. 'North America'
|
||||
required String continent,
|
||||
|
||||
/// e.g. 'Alaska is a haven for outdoor enthusiasts ...'
|
||||
required String knownFor,
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
required List<String> tags,
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg'
|
||||
required String imageUrl,
|
||||
}) = _Destination;
|
||||
|
||||
factory Destination.fromJson(Map<String, Object?> json) =>
|
||||
_$DestinationFromJson(json);
|
||||
}
|
@ -0,0 +1,339 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'destination.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
Destination _$DestinationFromJson(Map<String, dynamic> json) {
|
||||
return _Destination.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Destination {
|
||||
/// e.g. 'alaska'
|
||||
String get ref => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'Alaska'
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'United States'
|
||||
String get country => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'North America'
|
||||
String get continent => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'Alaska is a haven for outdoor enthusiasts ...'
|
||||
String get knownFor => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
List<String> get tags => throw _privateConstructorUsedError;
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg'
|
||||
String get imageUrl => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this Destination to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of Destination
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$DestinationCopyWith<Destination> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $DestinationCopyWith<$Res> {
|
||||
factory $DestinationCopyWith(
|
||||
Destination value, $Res Function(Destination) then) =
|
||||
_$DestinationCopyWithImpl<$Res, Destination>;
|
||||
@useResult
|
||||
$Res call(
|
||||
{String ref,
|
||||
String name,
|
||||
String country,
|
||||
String continent,
|
||||
String knownFor,
|
||||
List<String> tags,
|
||||
String imageUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$DestinationCopyWithImpl<$Res, $Val extends Destination>
|
||||
implements $DestinationCopyWith<$Res> {
|
||||
_$DestinationCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of Destination
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? ref = null,
|
||||
Object? name = null,
|
||||
Object? country = null,
|
||||
Object? continent = null,
|
||||
Object? knownFor = null,
|
||||
Object? tags = null,
|
||||
Object? imageUrl = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
ref: null == ref
|
||||
? _value.ref
|
||||
: ref // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
country: null == country
|
||||
? _value.country
|
||||
: country // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
continent: null == continent
|
||||
? _value.continent
|
||||
: continent // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
knownFor: null == knownFor
|
||||
? _value.knownFor
|
||||
: knownFor // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
tags: null == tags
|
||||
? _value.tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
imageUrl: null == imageUrl
|
||||
? _value.imageUrl
|
||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
) as $Val);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$DestinationImplCopyWith<$Res>
|
||||
implements $DestinationCopyWith<$Res> {
|
||||
factory _$$DestinationImplCopyWith(
|
||||
_$DestinationImpl value, $Res Function(_$DestinationImpl) then) =
|
||||
__$$DestinationImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call(
|
||||
{String ref,
|
||||
String name,
|
||||
String country,
|
||||
String continent,
|
||||
String knownFor,
|
||||
List<String> tags,
|
||||
String imageUrl});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$DestinationImplCopyWithImpl<$Res>
|
||||
extends _$DestinationCopyWithImpl<$Res, _$DestinationImpl>
|
||||
implements _$$DestinationImplCopyWith<$Res> {
|
||||
__$$DestinationImplCopyWithImpl(
|
||||
_$DestinationImpl _value, $Res Function(_$DestinationImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of Destination
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? ref = null,
|
||||
Object? name = null,
|
||||
Object? country = null,
|
||||
Object? continent = null,
|
||||
Object? knownFor = null,
|
||||
Object? tags = null,
|
||||
Object? imageUrl = null,
|
||||
}) {
|
||||
return _then(_$DestinationImpl(
|
||||
ref: null == ref
|
||||
? _value.ref
|
||||
: ref // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: null == name
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
country: null == country
|
||||
? _value.country
|
||||
: country // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
continent: null == continent
|
||||
? _value.continent
|
||||
: continent // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
knownFor: null == knownFor
|
||||
? _value.knownFor
|
||||
: knownFor // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
tags: null == tags
|
||||
? _value._tags
|
||||
: tags // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,
|
||||
imageUrl: null == imageUrl
|
||||
? _value.imageUrl
|
||||
: imageUrl // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$DestinationImpl implements _Destination {
|
||||
const _$DestinationImpl(
|
||||
{required this.ref,
|
||||
required this.name,
|
||||
required this.country,
|
||||
required this.continent,
|
||||
required this.knownFor,
|
||||
required final List<String> tags,
|
||||
required this.imageUrl})
|
||||
: _tags = tags;
|
||||
|
||||
factory _$DestinationImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$DestinationImplFromJson(json);
|
||||
|
||||
/// e.g. 'alaska'
|
||||
@override
|
||||
final String ref;
|
||||
|
||||
/// e.g. 'Alaska'
|
||||
@override
|
||||
final String name;
|
||||
|
||||
/// e.g. 'United States'
|
||||
@override
|
||||
final String country;
|
||||
|
||||
/// e.g. 'North America'
|
||||
@override
|
||||
final String continent;
|
||||
|
||||
/// e.g. 'Alaska is a haven for outdoor enthusiasts ...'
|
||||
@override
|
||||
final String knownFor;
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
final List<String> _tags;
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
@override
|
||||
List<String> get tags {
|
||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_tags);
|
||||
}
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg'
|
||||
@override
|
||||
final String imageUrl;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Destination(ref: $ref, name: $name, country: $country, continent: $continent, knownFor: $knownFor, tags: $tags, imageUrl: $imageUrl)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$DestinationImpl &&
|
||||
(identical(other.ref, ref) || other.ref == ref) &&
|
||||
(identical(other.name, name) || other.name == name) &&
|
||||
(identical(other.country, country) || other.country == country) &&
|
||||
(identical(other.continent, continent) ||
|
||||
other.continent == continent) &&
|
||||
(identical(other.knownFor, knownFor) ||
|
||||
other.knownFor == knownFor) &&
|
||||
const DeepCollectionEquality().equals(other._tags, _tags) &&
|
||||
(identical(other.imageUrl, imageUrl) ||
|
||||
other.imageUrl == imageUrl));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, ref, name, country, continent,
|
||||
knownFor, const DeepCollectionEquality().hash(_tags), imageUrl);
|
||||
|
||||
/// Create a copy of Destination
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$DestinationImplCopyWith<_$DestinationImpl> get copyWith =>
|
||||
__$$DestinationImplCopyWithImpl<_$DestinationImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$DestinationImplToJson(
|
||||
this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Destination implements Destination {
|
||||
const factory _Destination(
|
||||
{required final String ref,
|
||||
required final String name,
|
||||
required final String country,
|
||||
required final String continent,
|
||||
required final String knownFor,
|
||||
required final List<String> tags,
|
||||
required final String imageUrl}) = _$DestinationImpl;
|
||||
|
||||
factory _Destination.fromJson(Map<String, dynamic> json) =
|
||||
_$DestinationImpl.fromJson;
|
||||
|
||||
/// e.g. 'alaska'
|
||||
@override
|
||||
String get ref;
|
||||
|
||||
/// e.g. 'Alaska'
|
||||
@override
|
||||
String get name;
|
||||
|
||||
/// e.g. 'United States'
|
||||
@override
|
||||
String get country;
|
||||
|
||||
/// e.g. 'North America'
|
||||
@override
|
||||
String get continent;
|
||||
|
||||
/// e.g. 'Alaska is a haven for outdoor enthusiasts ...'
|
||||
@override
|
||||
String get knownFor;
|
||||
|
||||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching']
|
||||
@override
|
||||
List<String> get tags;
|
||||
|
||||
/// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg'
|
||||
@override
|
||||
String get imageUrl;
|
||||
|
||||
/// Create a copy of Destination
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$DestinationImplCopyWith<_$DestinationImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'destination.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$DestinationImpl _$$DestinationImplFromJson(Map<String, dynamic> json) =>
|
||||
_$DestinationImpl(
|
||||
ref: json['ref'] as String,
|
||||
name: json['name'] as String,
|
||||
country: json['country'] as String,
|
||||
continent: json['continent'] as String,
|
||||
knownFor: json['knownFor'] as String,
|
||||
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
imageUrl: json['imageUrl'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$DestinationImplToJson(_$DestinationImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'ref': instance.ref,
|
||||
'name': instance.name,
|
||||
'country': instance.country,
|
||||
'continent': instance.continent,
|
||||
'knownFor': instance.knownFor,
|
||||
'tags': instance.tags,
|
||||
'imageUrl': instance.imageUrl,
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
name: compass_model
|
||||
description: Compass App Data Model
|
||||
publish_to: 'none'
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.4.3
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.4.11
|
||||
freezed: ^2.5.7
|
||||
json_serializable: ^6.8.0
|
||||
lints: ^3.0.0
|
||||
test: ^1.24.0
|
||||
dependencies:
|
||||
freezed_annotation: ^2.4.4
|
||||
json_annotation: ^4.9.0
|
@ -0,0 +1,3 @@
|
||||
# https://dart.dev/guides/libraries/private-files
|
||||
# Created by `dart pub`
|
||||
.dart_tool/
|
@ -0,0 +1,13 @@
|
||||
A server app built using [Shelf](https://pub.dev/packages/shelf).
|
||||
|
||||
# Running the server
|
||||
|
||||
## Running with the Dart SDK
|
||||
|
||||
You can run the example with the [Dart SDK](https://dart.dev/get-dart)
|
||||
like this:
|
||||
|
||||
```
|
||||
$ dart run
|
||||
Server listening on port 8080
|
||||
```
|
@ -0,0 +1,30 @@
|
||||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
# linter:
|
||||
# rules:
|
||||
# - camel_case_types
|
||||
|
||||
# analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
||||
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,26 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:compass_server/routes/continent.dart';
|
||||
import 'package:compass_server/routes/destination.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart';
|
||||
import 'package:shelf_router/shelf_router.dart';
|
||||
|
||||
// Configure routes.
|
||||
final _router = Router()
|
||||
..get('/continent', continentHandler)
|
||||
..get('/destination', destinationHandler);
|
||||
|
||||
void main(List<String> args) async {
|
||||
// Use any available host or container IP (usually `0.0.0.0`).
|
||||
final ip = InternetAddress.anyIPv4;
|
||||
|
||||
// Configure a pipeline that logs requests.
|
||||
final handler =
|
||||
Pipeline().addMiddleware(logRequests()).addHandler(_router.call);
|
||||
|
||||
// For running in containers, we respect the PORT environment variable.
|
||||
final port = int.parse(Platform.environment['PORT'] ?? '8080');
|
||||
final server = await serve(handler, ip, port);
|
||||
print('Server listening on port ${server.port}');
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:compass_model/model.dart';
|
||||
|
||||
final _continents = [
|
||||
Continent(
|
||||
name: 'Europe',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/TmR12QdlVTT',
|
||||
),
|
||||
Continent(
|
||||
name: 'Asia',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/VJ8BXlQg8O1',
|
||||
),
|
||||
Continent(
|
||||
name: 'South America',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/flm_-o1aI8e',
|
||||
),
|
||||
Continent(
|
||||
name: 'Africa',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/-nzi8yFOBpF',
|
||||
),
|
||||
Continent(
|
||||
name: 'North America',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/jlbgFDrSUVE',
|
||||
),
|
||||
Continent(
|
||||
name: 'Oceania',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/vxyrDE-fZVL',
|
||||
),
|
||||
Continent(
|
||||
name: 'Australia',
|
||||
imageUrl: 'https://rstr.in/google/tripedia/z6vy6HeRyvZ',
|
||||
),
|
||||
];
|
||||
|
||||
Response continentHandler(Request req) {
|
||||
return Response.ok(jsonEncode(_continents));
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:shelf/shelf.dart';
|
||||
|
||||
Future<Response> destinationHandler(Request req) async {
|
||||
final file = File('assets/destinations.json');
|
||||
final jsonString = await file.readAsString();
|
||||
return Response.ok(jsonString);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
name: compass_server
|
||||
description: A server app using the shelf package and Docker.
|
||||
publish_to: 'none'
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.4.3
|
||||
|
||||
dependencies:
|
||||
args: ^2.4.0
|
||||
shelf: ^1.4.0
|
||||
shelf_router: ^1.1.0
|
||||
compass_model:
|
||||
path: ../model
|
||||
|
||||
dev_dependencies:
|
||||
http: ^1.1.0
|
||||
lints: ^3.0.0
|
||||
test: ^1.24.0
|
@ -0,0 +1,53 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:compass_model/model.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
final port = '8080';
|
||||
final host = 'http://0.0.0.0:$port';
|
||||
late Process p;
|
||||
|
||||
setUp(() async {
|
||||
p = await Process.start(
|
||||
'dart',
|
||||
['run', 'bin/compass_server.dart'],
|
||||
environment: {'PORT': port},
|
||||
);
|
||||
// Wait for server to start and print to stdout.
|
||||
await p.stdout.first;
|
||||
});
|
||||
|
||||
tearDown(() => p.kill());
|
||||
|
||||
test('Get Continent end-point', () async {
|
||||
// Query /continent end-point
|
||||
final response = await get(Uri.parse('$host/continent'));
|
||||
expect(response.statusCode, 200);
|
||||
// Parse json response list
|
||||
final list = jsonDecode(response.body) as List<dynamic>;
|
||||
// Parse items
|
||||
final continents = list.map((element) => Continent.fromJson(element));
|
||||
expect(continents.length, 7);
|
||||
expect(continents.first.name, 'Europe');
|
||||
});
|
||||
|
||||
test('Get Destination end-point', () async {
|
||||
// Query /destination end-point
|
||||
final response = await get(Uri.parse('$host/destination'));
|
||||
expect(response.statusCode, 200);
|
||||
// Parse json response list
|
||||
final list = jsonDecode(response.body) as List<dynamic>;
|
||||
// Parse items
|
||||
final destination = list.map((element) => Destination.fromJson(element));
|
||||
expect(destination.length, 137);
|
||||
expect(destination.first.name, 'Alaska');
|
||||
});
|
||||
|
||||
test('404', () async {
|
||||
final response = await get(Uri.parse('$host/foobar'));
|
||||
expect(response.statusCode, 404);
|
||||
});
|
||||
}
|
Loading…
Reference in new issue