Skip to content
This repository has been archived by the owner on Jan 9, 2024. It is now read-only.

Commit

Permalink
feat: api client (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
RuiMiguel authored Nov 21, 2023
1 parent ec3794b commit a0c6220
Show file tree
Hide file tree
Showing 15 changed files with 760 additions and 5 deletions.
8 changes: 7 additions & 1 deletion lib/app/view/app.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import 'package:api_client/api_client.dart';
import 'package:dash_ai_search/counter/counter.dart';
import 'package:dash_ai_search/l10n/l10n.dart';
import 'package:flutter/material.dart';

class App extends StatelessWidget {
const App({super.key});
const App({
required this.apiClient,
super.key,
});

final ApiClient apiClient;

@override
Widget build(BuildContext context) {
Expand Down
15 changes: 14 additions & 1 deletion lib/main_development.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import 'package:api_client/api_client.dart';
import 'package:dash_ai_search/app/app.dart';
import 'package:dash_ai_search/bootstrap.dart';

void main() {
bootstrap(() => const App());
bootstrap(
() {
final apiClient = ApiClient(
baseUrl: 'http://localhost:8080',
idTokenStream: const Stream.empty(),
refreshIdToken: () async => Future.value(),
);

return App(
apiClient: apiClient,
);
},
);
}
15 changes: 14 additions & 1 deletion lib/main_production.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import 'package:api_client/api_client.dart';
import 'package:dash_ai_search/app/app.dart';
import 'package:dash_ai_search/bootstrap.dart';

void main() {
bootstrap(() => const App());
bootstrap(
() {
final apiClient = ApiClient(
baseUrl: 'http://production',
idTokenStream: const Stream.empty(),
refreshIdToken: () async => Future.value(),
);

return App(
apiClient: apiClient,
);
},
);
}
15 changes: 14 additions & 1 deletion lib/main_staging.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import 'package:api_client/api_client.dart';
import 'package:dash_ai_search/app/app.dart';
import 'package:dash_ai_search/bootstrap.dart';

void main() {
bootstrap(() => const App());
bootstrap(
() {
final apiClient = ApiClient(
baseUrl: 'http://staging',
idTokenStream: const Stream.empty(),
refreshIdToken: () async => Future.value(),
);

return App(
apiClient: apiClient,
);
},
);
}
7 changes: 7 additions & 0 deletions packages/api_client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# See https://www.dartlang.org/guides/libraries/private-files

# Files and directories created by pub
.dart_tool/
.packages
build/
pubspec.lock
13 changes: 13 additions & 0 deletions packages/api_client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Api Client

[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason)
[![License: MIT][license_badge]][license_link]

Client to access the api


[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
1 change: 1 addition & 0 deletions packages/api_client/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:very_good_analysis/analysis_options.5.1.0.yaml
20 changes: 20 additions & 0 deletions packages/api_client/coverage_badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/api_client/lib/api_client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// Client to access the api
library api_client;

export 'src/api_client.dart';
188 changes: 188 additions & 0 deletions packages/api_client/lib/src/api_client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import 'dart:async';
import 'dart:io';

import 'package:http/http.dart' as http;

/// {@template api_client_error}
/// Error throw when accessing api failed.
///
/// Check [cause] and [stackTrace] for specific details.
/// {@endtemplate}
class ApiClientError implements Exception {
/// {@macro api_client_error}
ApiClientError(this.cause, this.stackTrace);

/// Error cause.
final dynamic cause;

/// The stack trace of the error.
final StackTrace stackTrace;

@override
String toString() {
return cause.toString();
}
}

/// Definition of a post call used by this client.
typedef PostCall = Future<http.Response> Function(
Uri, {
Object? body,
Map<String, String>? headers,
});

/// Definition of a patch call used by this client.
typedef PatchCall = Future<http.Response> Function(
Uri, {
Object? body,
Map<String, String>? headers,
});

/// Definition of a put call used by this client.
typedef PutCall = Future<http.Response> Function(
Uri, {
Object? body,
Map<String, String>? headers,
});

/// Definition of a get call used by this client.
typedef GetCall = Future<http.Response> Function(
Uri, {
Map<String, String>? headers,
});

/// {@template api_client}
/// Client to access the api
/// {@endtemplate}
class ApiClient {
/// {@macro api_client}
ApiClient({
required String baseUrl,
required Stream<String?> idTokenStream,
required Future<String?> Function() refreshIdToken,
PostCall postCall = http.post,
PutCall putCall = http.put,
PatchCall patchCall = http.patch,
GetCall getCall = http.get,
}) : _base = Uri.parse(baseUrl),
_post = postCall,
_put = putCall,
_patch = patchCall,
_get = getCall,
_refreshIdToken = refreshIdToken {
_idTokenSubscription = idTokenStream.listen((idToken) {
_idToken = idToken;
});
}

final Uri _base;
final PostCall _post;
final PostCall _put;
final PatchCall _patch;
final GetCall _get;
final Future<String?> Function() _refreshIdToken;

late final StreamSubscription<String?> _idTokenSubscription;
String? _idToken;

Map<String, String> get _headers => {
if (_idToken != null) 'Authorization': 'Bearer $_idToken',
};

Future<http.Response> _handleUnauthorized(
Future<http.Response> Function() sendRequest,
) async {
final response = await sendRequest();

if (response.statusCode == HttpStatus.unauthorized) {
_idToken = await _refreshIdToken();
return sendRequest();
}
return response;
}

/// Dispose of resources used by this client.
Future<void> dispose() async {
await _idTokenSubscription.cancel();
}

/// Sends a POST request to the specified [path] with the given [body].
Future<http.Response> post(
String path, {
Object? body,
Map<String, String>? queryParameters,
}) async {
return _handleUnauthorized(() async {
final response = await _post(
_base.replace(
path: path,
queryParameters: queryParameters,
),
body: body,
headers: _headers..addContentTypeJson(),
);

return response;
});
}

/// Sends a PATCH request to the specified [path] with the given [body].
Future<http.Response> patch(
String path, {
Object? body,
Map<String, String>? queryParameters,
}) async {
return _handleUnauthorized(() async {
final response = await _patch(
_base.replace(
path: path,
queryParameters: queryParameters,
),
body: body,
headers: _headers..addContentTypeJson(),
);

return response;
});
}

/// Sends a PUT request to the specified [path] with the given [body].
Future<http.Response> put(
String path, {
Object? body,
}) async {
return _handleUnauthorized(() async {
final response = await _put(
_base.replace(path: path),
body: body,
headers: _headers..addContentTypeJson(),
);

return response;
});
}

/// Sends a GET request to the specified [path].
Future<http.Response> get(
String path, {
Map<String, String>? queryParameters,
}) async {
return _handleUnauthorized(() async {
final response = await _get(
_base.replace(
path: path,
queryParameters: queryParameters,
),
headers: _headers,
);

return response;
});
}
}

extension on Map<String, String> {
void addContentTypeJson() {
addAll({HttpHeaders.contentTypeHeader: ContentType.json.value});
}
}
15 changes: 15 additions & 0 deletions packages/api_client/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: api_client
description: Client to access the api
version: 0.1.0+1
publish_to: none

environment:
sdk: ">=3.0.0 <4.0.0"

dev_dependencies:
mocktail: ^1.0.0
test: ^1.19.2
very_good_analysis: ^5.1.0

dependencies:
http: ^1.1.0
Loading

0 comments on commit a0c6220

Please sign in to comment.