You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
samples/compass_app/server/test/server_test.dart

178 lines
5.0 KiB

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.md
4 months ago
import 'dart:convert';
import 'dart:io';
[Compass App] Home screen with booking list (#2428) This PR introduces the storage and retrieval of bookings. ### Server implementation - New booking routes: - GET `/booking`: Obtain the list ofer user bookings - GET `/booking/{id}`: Obtain the specific booking object - POST `/booking`: Creates a new booking - The `BookingApiModel` objects are incomplete, the `Destination` is a reference, not the full object, and the `Activity` list are also listed as references only. - Server booking always has an existing booking created (Alaska, North America) for demo purposes. - Storage is "in memory", stopping the server deletes all stored bookings. ### New `BookingRepository` - New repository class. - Both local and remote implementations. - Converts the `BookingApiModel` into a complete `Booking` that the app can use, or a `BookingSummary` that only contains the necessary information for the home screen. ### New `LocalDataService` - Service that loads hard-coded data or from assets. ### New `HomeScreen` - Route path: `/` - Loads and displays the list of created bookings from the `BookingRepository`. - Tap on a booking title opens the `BookingScreen`. - Floating Action Button to create a new booking. ### Changes in `BookingScreen` - Can be accessed at the end of creating a booking or from the home screen when tapping a booking title. - Two commands: - `createBooking`: Takes the stored `ItineraryConfig` and creates a booking, the booking is stored to the `BookingRepository` (locally or on the server). - `loadBooking`: Takes a booking ID and loads that existing booking from the `BookingRepository`. - Simplified navigation: Once at `BookingScreen`, user can only navigate back to `HomeScreen`. - Share button converted to `FloatingActionButton` ### Integration Tests - Updated to use new home screen. - Updated to cover opening an existing booking in tests. ### TODO Next - Refactor the `compass_model` project and move data model classes to `server` and `app`, then delete project. - Implement some user information for the home screen (e.g. retrieve user name and profile picture url) ### Screencast [Screencast from 2024-09-02 16-25-25.webm](https://github.com/user-attachments/assets/8aba4a61-def6-4752-a4e5-70cbed362524) ## 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.md
3 months ago
// TODO: Remove the compass_model and replace by a server-side model
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## 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.md
3 months ago
import 'package:compass_server/config/constants.dart';
import 'package:compass_server/model/activity/activity.dart';
[Compass App] Home screen with booking list (#2428) This PR introduces the storage and retrieval of bookings. ### Server implementation - New booking routes: - GET `/booking`: Obtain the list ofer user bookings - GET `/booking/{id}`: Obtain the specific booking object - POST `/booking`: Creates a new booking - The `BookingApiModel` objects are incomplete, the `Destination` is a reference, not the full object, and the `Activity` list are also listed as references only. - Server booking always has an existing booking created (Alaska, North America) for demo purposes. - Storage is "in memory", stopping the server deletes all stored bookings. ### New `BookingRepository` - New repository class. - Both local and remote implementations. - Converts the `BookingApiModel` into a complete `Booking` that the app can use, or a `BookingSummary` that only contains the necessary information for the home screen. ### New `LocalDataService` - Service that loads hard-coded data or from assets. ### New `HomeScreen` - Route path: `/` - Loads and displays the list of created bookings from the `BookingRepository`. - Tap on a booking title opens the `BookingScreen`. - Floating Action Button to create a new booking. ### Changes in `BookingScreen` - Can be accessed at the end of creating a booking or from the home screen when tapping a booking title. - Two commands: - `createBooking`: Takes the stored `ItineraryConfig` and creates a booking, the booking is stored to the `BookingRepository` (locally or on the server). - `loadBooking`: Takes a booking ID and loads that existing booking from the `BookingRepository`. - Simplified navigation: Once at `BookingScreen`, user can only navigate back to `HomeScreen`. - Share button converted to `FloatingActionButton` ### Integration Tests - Updated to use new home screen. - Updated to cover opening an existing booking in tests. ### TODO Next - Refactor the `compass_model` project and move data model classes to `server` and `app`, then delete project. - Implement some user information for the home screen (e.g. retrieve user name and profile picture url) ### Screencast [Screencast from 2024-09-02 16-25-25.webm](https://github.com/user-attachments/assets/8aba4a61-def6-4752-a4e5-70cbed362524) ## 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.md
3 months ago
import 'package:compass_server/model/booking/booking.dart';
import 'package:compass_server/model/continent/continent.dart';
import 'package:compass_server/model/destination/destination.dart';
import 'package:compass_server/model/login_request/login_request.dart';
import 'package:compass_server/model/login_response/login_response.dart';
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.md
4 months ago
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;
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## 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.md
3 months ago
var headers = {
'Authorization': 'Bearer ${Constants.token}',
};
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.md
4 months ago
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
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## 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.md
3 months ago
final response = await get(
Uri.parse('$host/continent'),
headers: headers,
);
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.md
4 months ago
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
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## 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.md
3 months ago
final response = await get(
Uri.parse('$host/destination'),
headers: headers,
);
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.md
4 months ago
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');
});
Compass App: Activities screen, error handling and logs (#2371) This PR introduces the Activities screen, handling of errors in view models and commands, and logs using the dart `logging` package. **Activities** - The screen loads a list of activities, split in daytime and evening activities, and the user can select them. - Server adds the endpoint `/destination/<id>/activitity` which was missing before. Screencast provided: [Screencast from 2024-07-29 16-29-02.webm](https://github.com/user-attachments/assets/a56024d8-0a9c-49e7-8fd0-c895da15badc) **Error handling** _UI Error handling:_ In the screencast you can see a `SnackBar` appearing, since the "Confirm" button is not yet implemented. The `saveActivities` Command returns an error `Result.error()`, then the error state is exposed by the Command and consumed by the listener in the `ActivityScreen`, which displays a `SnackBar` and consumes the state. Functionality is similar to the one found in [UI events - Consuming events can trigger state updates](https://developer.android.com/topic/architecture/ui-layer/events#consuming-trigger-updates) from the Android architecture guide, as the command state is "consumed" and cleared. The Snackbar also includes an action to "try again". Tapping on it calls to the failed Command `execute()` so users can run the action again. For example, here the `saveActivities` command failed, so `error` is `true`. Then we call to `clearResult()` to remove the failed status, and show a `SnackBar`, with the `SnackBarAction` that runs `saveActivities` again when tapped. ```dart if (widget.viewModel.saveActivities.error) { widget.viewModel.saveActivities.clearResult(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Error while saving activities'), action: SnackBarAction( label: "Try again", onPressed: widget.viewModel.saveActivities.execute, ), ), ); } ``` Since commands expose `running`, `error` and `completed`, it is easy to implement loading and error indicator widgets: [Screencast from 2024-07-29 16-55-42.webm](https://github.com/user-attachments/assets/fb5772d0-7b9d-4ded-8fa2-9ce347f4d555) As side node, we can easily simulate that state by adding these lines in any of the repository implementations: ```dart await Future.delayed(Durations.extralong1); return Result.error(Exception('ERROR!')); ``` _In-code error handling:_ The project introduces the `logging` package. In the entry point `main_development.dart` the log level is configured. Then in code, a `Logger` is creaded in each View Model with the name of the class. Then the log calls are used depending on the `Result` response, some finer traces are also added. By default, they are printed to the IDE debug console, for example: ``` [SearchFormViewModel] Continents (7) loaded [SearchFormViewModel] ItineraryConfig loaded [SearchFormViewModel] Selected continent: Asia [SearchFormViewModel] Selected date range: 2024-07-30 00:00:00.000 - 2024-08-08 00:00:00.000 [SearchFormViewModel] Set guests number: 1 [SearchFormViewModel] ItineraryConfig saved ``` **Other changes** - The json files containing destinations and activities are moved into the `app/assets/` folders, and the server is querying those files instead of their own copy. This is done to avoid file duplication but we can make a copy of those assets files for the server if we decide to. **TODO Next** - I will implement the "book a trip" screen which would complete the main application flow, which should introduce a more complex "component/use case" outside a view model. ## 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.md
4 months ago
test('Get Activities end-point', () async {
// Query /destination/alaska/activity end-point
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## 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.md
3 months ago
final response = await get(
Uri.parse('$host/destination/alaska/activity'),
headers: headers,
);
Compass App: Activities screen, error handling and logs (#2371) This PR introduces the Activities screen, handling of errors in view models and commands, and logs using the dart `logging` package. **Activities** - The screen loads a list of activities, split in daytime and evening activities, and the user can select them. - Server adds the endpoint `/destination/<id>/activitity` which was missing before. Screencast provided: [Screencast from 2024-07-29 16-29-02.webm](https://github.com/user-attachments/assets/a56024d8-0a9c-49e7-8fd0-c895da15badc) **Error handling** _UI Error handling:_ In the screencast you can see a `SnackBar` appearing, since the "Confirm" button is not yet implemented. The `saveActivities` Command returns an error `Result.error()`, then the error state is exposed by the Command and consumed by the listener in the `ActivityScreen`, which displays a `SnackBar` and consumes the state. Functionality is similar to the one found in [UI events - Consuming events can trigger state updates](https://developer.android.com/topic/architecture/ui-layer/events#consuming-trigger-updates) from the Android architecture guide, as the command state is "consumed" and cleared. The Snackbar also includes an action to "try again". Tapping on it calls to the failed Command `execute()` so users can run the action again. For example, here the `saveActivities` command failed, so `error` is `true`. Then we call to `clearResult()` to remove the failed status, and show a `SnackBar`, with the `SnackBarAction` that runs `saveActivities` again when tapped. ```dart if (widget.viewModel.saveActivities.error) { widget.viewModel.saveActivities.clearResult(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Error while saving activities'), action: SnackBarAction( label: "Try again", onPressed: widget.viewModel.saveActivities.execute, ), ), ); } ``` Since commands expose `running`, `error` and `completed`, it is easy to implement loading and error indicator widgets: [Screencast from 2024-07-29 16-55-42.webm](https://github.com/user-attachments/assets/fb5772d0-7b9d-4ded-8fa2-9ce347f4d555) As side node, we can easily simulate that state by adding these lines in any of the repository implementations: ```dart await Future.delayed(Durations.extralong1); return Result.error(Exception('ERROR!')); ``` _In-code error handling:_ The project introduces the `logging` package. In the entry point `main_development.dart` the log level is configured. Then in code, a `Logger` is creaded in each View Model with the name of the class. Then the log calls are used depending on the `Result` response, some finer traces are also added. By default, they are printed to the IDE debug console, for example: ``` [SearchFormViewModel] Continents (7) loaded [SearchFormViewModel] ItineraryConfig loaded [SearchFormViewModel] Selected continent: Asia [SearchFormViewModel] Selected date range: 2024-07-30 00:00:00.000 - 2024-08-08 00:00:00.000 [SearchFormViewModel] Set guests number: 1 [SearchFormViewModel] ItineraryConfig saved ``` **Other changes** - The json files containing destinations and activities are moved into the `app/assets/` folders, and the server is querying those files instead of their own copy. This is done to avoid file duplication but we can make a copy of those assets files for the server if we decide to. **TODO Next** - I will implement the "book a trip" screen which would complete the main application flow, which should introduce a more complex "component/use case" outside a view model. ## 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.md
4 months ago
expect(response.statusCode, 200);
// Parse json response list
final list = jsonDecode(response.body) as List<dynamic>;
// Parse items
final activity = list.map((element) => Activity.fromJson(element));
expect(activity.length, 20);
expect(activity.first.name, 'Glacier Trekking and Ice Climbing');
});
[Compass App] Home screen with booking list (#2428) This PR introduces the storage and retrieval of bookings. ### Server implementation - New booking routes: - GET `/booking`: Obtain the list ofer user bookings - GET `/booking/{id}`: Obtain the specific booking object - POST `/booking`: Creates a new booking - The `BookingApiModel` objects are incomplete, the `Destination` is a reference, not the full object, and the `Activity` list are also listed as references only. - Server booking always has an existing booking created (Alaska, North America) for demo purposes. - Storage is "in memory", stopping the server deletes all stored bookings. ### New `BookingRepository` - New repository class. - Both local and remote implementations. - Converts the `BookingApiModel` into a complete `Booking` that the app can use, or a `BookingSummary` that only contains the necessary information for the home screen. ### New `LocalDataService` - Service that loads hard-coded data or from assets. ### New `HomeScreen` - Route path: `/` - Loads and displays the list of created bookings from the `BookingRepository`. - Tap on a booking title opens the `BookingScreen`. - Floating Action Button to create a new booking. ### Changes in `BookingScreen` - Can be accessed at the end of creating a booking or from the home screen when tapping a booking title. - Two commands: - `createBooking`: Takes the stored `ItineraryConfig` and creates a booking, the booking is stored to the `BookingRepository` (locally or on the server). - `loadBooking`: Takes a booking ID and loads that existing booking from the `BookingRepository`. - Simplified navigation: Once at `BookingScreen`, user can only navigate back to `HomeScreen`. - Share button converted to `FloatingActionButton` ### Integration Tests - Updated to use new home screen. - Updated to cover opening an existing booking in tests. ### TODO Next - Refactor the `compass_model` project and move data model classes to `server` and `app`, then delete project. - Implement some user information for the home screen (e.g. retrieve user name and profile picture url) ### Screencast [Screencast from 2024-09-02 16-25-25.webm](https://github.com/user-attachments/assets/8aba4a61-def6-4752-a4e5-70cbed362524) ## 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.md
3 months ago
test('Get bookings end-point', () async {
final response = await get(
Uri.parse('$host/booking'),
headers: headers,
);
expect(response.statusCode, 200);
// Parse json response list
final list = jsonDecode(response.body) as List<dynamic>;
// Parse items
final bookings = list.map((element) => Booking.fromJson(element));
expect(bookings.length, 1);
expect(bookings.first.id, 0);
});
test('Get booking with id 0', () async {
final response = await get(
Uri.parse('$host/booking/0'),
headers: headers,
);
expect(response.statusCode, 200);
final booking = Booking.fromJson(jsonDecode(response.body));
// Got booking with id 0
expect(booking.id, 0);
});
test('Store a booking', () async {
final response = await post(
Uri.parse('$host/booking'),
headers: headers,
body: jsonEncode(
Booking(
name: "DESTINATION, CONTINENT",
startDate: DateTime(2024, 1, 1),
endDate: DateTime(2024, 2, 2),
destinationRef: 'REF',
activitiesRef: ['ACT1', 'ACT2'],
),
),
);
expect(response.statusCode, 201);
final booking = Booking.fromJson(jsonDecode(response.body));
// New ID should be 1
expect(booking.id, 1);
});
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.md
4 months ago
test('404', () async {
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## 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.md
3 months ago
final response = await get(
Uri.parse('$host/foobar'),
headers: headers,
);
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.md
4 months ago
expect(response.statusCode, 404);
});
Compass App: Basic auth (#2385) This PR introduces basic auth implementation between the app and the server as part of the architectural example. This PR is a big bigger than the previous ones so I hope this explanation helps: ### Server implementation The server introduces a new endpoint `/login` to perform login requests, which accepts login requests defined in the `LoginRequest` data class, with an email and password. The login process "simulates" checking on the email and password and responds with a "token" and user ID, defined by the `LoginResponse` data class. This is a simple hard-coded check and in any way a guide on how to implement authentication, just a way to demonstrate an architectural example. The server also implements a middleware in `server/lib/middleware/auth.dart`. This checks that the requests between the app and the server carry a valid authorization token in the headers, responding with an unauthorized error otherwise. ### App implementation The app introduces the following new parts: - `AuthTokenRepository`: In charge of storing the auth token. - `AuthLoginComponent`: In charge of performing login. - `AuthLogoutComponent`: In charge of performing logout. - `LoginScreen` with `LoginViewModel`: Displays the login screen. - `LogoutButton` with `LogoutViewModel`: Displays a logout button. The `AuthTokenRepository` acts as the source of truth to decide if the user is logged in or not. If the repository contains a token, it means the user is logged in, otherwise if the token is null, it means that the user is logged out. This repository is also a `ChangeNotifier`, which allows listening to change in it. The `GoRouter` has been modified so it listens to changes in the `AuthTokenRepository` using the `refreshListenable` property. It also implements a `redirect`, so if the token is set to `null` in the repository, the router will redirect users automatically to the login screen. This follows the example found in https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart On app start, `GoRouter` checks the `AuthTokenRepository`, if a token exists the user stays in `/`, if not, the user is redirected to `/login`. The `ApiClient` has also been modified, so it reads the stored token from the repository when performing network calls, and adds it to the auth headers. The two new components implement basic login and logout functionality. The `AuthLoginComponent` will send the request using the `ApiClient`, and then store the token from the response. The `AuthLogoutComponent` clears the stored token from the repository, and as well clears any existing itinerary configuration, effectively cleaning the app state. Performing logout redirects the user to the login screen, as explained. The `LoginScreen` uses the `AuthLoginComponent` internally, it displays two text fields and a login button, plus the application logo on top. A successful login redirects the user to `/`. The `LogoutButton` replaces the home button at the `/`, and on tap it will perform logout using the `AuthLogoutComponent`. **Development target app** The development target app works slightly different compared to the staging build. In this case, the `AuthTokenRepository` always contains a fake token, so the app believes it is always logged in. Auth is only used in the staging build when the server is involved. ## Screenshots <details> <summary>Screenshots</summary> The logout button in the top right corner: ![Screenshot from 2024-08-14 15-28-54](https://github.com/user-attachments/assets/1c5a37dc-9fa1-4950-917e-0c7272896780) The login screen: ![Screenshot from 2024-08-14 15-28-12](https://github.com/user-attachments/assets/3c26ccc2-8e3b-42d2-a230-d31048af6960) </details> ## 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.md
3 months ago
test('Login with valid credentials', () async {
final response = await post(
Uri.parse('$host/login'),
body: jsonEncode(
LoginRequest(
email: Constants.email,
password: Constants.password,
),
),
);
expect(response.statusCode, 200);
final loginResponse = LoginResponse.fromJson(jsonDecode(response.body));
expect(loginResponse.token, Constants.token);
expect(loginResponse.userId, Constants.userId);
});
test('Login with wrong credentials', () async {
final response = await post(
Uri.parse('$host/login'),
body: jsonEncode(
LoginRequest(
email: 'INVALID',
password: 'INVALID',
),
),
);
expect(response.statusCode, 401);
});
test('Unauthorized request', () async {
// Query /continent end-point
// No auth headers
final response = await get(
Uri.parse('$host/continent'),
);
expect(response.statusCode, 401);
});
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.md
4 months ago
}