Skip to content

Commit

Permalink
feat: openfoodfacts#349 - added support for Events API
Browse files Browse the repository at this point in the history
New files:
* `api_events_test.dart`: Tests around Events API.
* `BadgeBase.dart`: Events API: badge.
* `BadgeBase.g.dart`: generated
* `EventCreate.dart`: Events API: event create.
* `EventCreate.g.dart`: generated
* `events.dart`: Client calls of the Events API (Open Food Facts).
* `EventsBase.dart`: Events API: event.
* `EventsBase.g.dart`: generated
* `LeaderboardEntry.dart`: Events API: leaderboard entry.
* `LeaderboardEntry.g.dart`: generated

Impacted files:
* `JsonHelper.dart`: added a nullable timestamp conversion method.
* `OpenFoodAPIConfiguration.dart`: added host entries for Events API.
* `openfoodfacts.dart`: exported new Events related files.
* `UriHelper.dart`: added Events related method `getEventsUri`.
  • Loading branch information
monsieurtanuki committed Feb 15, 2022
1 parent cf492c5 commit c7dc345
Show file tree
Hide file tree
Showing 14 changed files with 688 additions and 0 deletions.
214 changes: 214 additions & 0 deletions lib/events.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import 'dart:async';
import 'dart:convert';

import 'package:http/http.dart';
import 'package:openfoodfacts/model/BadgeBase.dart';
import 'package:openfoodfacts/model/EventCreate.dart';
import 'package:openfoodfacts/model/EventsBase.dart';
import 'package:openfoodfacts/model/LeaderboardEntry.dart';
import 'package:openfoodfacts/model/User.dart';

import 'utils/HttpHelper.dart';
import 'utils/QueryType.dart';
import 'utils/UriHelper.dart';

