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

feat: api client #1

Merged
merged 9 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
omartinma marked this conversation as resolved.
Show resolved Hide resolved

[![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