/// Client calls of the Events API (Open Food Facts).
///
/// cf. https://events.openfoodfacts.net/docs
class EventsAPIClient {
EventsAPIClient._();

/// Returns all the [EventsBase], with optional filters and paging.
static Future<List<EventsBase>> getEvents({
final String? userId,
final String? deviceId,
final int? skip,
final int? limit,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
if (userId != null) {
parameters['user_id'] = userId;
}
if (deviceId != null) {
parameters['device_id'] = deviceId;
}
if (skip != null) {
parameters['skip'] = skip.toString();
}
if (limit != null) {
parameters['limit'] = limit.toString();
}
final Response response = await HttpHelper().doGetRequest(
UriHelper.getEventsUri(
path: '/events',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final List<EventsBase> result = <EventsBase>[];
final List<dynamic> json = jsonDecode(response.body) as List<dynamic>;
for (var element in json) {
result.add(EventsBase.fromJson(element));
}
return result;
}

/// Returns all the events counts, with optional filters.
static Future<Map<String, int>> getEventsCount({
final String? userId,
final String? deviceId,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
if (userId != null) {
parameters['user_id'] = userId;
}
if (deviceId != null) {
parameters['device_id'] = deviceId;
}
final Response response = await HttpHelper().doGetRequest(
UriHelper.getEventsUri(
path: '/events/count',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final Map<String, int> result = <String, int>{};
final Map<String, dynamic> json =
jsonDecode(response.body) as Map<String, dynamic>;
for (final String key in json.keys) {
result[key] = json[key] as int;
}
return result;
}

/// Returns the score, with optional filters.
static Future<int> getScores({
final String? userId,
final String? deviceId,
final String? eventType,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
if (userId != null) {
parameters['user_id'] = userId;
}
if (deviceId != null) {
parameters['device_id'] = deviceId;
}
if (eventType != null) {
parameters['event_type'] = eventType;
}
final Response response = await HttpHelper().doGetRequest(
UriHelper.getEventsUri(
path: '/scores',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final Map<String, dynamic> json =
jsonDecode(response.body) as Map<String, dynamic>;
return json['score'] as int;
}

/// Returns all the [BadgeBase], with optional filters.
static Future<List<BadgeBase>> getBadges({
final String? userId,
final String? deviceId,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
if (userId != null) {
parameters['user_id'] = userId;
}
if (deviceId != null) {
parameters['device_id'] = deviceId;
}
final Response response = await HttpHelper().doGetRequest(
UriHelper.getEventsUri(
path: '/badges',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final List<BadgeBase> result = <BadgeBase>[];
final List<dynamic> json = jsonDecode(response.body) as List<dynamic>;
for (var element in json) {
result.add(BadgeBase.fromJson(element));
}
return result;
}

/// Returns all the [LeaderboardEntry], with optional filters.
static Future<List<LeaderboardEntry>> getLeaderboard({
final String? eventType,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
if (eventType != null) {
parameters['event_type'] = eventType;
}
final Response response = await HttpHelper().doGetRequest(
UriHelper.getEventsUri(
path: '/leaderboard',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final List<LeaderboardEntry> result = <LeaderboardEntry>[];
final List<dynamic> json = jsonDecode(response.body) as List<dynamic>;
for (var element in json) {
result.add(LeaderboardEntry.fromJson(element));
}
return result;
}

/// Adds an event.
static Future<void> createEvent({
required final EventCreate eventCreate,
required final User user,
final QueryType? queryType,
}) async {
final Response response = await HttpHelper().doPostRequest(
UriHelper.getEventsUri(
path: '/events',
queryType: queryType,
),
eventCreate.toData(),
user,
queryType: queryType,
);
_checkResponse(response);
}

/// Throws a detailed exception if relevant. Does nothing if [response] is OK.
static void _checkResponse(final Response response) {
if (response.statusCode != 200) {
// cf. HTTPValidationError
String? exception;
try {
final Map<String, dynamic> json =
jsonDecode(response.body) as Map<String, dynamic>;
exception = json['detail'];
} catch (e) {
//
}
if (exception != null) {
throw Exception(exception);
}
// TODO(monsieurtanuki): have a look at ValidationError in https://events.openfoodfacts.org/docs
throw Exception('Wrong status code: ${response.statusCode}');
}
}
}
35 changes: 35 additions & 0 deletions lib/model/BadgeBase.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:json_annotation/json_annotation.dart';
import '../interface/JsonObject.dart';

part 'BadgeBase.g.dart';

/// Events API: badge.
@JsonSerializable()
class BadgeBase extends JsonObject {
@JsonKey(name: 'user_id')
final String? userId;

@JsonKey(name: 'badge_name')
final String badgeName;

@JsonKey()
final int level;

BadgeBase({
required this.badgeName,
required this.level,
this.userId,
});

factory BadgeBase.fromJson(Map<String, dynamic> json) =>
_$BadgeBaseFromJson(json);

@override
Map<String, dynamic> toJson() => _$BadgeBaseToJson(this);

@override
String toString() => 'BadgeBase(badgeName: $badgeName'
', level: $level'
'${userId == null ? '' : ', userId: $userId'}'
')';
}
19 changes: 19 additions & 0 deletions lib/model/BadgeBase.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions lib/model/EventCreate.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:openfoodfacts/utils/JsonHelper.dart';
import '../interface/JsonObject.dart';

part 'EventCreate.g.dart';

/// Events API: event create.
@JsonSerializable()
class EventCreate extends JsonObject {
@JsonKey(name: 'event_type')
final String eventType;

@JsonKey(fromJson: JsonHelper.nullableStringTimestampToDate)
final DateTime? timestamp;

@JsonKey(name: 'user_id')
final String? userId;

@JsonKey()
final String? barcode;

@JsonKey()
final int? points;

@JsonKey(name: 'device_id')
final String? deviceId;

EventCreate({
required this.eventType,
this.timestamp,
this.userId,
this.barcode,
this.points,
this.deviceId,
});

factory EventCreate.fromJson(Map<String, dynamic> json) =>
_$EventCreateFromJson(json);

@override
Map<String, dynamic> toJson() => _$EventCreateToJson(this);

@override
String toString() => 'EventCreate(eventType: $eventType'
'${timestamp == null ? '' : ', timestamp: $timestamp'}'
'${userId == null ? '' : ', userId: $userId'}'
'${barcode == null ? '' : ', barcode: $barcode'}'
'${points == null ? '' : ', points: $points'}'
'${deviceId == null ? '' : ', deviceId: $deviceId'}'
')';
}
26 changes: 26 additions & 0 deletions lib/model/EventCreate.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions lib/model/EventsBase.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:openfoodfacts/utils/JsonHelper.dart';
import '../interface/JsonObject.dart';

part 'EventsBase.g.dart';

/// Events API: event.
@JsonSerializable()
class EventsBase extends JsonObject {
@JsonKey(name: 'event_type')
final String eventType;

@JsonKey(fromJson: JsonHelper.nullableStringTimestampToDate)
final DateTime? timestamp;

@JsonKey(name: 'user_id')
final String? userId;

@JsonKey()
final String? barcode;

@JsonKey()
final int? points;

EventsBase({
required this.eventType,
this.timestamp,
this.userId,
this.barcode,
this.points,
});

factory EventsBase.fromJson(Map<String, dynamic> json) =>
_$EventsBaseFromJson(json);

@override
Map<String, dynamic> toJson() => _$EventsBaseToJson(this);

@override
String toString() => 'EventsBase(eventType: $eventType'
'${timestamp == null ? '' : ', timestamp: $timestamp'}'
'${userId == null ? '' : ', userId: $userId'}'
'${barcode == null ? '' : ', barcode: $barcode'}'
'${points == null ? '' : ', points: $points'}'
')';
}
Loading

0 comments on commit c7dc345

Please sign in to comment.